diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index 8b170a05982c..e54139807e0d 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -12,27 +12,30 @@ parameters: - name: cacheKeyDocker type: string default: ".devcontainer/Dockerfile" -- name: cacheKeyDockerVersion +- name: cacheKeyVersion type: string - default: $(cacheKeyBuildImage) -- name: cacheKeyDockerName + default: $(cacheKeyVersion) +- name: pathCacheTemp type: string - default: envoy_build_image -- name: cacheKeyDockerPath - type: string - default: /mnt/docker -- name: cacheKeyDockerTmpDir + default: $(pathCacheTemp) + +- name: tmpfsCacheDisabled type: string - default: /mnt/docker_cache -- name: cacheKeyDockerNoTmpfs + default: '' +- name: tmpfsDockerDisabled type: string default: '' -- name: cacheKey + +- name: cacheKeyBazel type: string - default: $(cacheKeyBazelFiles) + default: $(cacheKeyBazel) - name: cacheVersion type: string - default: $(cacheKeyBazel) + default: $(cacheKeyVersion) + +- name: pathDockerBind + type: string + default: $(pathDockerBind) - name: rbe displayName: "Enable RBE" @@ -72,6 +75,10 @@ parameters: type: string default: true +- name: diskspaceHack + type: boolean + default: false + - name: stepsPre type: stepList default: [] @@ -88,6 +95,10 @@ steps: fetchDepth: ${{ parameters.repoFetchDepth }} fetchTags: ${{ parameters.repoFetchTags }} +- bash: ./.azure-pipelines/cleanup.sh + displayName: "Free disk space" + condition: and(succeeded(), eq('${{ parameters.diskspaceHack }}', true)) + # Set up tmpfs directories for self-hosted agents which have a surplus of mem. # # NB: Do not add any directory that grow larger than spare memory capacity! @@ -119,7 +130,7 @@ steps: done sudo chown -R azure-pipelines:azure-pipelines $(Build.StagingDirectory)/bazel_root/ displayName: "Mount/tmpfs bazel directories" - condition: and(succeeded(), eq('${{ parameters.managedAgent }}', false)) + condition: and(succeeded(), eq('${{ parameters.managedAgent }}', false), ne('${{ parameters.tmpfsDockerDisabled }}', true)) - bash: | set -e @@ -132,30 +143,18 @@ steps: sudo chown -R vsts:vsts "${CACHE_DIRS[@]}" $(Build.StagingDirectory)/bazel_root/ echo "Created bazel cache directories: "${CACHE_DIRS[*]}"" displayName: "Create bazel directories" - condition: and(succeeded(), eq('${{ parameters.managedAgent }}', true)) + condition: and(succeeded(), eq('${{ parameters.managedAgent }}', true), eq('${{ parameters.tmpfsDockerDisabled }}', true)) # Caching -- task: Cache@2 - inputs: - key: '"${{ parameters.ciTarget }}" | "${{ parameters.cacheVersion }}" | "${{ parameters.artifactSuffix }}" | ${{ parameters.cacheKey }}' - path: $(Build.StagingDirectory)/bazel - cacheHitVar: BAZEL_CACHE_RESTORED - continueOnError: true -- script: | - set -e - sudo tar xf $(Build.StagingDirectory)/bazel/cache.tar.zst -C $(Build.StagingDirectory) --warning=no-timestamp - sudo rm -rf $(Build.StagingDirectory)/bazel/* - displayName: "Cache/restore (${{ parameters.ciTarget }})" - condition: and(not(canceled()), eq(variables.BAZEL_CACHE_RESTORED, 'true')) - template: cached.yml parameters: - key: "${{ parameters.cacheKeyDocker }}" - version: "${{ parameters.cacheKeyDockerVersion }}" - name: "${{ parameters.cacheKeyDockerName }}" - path: "${{ parameters.cacheKeyDockerPath }}" - tmpDirectory: "${{ parameters.cacheKeyDockerTmpDir }}" - tmpNoTmpfs: "${{ parameters.cacheKeyDockerNoTmpfs }}" + keyBazel: "${{ parameters.cacheKeyBazel }}" + keyDocker: "${{ parameters.cacheKeyDocker }}" + pathDockerBind: "${{ parameters.pathDockerBind }}" arch: "${{ parameters.artifactSuffix }}" + pathTemp: "${{ parameters.pathCacheTemp }}" + tmpfsDisabled: "${{ parameters.tmpfsCacheDisabled }}" + tmpfsDockerDisabled: "${{ parameters.tmpfsDockerDisabled }}" - ${{ each step in parameters.stepsPre }}: - ${{ each pair in step }}: @@ -195,9 +194,7 @@ steps: ${{ if parameters.rbe }}: GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs) ${{ parameters.bazelBuildExtraOptions }}" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs) ${{ parameters.bazelBuildExtraOptions }}" ${{ if eq(parameters.rbe, false) }}: BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" BAZEL_REMOTE_CACHE: $(LocalBuildCache) @@ -221,22 +218,9 @@ steps: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} -- script: | - set -e - CACHE_DIRS=( - ".cache" - "bazel_root/install" - "repository_cache/" - "bazel_root/base/external") - mkdir -p $(Build.StagingDirectory)/bazel/ - sudo tar cf - -C $(Build.StagingDirectory) "${CACHE_DIRS[@]}" \ - | zstd - -T0 -o $(Build.StagingDirectory)/bazel/cache.tar.zst - echo "Created tarball ($(Build.StagingDirectory)/bazel/cache.tar.zst): ${CACHE_DIRS[@]}" - displayName: "Cache/save (${{ parameters.ciTarget }})" - condition: and(not(canceled()), ne(variables.BAZEL_CACHE_RESTORED, 'true')) - - task: PublishTestResults@2 inputs: + publishRunAttachments: false testResultsFiles: "**/bazel-out/**/testlogs/**/test.xml" testRunTitle: "${{ parameters.ciTarget }}" searchFolder: $(Build.StagingDirectory)/bazel_root diff --git a/.azure-pipelines/cached.yml b/.azure-pipelines/cached.yml index d75ef8b5771e..acbf9e486491 100644 --- a/.azure-pipelines/cached.yml +++ b/.azure-pipelines/cached.yml @@ -2,25 +2,36 @@ parameters: - name: name type: string - default: envoy_build_image -- name: version - type: string - default: "" + default: $(cacheKeyName) - name: arch type: string default: "" -- name: key +- name: version + type: string + default: $(cacheKeyVersion) + +- name: keyDocker + type: string + default: $(cacheKeyDocker) +- name: keyBazel type: string - default: ".devcontainer/Dockerfile" -- name: tmpDirectory + default: $(cacheKeyBazel) + +- name: pathTemp + type: string + default: $(pathCacheTemp) + +- name: tmpfsDisabled type: string - default: /mnt/docker_cache -- name: tmpNoTmpfs + default: +- name: tmpfsDockerDisabled type: string default: -- name: path + +- name: pathDockerBind type: string - default: /mnt/docker + default: $(pathDockerBind) + - name: cacheTimeoutWorkaround type: number default: 5 @@ -30,24 +41,37 @@ parameters: steps: -- script: sudo .azure-pipelines/docker/prepare_cache.sh "${{ parameters.tmpDirectory }}" "${{ parameters.tmpNoTmpfs }}" - displayName: "Cache/prepare (${{ parameters.name }})" +- script: sudo .azure-pipelines/docker/prepare_cache.sh "${{ parameters.pathTemp }}" "${{ parameters.tmpfsDisabled }}" + displayName: "Cache/prepare" + +- task: Cache@2 + env: + VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" + displayName: "Cache (Docker)" + inputs: + key: '${{ parameters.name }} | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyDocker }}' + path: "${{ parameters.pathTemp }}/docker" + cacheHitVar: DOCKER_CACHE_RESTORED + - task: Cache@2 env: VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" - displayName: "Cache/fetch (${{ parameters.name }})" + displayName: "Cache (Bazel)" inputs: - key: '${{ parameters.name }} | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.key }}' - path: "${{ parameters.tmpDirectory }}" - cacheHitVar: CACHE_RESTORED + key: '${{ parameters.name }} | bazel | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyBazel }}' + path: "${{ parameters.pathTemp }}/bazel" + cacheHitVar: BAZEL_CACHE_RESTORED -# Prime the cache for all jobs -- script: sudo .azure-pipelines/docker/prime_cache.sh "${{ parameters.tmpDirectory }}" "${{ parameters.arch }}" +# Prime the caches for all jobs +- script: .azure-pipelines/docker/prime_cache.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.arch }}" + env: + DOCKER_RESTORED: $(DOCKER_CACHE_RESTORED) + BAZEL_RESTORED: $(BAZEL_CACHE_RESTORED) displayName: "Cache/prime (${{ parameters.name }})" # TODO(phlax): figure if there is a way to test cache without downloading it - condition: and(not(canceled()), eq(${{ parameters.prime }}, true), ne(variables.CACHE_RESTORED, 'true')) + condition: and(not(canceled()), eq(${{ parameters.prime }}, true), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) -# Load the cache for a job -- script: sudo .azure-pipelines/docker/load_cache.sh "${{ parameters.tmpDirectory }}" "${{ parameters.path }}" - displayName: "Cache/restore (${{ parameters.name }})" +# Load the caches for a job +- script: sudo .azure-pipelines/docker/load_caches.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.pathDockerBind }}" "${{ parameters.tmpfsDockerDisabled }}" + displayName: "Cache/restore" condition: and(not(canceled()), eq(${{ parameters.prime }}, false)) diff --git a/.azure-pipelines/docker/clean_docker.sh b/.azure-pipelines/docker/clean_docker.sh new file mode 100755 index 000000000000..cbad33a4ad57 --- /dev/null +++ b/.azure-pipelines/docker/clean_docker.sh @@ -0,0 +1,11 @@ +#!/bin/bash -e + +set -o pipefail + +echo "Stopping Docker ..." +systemctl stop docker + +echo "Restarting Docker with empty /var/lib/docker ..." +mv /var/lib/docker/ /var/lib/docker.old +mkdir /var/lib/docker +systemctl start docker diff --git a/.azure-pipelines/docker/create_cache.sh b/.azure-pipelines/docker/create_cache.sh new file mode 100755 index 000000000000..4079df028f4c --- /dev/null +++ b/.azure-pipelines/docker/create_cache.sh @@ -0,0 +1,22 @@ +#!/bin/bash -e + +set -o pipefail + +CACHE_TARBALL="${1}" +shift + +echo "Exporting ${*} -> ${CACHE_TARBALL}" + +CACHE_PATH="$(dirname "$CACHE_TARBALL")" +mkdir -p "$CACHE_PATH" + +CACHE_ARGS=() +for path in "$@"; do + total="$(du -sh "$path" | cut -f1)" + echo "Adding cache dir (${path}): ${total}" + CACHE_ARGS+=(-C "$path" .) +done + +tar cf - "${CACHE_ARGS[@]}" | zstd - -q -T0 -o "$CACHE_TARBALL" +echo "Cache tarball created: ${CACHE_TARBALL}" +ls -lh "$CACHE_TARBALL" diff --git a/.azure-pipelines/docker/load_cache.sh b/.azure-pipelines/docker/load_caches.sh similarity index 53% rename from .azure-pipelines/docker/load_cache.sh rename to .azure-pipelines/docker/load_caches.sh index 78c6cd8e5d99..5170249f2f6d 100755 --- a/.azure-pipelines/docker/load_cache.sh +++ b/.azure-pipelines/docker/load_caches.sh @@ -1,16 +1,22 @@ #!/bin/bash -e -DOCKER_CACHE_PATH="$1" -DOCKER_BIND_PATH="$2" +ENVOY_DOCKER_BUILD_DIR="$1" +CACHE_PATH="$2" +DOCKER_BIND_PATH="$3" +DOCKER_NO_TMPFS="$4" -if [[ -z "$DOCKER_CACHE_PATH" ]]; then - echo "load_docker_cache called without path arg" >&2 + +if [[ -z "$CACHE_PATH" ]]; then + echo "load_caches called without path arg" >&2 exit 1 fi - +DOCKER_CACHE_PATH="${CACHE_PATH}/docker" DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" +BAZEL_CACHE_PATH="${CACHE_PATH}/bazel" +BAZEL_CACHE_TARBALL="${BAZEL_CACHE_PATH}/bazel.tar.zst" + echo "Stopping Docker daemon ..." systemctl stop docker docker.socket @@ -22,22 +28,22 @@ if id -u vsts &> /dev/null && [[ -n "$DOCKER_BIND_PATH" ]]; then echo "Binding docker directory ${DOCKER_BIND_PATH} -> /var/lib/docker ..." mkdir -p "$DOCKER_BIND_PATH" mount -o bind "$DOCKER_BIND_PATH" /var/lib/docker -elif ! id -u vsts &> /dev/null; then +elif ! id -u vsts &> /dev/null && [[ -z "$DOCKER_NO_TMPFS" ]]; then echo "Mounting tmpfs directory -> /var/lib/docker ..." # Use a ramdisk to load docker (avoids Docker slow start on big disk) mount -t tmpfs none /var/lib/docker else - # If we are on a managed host but the bind path is not set then we need to remove + # If we are on a managed/resource-constrained host but the bind path is not set then we need to remove # the old /var/lib/docker to free some space (maybe) DOCKER_REMOVE_EXISTING=1 fi if [[ -e "${DOCKER_CACHE_TARBALL}" ]]; then echo "Extracting docker cache ${DOCKER_CACHE_TARBALL} -> /var/lib/docker ..." - zstd --stdout -d "$DOCKER_CACHE_TARBALL" | tar -xf - -C /var/lib/docker + zstd --stdout -d "$DOCKER_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C /var/lib/docker touch /tmp/DOCKER_CACHE_RESTORED else - echo "No cache to restore, starting Docker with no data" + echo "No Docker cache to restore, starting Docker with no data" fi echo "Starting Docker daemon ..." @@ -51,9 +57,27 @@ else rm -rf "${DOCKER_CACHE_PATH}" fi docker images -df -h + +mkdir -p "${ENVOY_DOCKER_BUILD_DIR}" + +if [[ -e "${BAZEL_CACHE_TARBALL}" ]]; then + echo "Extracting bazel cache ${BAZEL_CACHE_TARBALL} -> ${ENVOY_DOCKER_BUILD_DIR} ..." + zstd --stdout -d "$BAZEL_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C "${ENVOY_DOCKER_BUILD_DIR}" +else + echo "No bazel cache to restore, starting bazel with no data" +fi + +if mountpoint -q "${CACHE_PATH}"; then + echo "Unmount cache tmp ${CACHE_PATH} ..." + umount "${CACHE_PATH}" +else + echo "Remove cache tmp ${CACHE_PATH} ..." + rm -rf "${CACHE_PATH}" +fi # this takes time but may be desirable in some situations if [[ -n "$DOCKER_REMOVE_EXISTING" ]]; then rm -rf /var/lib/docker.old fi + +df -h diff --git a/.azure-pipelines/docker/prepare_cache.sh b/.azure-pipelines/docker/prepare_cache.sh index fe417d5f5e41..792b9f8f5684 100755 --- a/.azure-pipelines/docker/prepare_cache.sh +++ b/.azure-pipelines/docker/prepare_cache.sh @@ -3,7 +3,7 @@ DOCKER_CACHE_PATH="$1" NO_MOUNT_TMPFS="${2:-}" DOCKER_CACHE_OWNERSHIP="vsts:vsts" - +TMPFS_SIZE=5G if [[ -z "$DOCKER_CACHE_PATH" ]]; then echo "prepare_docker_cache called without path arg" >&2 @@ -18,6 +18,8 @@ echo "Creating cache directory (${DOCKER_CACHE_PATH}) ..." mkdir -p "${DOCKER_CACHE_PATH}" if [[ -z "$NO_MOUNT_TMPFS" ]]; then echo "Mount tmpfs directory: ${DOCKER_CACHE_PATH}" - mount -t tmpfs none "$DOCKER_CACHE_PATH" + mount -o size="$TMPFS_SIZE" -t tmpfs none "$DOCKER_CACHE_PATH" fi +mkdir -p "${DOCKER_CACHE_PATH}/docker" +mkdir -p "${DOCKER_CACHE_PATH}/bazel" chown -R "$DOCKER_CACHE_OWNERSHIP" "${DOCKER_CACHE_PATH}" diff --git a/.azure-pipelines/docker/prime_cache.sh b/.azure-pipelines/docker/prime_cache.sh index d5bef3388a44..8f8a0af6190c 100755 --- a/.azure-pipelines/docker/prime_cache.sh +++ b/.azure-pipelines/docker/prime_cache.sh @@ -1,40 +1,65 @@ #!/bin/bash -e -DOCKER_CACHE_PATH="$1" -DOCKER_CACHE_ARCH="$2" +ENVOY_DOCKER_BUILD_DIR="$1" +CACHE_PATH="$2" +CACHE_ARCH="$3" -if [[ -z "$DOCKER_CACHE_PATH" ]]; then +echo "Docker restored: $DOCKER_RESTORED" +echo "Bazel restored: $BAZEL_RESTORED" + +if [[ -z "$CACHE_PATH" ]]; then echo "prime_docker_cache called without path arg" >&2 exit 1 fi -if [[ "$DOCKER_CACHE_ARCH" == ".arm64" ]]; then - DOCKER_CACHE_ARCH=linux/arm64 +if [[ "$CACHE_ARCH" == ".arm64" ]]; then + CACHE_ARCH=linux/arm64 else - DOCKER_CACHE_ARCH=linux/amd64 + CACHE_ARCH=linux/amd64 fi -DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" - -echo "Stopping Docker ..." -systemctl stop docker - -echo "Restarting Docker with empty /var/lib/docker ..." -mv /var/lib/docker/ /var/lib/docker.old -mkdir /var/lib/docker -systemctl start docker +DOCKER_CACHE_TARBALL="${CACHE_PATH}/docker/docker.tar.zst" +BAZEL_CACHE_TARBALL="${CACHE_PATH}/bazel/bazel.tar.zst" +BAZEL_PATH=/tmp/envoy-docker-build -BUILD_IMAGE=$(head -n1 .devcontainer/Dockerfile | cut -d: -f2) +echo +echo "================ Load caches ===================" +if [[ "$DOCKER_RESTORED" == "true" ]] || [[ "$BAZEL_RESTORED" == "true" ]]; then + sudo ./.azure-pipelines/docker/load_caches.sh "$ENVOY_DOCKER_BUILD_DIR" "$CACHE_PATH" "" true +else + sudo ./.azure-pipelines/docker/clean_docker.sh + echo "No caches to restore" +fi +echo "===================================================" +echo -echo "Pulling build image for ${DOCKER_CACHE_ARCH} (${BUILD_IMAGE}) ..." -docker pull -q --platform "${DOCKER_CACHE_ARCH}" "envoyproxy/envoy-build-ubuntu:${BUILD_IMAGE}" +echo +echo "================ Bazel fetch ======================" +# Fetch bazel dependencies +if [[ "$BAZEL_RESTORED" != "true" ]]; then + echo "Fetching bazel" + ./ci/run_envoy_docker.sh './ci/do_ci.sh fetch' +else + echo "Not fetching bazel as it was restored" +fi +echo "===================================================" +echo -echo "Stopping docker" -systemctl stop docker +docker images +df -h -echo "Exporting /var/lib/docker -> ${DOCKER_CACHE_PATH}" -mkdir -p "$DOCKER_CACHE_PATH" -tar cf - -C /var/lib/docker . | zstd - -T0 -o "$DOCKER_CACHE_TARBALL" +echo +echo "================ Save caches ======================" +# Save the caches -> tarballs +if [[ "$DOCKER_RESTORED" != "true" ]]; then + echo "Stopping docker" + sudo systemctl stop docker docker.socket + sudo ./.azure-pipelines/docker/create_cache.sh "${DOCKER_CACHE_TARBALL}" /var/lib/docker +fi -echo "Docker cache tarball created: ${DOCKER_CACHE_TARBALL}" -ls -lh "$DOCKER_CACHE_TARBALL" +if [[ "$BAZEL_RESTORED" != "true" ]]; then + sudo ./.azure-pipelines/docker/create_cache.sh "${BAZEL_CACHE_TARBALL}" "${BAZEL_PATH}" +fi +sudo chmod o+r -R "${CACHE_PATH}" +echo "===================================================" +echo diff --git a/.azure-pipelines/docker/save_cache.sh b/.azure-pipelines/docker/save_cache.sh index 85f912cbad2d..462177b3f03f 100755 --- a/.azure-pipelines/docker/save_cache.sh +++ b/.azure-pipelines/docker/save_cache.sh @@ -1,5 +1,7 @@ #!/bin/bash -e +set -o pipefail + DOCKER_CACHE_PATH="$1" NO_MOUNT_TMPFS="${2:-}" diff --git a/.azure-pipelines/env.yml b/.azure-pipelines/env.yml index ed70d498c8cc..ca6e5ecb97e4 100644 --- a/.azure-pipelines/env.yml +++ b/.azure-pipelines/env.yml @@ -42,19 +42,16 @@ jobs: steps: - template: cached.yml parameters: - version: "$(cacheKeyBuildImage)" prime: true - job: cache_arm dependsOn: [] displayName: Cache (arm64) - pool: - vmImage: $(agentUbuntu) + pool: envoy-arm-small steps: - template: cached.yml parameters: prime: true arch: .arm64 - version: "$(cacheKeyBuildImage)" - job: repo dependsOn: [] diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index fdb87e4631f1..14406ecf7e96 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -44,14 +44,25 @@ variables: ## Variable settings # Caches (tip: append a version suffix while testing caches) -- name: cacheKeyBuildImage - value: v0 -- name: cacheKeyDockerBuild +- name: cacheKeyName + value: envoy +- name: cacheKeyVersion value: v0 - name: cacheKeyBazel + value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' +- name: cacheKeyDocker + value: ".devcontainer/Dockerfile" +# Docker build uses separate docker cache +- name: cacheKeyDockerBuild + # VERSION.txt is included to refresh Docker images for release + value: '"publish_docker" | ci/Dockerfile-envoy | VERSION.txt' +- name: cacheKeyDockerBuildVersion value: v0 -- name: cacheKeyBazelFiles - value: './WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' + +- name: pathCacheTemp + value: /mnt/cache +- name: pathDockerBind + value: /mnt/docker - name: authGithubSSHKeyPublic value: "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=" diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index 54cb0c899d33..05f8e8afa763 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -99,7 +99,8 @@ jobs: condition: | and(not(canceled()), eq(${{ parameters.runChecks }}, 'true')) - timeoutInMinutes: 300 + # TODO(phlax): Make this configurable and ratchet down! + timeoutInMinutes: 240 pool: "envoy-x64-large" strategy: maxParallel: 2 diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 6e7c82e577d7..493914f9ae9c 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -25,11 +25,18 @@ parameters: type: string default: "" +# Timeout/s +- name: timeoutPrechecks + type: number + # Building the rst from protos can take a while even with RBE if there is + # a lot of change - eg protobuf changed, or a primitve proto changed. + default: 40 + jobs: - job: prechecks displayName: Precheck - timeoutInMinutes: 30 + timeoutInMinutes: ${{ parameters.timeoutPrechecks }} pool: vmImage: $(agentUbuntu) variables: @@ -114,9 +121,7 @@ jobs: env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs)" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') @@ -146,9 +151,7 @@ jobs: env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs)" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} condition: eq(variables['CI_TARGET'], 'docs') diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index d80c1f505727..7827eae79ff2 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -107,16 +107,14 @@ jobs: - template: ../bazel.yml parameters: ciTarget: docker-upload - # cacheVersion: $(cacheKeyBazel) publishEnvoy: false publishTestResults: false - # VERSION.txt is included to refresh Docker images for release - cacheKeyDocker: "ci/Dockerfile-envoy | VERSION.txt| $(cacheKeyBazelFiles)" - cacheKeyDockerName: publish_docker - cacheKeyDockerTmpDir: /var/azpcache - cacheKeyDockerNoTmpfs: true - cacheKeyDockerPath: "" - cacheKeyDockerVersion: "$(cacheKeyDockerBuild)" + pathDockerBind: "" + cacheKeyDocker: "$(cacheKeyDockerBuild)" + cacheKeyVersion: "$(cacheKeyDockerBuildVersion)" + pathCacheTemp: /var/azpcache + tmpfsCacheDisabled: true + diskspaceHack: true env: GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} stepsPre: @@ -152,9 +150,11 @@ jobs: DOCKER_BUILD_TIMEOUT: ${{ parameters.timeoutDockerBuild }} stepsPost: - script: | - sudo .azure-pipelines/docker/save_cache.sh /var/azpcache true + set -e + sudo .azure-pipelines/docker/save_cache.sh /var/azpcache/docker true sudo rm -rf /var/lib/docker displayName: "Cache/save (publish_docker)" + condition: and(succeeded(), ne(variables.DOCKER_CACHE_RESTORED, 'true')) - job: package_x64 displayName: Linux debs (x64) @@ -175,6 +175,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: bazel.distribution + tmpfsCacheDisabled: true stepsPre: - template: ../gpg.yml parameters: @@ -195,7 +196,7 @@ jobs: and(not(canceled()), eq(${{ parameters.runPackaging }}, 'true')) timeoutInMinutes: 120 - pool: "envoy-arm-large" + pool: "envoy-arm-small" steps: - task: DownloadBuildArtifacts@0 inputs: @@ -211,6 +212,7 @@ jobs: rbe: false artifactSuffix: ".arm64" bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" + tmpfsDockerDisabled: true stepsPre: - template: ../gpg.yml parameters: @@ -252,9 +254,7 @@ jobs: env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs)" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} @@ -270,9 +270,7 @@ jobs: env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --jobs=$(RbeJobs)" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs-publish-latest' diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index 67898770c463..eeffccda92eb 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -29,8 +29,7 @@ jobs: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_DOCKER_IN_DOCKER: 1 ENVOY_RBE: 1 - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --jobs=$(RbeJobs)" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} displayName: "Verify packages" @@ -38,7 +37,7 @@ jobs: displayName: Debs (arm64) condition: and(not(canceled()), succeeded(), ne(stageDependencies.env.repo.outputs['changed.mobileOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.docsOnly'], 'true'), ne(stageDependencies.env.repo.outputs['changed.examplesOnly'], 'true')) timeoutInMinutes: 120 - pool: "envoy-arm-large" + pool: "envoy-arm-small" steps: - task: DownloadBuildArtifacts@0 inputs: @@ -53,10 +52,13 @@ jobs: AZP_BRANCH: $(Build.SourceBranch) ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_DOCKER_IN_DOCKER: 1 - ENVOY_RBE: 1 - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_REMOTE_CACHE: $(LocalBuildCache) + BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} + ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" + ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" displayName: "Verify packages" - job: verified diff --git a/.azure-pipelines/stage/windows.yml b/.azure-pipelines/stage/windows.yml index a59e01d024d3..83ab4774765e 100644 --- a/.azure-pipelines/stage/windows.yml +++ b/.azure-pipelines/stage/windows.yml @@ -32,9 +32,7 @@ jobs: CI_TARGET: "windows" ENVOY_DOCKER_BUILD_DIR: "$(Build.StagingDirectory)" ENVOY_RBE: "true" - BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=remote-msvc-cl --jobs=$(RbeJobs) --flaky_test_attempts=2" - BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com - BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + BAZEL_BUILD_EXTRA_OPTIONS: "--config=remote-ci --config=rbe-google --config=remote-msvc-cl --jobs=$(RbeJobs) --flaky_test_attempts=2" GCP_SERVICE_ACCOUNT_KEY: ${{ parameters.authGCP }} - task: PublishTestResults@2 diff --git a/.azure-pipelines/stages.yml b/.azure-pipelines/stages.yml index 39ca4fc3a8f3..40ac88137447 100644 --- a/.azure-pipelines/stages.yml +++ b/.azure-pipelines/stages.yml @@ -57,6 +57,7 @@ stages: jobs: - template: env.yml + - stage: prechecks displayName: Prechecks dependsOn: ["env"] @@ -106,6 +107,7 @@ stages: jobs: - template: stage/checks.yml parameters: + cacheTestResults: ${{ parameters.cacheTestResults }} concurrencyChecks: ${{ parameters.concurrencyChecks }} authGCP: $(GcpServiceAccountKey) bucketGCP: $(GcsArtifactBucket) diff --git a/.bazelrc b/.bazelrc index 17a7fa0b9b4e..cf298635aa20 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,9 +10,11 @@ # Startup options cannot be selected via config. startup --host_jvm_args=-Xmx3g +fetch --color=yes run --color=yes build --color=yes +build --jobs=HOST_CPUS-1 build --workspace_status_command="bash bazel/get_workspace_status" build --incompatible_strict_action_env build --java_runtime_version=remotejdk_11 @@ -22,12 +24,19 @@ build --platform_mappings=bazel/platform_mappings build --copt=-DABSL_MIN_LOG_LEVEL=4 build --define envoy_mobile_listener=enabled build --experimental_repository_downloader_retries=2 +build --enable_platform_specific_config -# Pass PATH, CC, CXX and LLVM_CONFIG variables from the environment. +# Pass CC, CXX and LLVM_CONFIG variables from the environment. +# We assume they have stable values, so this won't cause action cache misses. build --action_env=CC --host_action_env=CC build --action_env=CXX --host_action_env=CXX build --action_env=LLVM_CONFIG --host_action_env=LLVM_CONFIG -build --action_env=PATH --host_action_env=PATH +# Do not pass through PATH however. +# It tends to have machine-specific values, such as dynamically created temp folders. +# This would make it impossible to share remote action cache hits among machines. +# build --action_env=PATH --host_action_env=PATH +# To make our own CI green, we do need that flag on Windows though. +build:windows --action_env=PATH --host_action_env=PATH # Allow stamped caches to bust when local filesystem changes. # Requires setting `BAZEL_VOLATILE_DIRTY` in the env. @@ -37,7 +46,6 @@ build --action_env=BAZEL_VOLATILE_DIRTY --host_action_env=BAZEL_VOLATILE_DIRTY # Requires setting `BAZEL_FAKE_SCM_REVISION` in the env. build --action_env=BAZEL_FAKE_SCM_REVISION --host_action_env=BAZEL_FAKE_SCM_REVISION -build --enable_platform_specific_config build --test_summary=terse # TODO(keith): Remove once these 2 are the default @@ -69,8 +77,6 @@ build --@com_googlesource_googleurl//build_config:system_icu=0 # Common flags for sanitizers build:sanitizer --define tcmalloc=disabled build:sanitizer --linkopt -ldl -build:sanitizer --build_tag_filters=-no_san -build:sanitizer --test_tag_filters=-no_san # Common flags for Clang build:clang --action_env=BAZEL_COMPILER=clang @@ -90,6 +96,8 @@ build:asan --config=sanitizer # ASAN install its signal handler, disable ours so the stacktrace will be printed by ASAN build:asan --define signal_trace=disabled build:asan --define ENVOY_CONFIG_ASAN=1 +build:asan --build_tag_filters=-no_san +build:asan --test_tag_filters=-no_san build:asan --copt -fsanitize=address,undefined build:asan --linkopt -fsanitize=address,undefined # vptr and function sanitizer are enabled in clang-asan if it is set up via bazel/setup_clang.sh. @@ -143,12 +151,15 @@ build:clang-tsan --copt -DEVENT__DISABLE_DEBUG_MODE # https://github.com/abseil/abseil-cpp/issues/760 # https://github.com/google/sanitizers/issues/953 build:clang-tsan --test_env="TSAN_OPTIONS=report_atomic_races=0" +build:clang-tsan --test_timeout=120,600,1500,4800 # Clang MSAN - this is the base config for remote-msan and docker-msan. To run this config without # our build image, follow https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo # with libc++ instruction and provide corresponding `--copt` and `--linkopt` as well. build:clang-msan --action_env=ENVOY_MSAN=1 build:clang-msan --config=sanitizer +build:clang-msan --build_tag_filters=-no_san +build:clang-msan --test_tag_filters=-no_san build:clang-msan --define ENVOY_CONFIG_MSAN=1 build:clang-msan --copt -fsanitize=memory build:clang-msan --linkopt -fsanitize=memory @@ -182,6 +193,7 @@ build --test_env=HEAPCHECK=normal --test_env=PPROF_PATH # Coverage options coverage --config=coverage coverage --build_tests_only + build:coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 build:coverage --action_env=GCOV=llvm-profdata build:coverage --copt=-DNDEBUG @@ -190,20 +202,22 @@ build:coverage --test_timeout=390,750,1500,5700 build:coverage --define=dynamic_link_tests=true build:coverage --define=ENVOY_CONFIG_COVERAGE=1 build:coverage --cxxopt="-DENVOY_CONFIG_COVERAGE=1" -build:coverage --coverage_support=@envoy//bazel/coverage:coverage_support -build:coverage --test_env=CC_CODE_COVERAGE_SCRIPT=bazel/coverage/collect_cc_coverage.sh build:coverage --test_env=HEAPCHECK= build:coverage --combined_report=lcov build:coverage --strategy=TestRunner=sandboxed,local build:coverage --strategy=CoverageReport=sandboxed,local build:coverage --experimental_use_llvm_covmap +build:coverage --experimental_generate_llvm_lcov build:coverage --collect_code_coverage -build:coverage --test_tag_filters=-nocoverage -build:coverage --instrumentation_filter="//source(?!/common/quic/platform)[/:],//envoy[/:],//contrib(?!/.*/test)[/:]" +build:coverage --instrumentation_filter="^//source(?!/common/quic/platform)[/:],^//envoy[/:],^//contrib(?!/.*/test)[/:]" +build:coverage --remote_download_toplevel + build:test-coverage --test_arg="-l trace" build:test-coverage --test_arg="--log-path /dev/null" +build:test-coverage --test_tag_filters=-nocoverage,-fuzz_target build:fuzz-coverage --config=plain-fuzzer build:fuzz-coverage --run_under=@envoy//bazel/coverage:fuzz_coverage_wrapper.sh +build:fuzz-coverage --test_tag_filters=-nocoverage # Remote execution: https://docs.bazel.build/versions/master/remote-execution.html build:rbe-toolchain --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 @@ -263,10 +277,6 @@ build:remote --spawn_strategy=remote,sandboxed,local build:remote --strategy=Javac=remote,sandboxed,local build:remote --strategy=Closure=remote,sandboxed,local build:remote --strategy=Genrule=remote,sandboxed,local -build:remote --remote_timeout=7200 -build:remote --google_default_credentials=true -build:remote --remote_download_toplevel -build:remote --nobuild_runfile_links # Windows bazel does not allow sandboxed as a spawn strategy build:remote-windows --spawn_strategy=remote,local @@ -306,6 +316,25 @@ build:remote-clang-cl --config=remote-windows build:remote-clang-cl --config=clang-cl build:remote-clang-cl --config=rbe-toolchain-clang-cl +## Compile-time-options testing +# Right now, none of the available compile-time options conflict with each other. If this +# changes, this build type may need to be broken up. +build:compile-time-options --define=admin_html=disabled +build:compile-time-options --define=signal_trace=disabled +build:compile-time-options --define=hot_restart=disabled +build:compile-time-options --define=google_grpc=disabled +build:compile-time-options --define=boringssl=fips +build:compile-time-options --define=log_debug_assert_in_release=enabled +build:compile-time-options --define=path_normalization_by_default=true +build:compile-time-options --define=deprecated_features=disabled +build:compile-time-options --define=tcmalloc=gperftools +build:compile-time-options --define=zlib=ng +build:compile-time-options --define=uhv=enabled +build:compile-time-options --config=libc++20 +build:compile-time-options --test_env=ENVOY_HAS_EXTRA_EXTENSIONS=true +build:compile-time-options --@envoy//bazel:http3=False +build:compile-time-options --@envoy//source/extensions/filters/http/kill_request:enabled + # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:41c5a05d708972d703661b702a63ef5060125c33 @@ -339,16 +368,13 @@ build:docker-tsan --config=rbe-toolchain-clang-libc++ build:docker-tsan --config=rbe-toolchain-tsan # CI configurations -build:remote-ci --remote_cache=grpcs://remotebuildexecution.googleapis.com -build:remote-ci --remote_executor=grpcs://remotebuildexecution.googleapis.com build:remote-ci --config=ci +build:remote-ci --remote_download_minimal + # Note this config is used by mobile CI also. build:ci --noshow_progress build:ci --noshow_loading_progress - -# Build Event Service -build:google-bes --bes_backend=grpcs://buildeventservice.googleapis.com -build:google-bes --bes_results_url=https://source.cloud.google.com/results/invocations/ +build:ci --test_output=errors # Fuzz builds @@ -439,6 +465,50 @@ build:windows --features=fully_static_link build:windows --features=static_link_msvcrt build:windows --dynamic_mode=off +# RBE (Google) +build:rbe-google --google_default_credentials=true +build:rbe-google --remote_cache=grpcs://remotebuildexecution.googleapis.com +build:rbe-google --remote_executor=grpcs://remotebuildexecution.googleapis.com +build:rbe-google --remote_timeout=7200 +build:rbe-google --remote_instance_name=projects/envoy-ci/instances/default_instance + +build:rbe-google-bes --bes_backend=grpcs://buildeventservice.googleapis.com +build:rbe-google-bes --bes_results_url=https://source.cloud.google.com/results/invocations/ + +# RBE (Engflow mobile) +build:rbe-engflow --google_default_credentials=false +build:rbe-engflow --remote_cache=grpcs://envoy.cluster.engflow.com +build:rbe-engflow --remote_executor=grpcs://envoy.cluster.engflow.com +build:rbe-engflow --bes_backend=grpcs://envoy.cluster.engflow.com/ +build:rbe-engflow --bes_results_url=https://envoy.cluster.engflow.com/invocation/ +build:rbe-engflow --experimental_credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:rbe-engflow --grpc_keepalive_time=30s +build:rbe-engflow --remote_timeout=3600s +build:rbe-engflow --bes_timeout=3600s +build:rbe-engflow --bes_upload_mode=fully_async + +############################################################################# +# debug: Various Bazel debugging flags +############################################################################# +# debug/bazel +common:debug-bazel --announce_rc +common:debug-bazel -s +# debug/sandbox +common:debug-sandbox --verbose_failures +common:debug-sandbox --sandbox_debug +# debug/coverage +common:debug-coverage --action_env=VERBOSE_COVERAGE=true +common:debug-coverage --test_env=VERBOSE_COVERAGE=true +common:debug-coverage --test_env=DISPLAY_LCOV_CMD=true +common:debug-coverage --config=debug-tests +# debug/tests +common:debug-tests --test_output=all +# debug/everything +common:debug --config=debug-bazel +common:debug --config=debug-sandbox +common:debug --config=debug-coverage +common:debug --config=debug-tests + try-import %workspace%/clang.bazelrc try-import %workspace%/user.bazelrc try-import %workspace%/local_tsan.bazelrc diff --git a/.bazelversion b/.bazelversion index dfda3e0b4f01..dc0208aba8e4 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.1.0 +6.3.1 diff --git a/.github/actions/diskspace/action.yml b/.github/actions/diskspace/action.yml new file mode 100644 index 000000000000..8df3a6f89957 --- /dev/null +++ b/.github/actions/diskspace/action.yml @@ -0,0 +1,27 @@ +inputs: + to_remove: + type: string + default: | + /opt/hostedtoolcache + /usr/local/lib/android + /usr/local/.ghcup + +runs: + using: composite + steps: + - id: remove_cruft + name: Cruft removal + run: | + echo "Disk space before cruft removal" + df -h + + TO_REMOVE=(${{ inputs.to_remove }}) + + for removal in "${TO_REMOVE[@]}"; do + echo "Removing: ${removal} ..." + sudo rm -rf "$removal" + done + + echo "Disk after before cruft removal" + df -h + shell: bash diff --git a/.github/actions/pr_notifier/requirements.txt b/.github/actions/pr_notifier/requirements.txt index f9dfcc84ad24..2bf2905a92a1 100644 --- a/.github/actions/pr_notifier/requirements.txt +++ b/.github/actions/pr_notifier/requirements.txt @@ -138,30 +138,30 @@ charset-normalizer==3.1.0 \ --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab # via requests -cryptography==41.0.2 \ - --hash=sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711 \ - --hash=sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7 \ - --hash=sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd \ - --hash=sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e \ - --hash=sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58 \ - --hash=sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0 \ - --hash=sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d \ - --hash=sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83 \ - --hash=sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831 \ - --hash=sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766 \ - --hash=sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b \ - --hash=sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c \ - --hash=sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182 \ - --hash=sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f \ - --hash=sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa \ - --hash=sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4 \ - --hash=sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a \ - --hash=sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2 \ - --hash=sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76 \ - --hash=sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5 \ - --hash=sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee \ - --hash=sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f \ - --hash=sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14 +cryptography==41.0.3 \ + --hash=sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306 \ + --hash=sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84 \ + --hash=sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47 \ + --hash=sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d \ + --hash=sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116 \ + --hash=sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207 \ + --hash=sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81 \ + --hash=sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087 \ + --hash=sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd \ + --hash=sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507 \ + --hash=sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858 \ + --hash=sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae \ + --hash=sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34 \ + --hash=sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906 \ + --hash=sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd \ + --hash=sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922 \ + --hash=sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7 \ + --hash=sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4 \ + --hash=sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574 \ + --hash=sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1 \ + --hash=sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c \ + --hash=sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e \ + --hash=sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de # via pyjwt deprecated==1.2.13 \ --hash=sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d \ @@ -175,9 +175,9 @@ pycparser==2.20 \ --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 # via cffi -pygithub==1.59.0 \ - --hash=sha256:126bdbae72087d8d038b113aab6b059b4553cb59348e3024bb1a1cae406ace9e \ - --hash=sha256:6e05ff49bac3caa7d1d6177a10c6e55a3e20c85b92424cc198571fd0cf786690 +pygithub==1.59.1 \ + --hash=sha256:3d87a822e6c868142f0c2c4bf16cce4696b5a7a4d142a7bd160e1bdf75bc54a9 \ + --hash=sha256:c44e3a121c15bf9d3a5cc98d94c9a047a5132a9b01d22264627f58ade9ddc217 # via -r requirements.in pyjwt[crypto]==2.4.0 \ --hash=sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf \ diff --git a/.github/workflows/POLICY.md b/.github/workflows/POLICY.md index 86d775493dc9..c52488cd22ef 100644 --- a/.github/workflows/POLICY.md +++ b/.github/workflows/POLICY.md @@ -40,7 +40,7 @@ Do not allow any bots or app users to do so, unless this is specifically require For example, you could add a `job` condition to prevent any bots from triggering the workflow: ```yaml - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index c036a726ef33..11a61854caf7 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -115,20 +115,7 @@ jobs: run: git config --global --add safe.directory /__w/envoy/envoy - if: ${{ inputs.diskspace_hack }} - name: Cruft removal - run: | - echo "Disk space before cruft removal" - df -h - - TO_REMOVE=( - /opt/hostedtoolcache - /usr/local/lib/android - /usr/local/.ghcup) - - for removal in "${TO_REMOVE[@]}"; do - echo "Removing: ${removal} ..." - sudo rm -rf "$removal" - done + uses: ./.github/actions/diskspace - run: | echo "disk space at beginning of build:" df -h diff --git a/.github/workflows/_env.yml b/.github/workflows/_env.yml index c31814c893ca..d7263d96d99e 100644 --- a/.github/workflows/_env.yml +++ b/.github/workflows/_env.yml @@ -42,6 +42,8 @@ on: default: outputs: + debug: + value: false agent_ubuntu: value: ubuntu-22.04 build_image_ubuntu: diff --git a/.github/workflows/check-deps.yml b/.github/workflows/check-deps.yml index 984a52a57b0f..9bb8055cb624 100644 --- a/.github/workflows/check-deps.yml +++ b/.github/workflows/check-deps.yml @@ -1,16 +1,17 @@ name: Check dependencies +permissions: + contents: read + on: schedule: - - cron: '0 8 * * *' + - cron: '0 8 * * *' workflow_dispatch: -permissions: read-all - jobs: build: runs-on: ubuntu-22.04 - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index d1e4339f34ad..8742e8b75099 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -9,7 +9,7 @@ permissions: jobs: retest: - if: | + if: >- ${{ github.event.issue.pull_request && github.repository == 'envoyproxy/envoy' diff --git a/.github/workflows/depsreview.yml b/.github/workflows/depsreview.yml index e53564b0bb9d..6d2794ce0d75 100644 --- a/.github/workflows/depsreview.yml +++ b/.github/workflows/depsreview.yml @@ -13,4 +13,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v3 - name: 'Dependency Review' - uses: actions/dependency-review-action@1360a344ccb0ab6e9475edef90ad2f46bf8003b1 + uses: actions/dependency-review-action@7d90b4f05fea31dde1c4a1fb3fa787e197ea93ab diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 2ec5bd5969bd..8bc5d511b01e 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -1,5 +1,8 @@ name: Publish & verify +permissions: + contents: read + on: # This runs untrusted code, do not expose secrets in the verify job workflow_dispatch: @@ -19,12 +22,9 @@ concurrency: }}-${{ github.workflow }} cancel-in-progress: true -permissions: - contents: read - jobs: env: - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (!contains(github.actor, '[bot]') diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index 2764e936d257..a534c5e8be06 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -13,7 +13,7 @@ concurrency: jobs: sync: runs-on: ubuntu-22.04 - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.push diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index 1f4f72faae9a..2af11908e721 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -1,5 +1,8 @@ name: android_build +permissions: + contents: read + on: push: branches: @@ -14,11 +17,16 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write androidbuild: if: ${{ needs.env.outputs.mobile_android_build == 'true' }} needs: env + permissions: + contents: read + packages: read name: android_build runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 90 @@ -35,8 +43,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-clang") \ + cd mobile + ./bazelw build \ + --config=mobile-remote-ci \ --fat_apk_cpu=x86_64 \ --linkopt=-fuse-ld=lld \ //:android_dist @@ -46,6 +55,9 @@ jobs: needs: - env - androidbuild + permissions: + contents: read + packages: read name: java_helloworld runs-on: macos-12 timeout-minutes: 50 @@ -57,7 +69,9 @@ jobs: java-package: jdk architecture: x64 distribution: zulu - - run: cd mobile && ./ci/mac_ci_setup.sh --android + - run: | + cd mobile + ./ci/mac_ci_setup.sh --android name: 'Install dependencies' - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start emulator' @@ -72,12 +86,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + cd mobile + ./bazelw build \ + --config=mobile-remote-ci-macos \ --fat_apk_cpu=x86_64 \ //examples/java/hello_world:hello_envoy - adb install -r --no-incremental bazel-bin/examples/java/hello_world/hello_envoy.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoy/.MainActivity + adb install -r --no-incremental bazel-bin/examples/java/hello_world/hello_envoy.apk + adb shell am start -n io.envoyproxy.envoymobile.helloenvoy/.MainActivity - name: 'Check connectivity' run: | timeout 30 adb logcat -e "received headers with status 301" -m 1 || { @@ -87,11 +102,15 @@ jobs: } exit 1 } + kotlinhelloworld: if: ${{ needs.env.outputs.mobile_android_build == 'true' }} needs: - env - androidbuild + permissions: + contents: read + packages: read name: kotlin_helloworld runs-on: macos-12 timeout-minutes: 50 @@ -104,7 +123,9 @@ jobs: architecture: x64 distribution: zulu - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh --android + run: | + cd mobile + ./ci/mac_ci_setup.sh --android - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start emulator' with: @@ -118,12 +139,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + cd mobile + ./bazelw build \ + --config=mobile-remote-ci-macos \ --fat_apk_cpu=x86_64 \ //examples/kotlin/hello_world:hello_envoy_kt - adb install -r --no-incremental bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity + adb install -r --no-incremental bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk + adb shell am start -n io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity - name: 'Check connectivity' run: | timeout 30 adb logcat -e "received headers with status 200" -m 1 || { @@ -139,6 +161,9 @@ jobs: needs: - env - androidbuild + permissions: + contents: read + packages: read name: kotlin_baseline_app runs-on: macos-12 timeout-minutes: 50 @@ -151,7 +176,9 @@ jobs: architecture: x64 distribution: zulu - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh --android + run: | + cd mobile + ./ci/mac_ci_setup.sh --android - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start emulator' with: @@ -165,12 +192,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + cd mobile + ./bazelw build \ + --config=mobile-remote-ci-macos \ --fat_apk_cpu=x86_64 \ //test/kotlin/apps/baseline:hello_envoy_kt - adb install -r --no-incremental bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity + adb install -r --no-incremental bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk + adb shell am start -n io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity - name: 'Check connectivity' run: | timeout 30 adb logcat -e "received headers with status 301" -m 1 || { @@ -180,11 +208,15 @@ jobs: } exit 1 } + kotlinexperimentalapp: if: ${{ needs.env.outputs.mobile_android_build_all == 'true' }} needs: - env - androidbuild + permissions: + contents: read + packages: read name: kotlin_experimental_app runs-on: macos-12 timeout-minutes: 50 @@ -197,7 +229,9 @@ jobs: architecture: x64 distribution: zulu - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh --android + run: | + cd mobile + ./ci/mac_ci_setup.sh --android - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start emulator' with: @@ -211,13 +245,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + cd mobile + ./bazelw build \ + --config=mobile-remote-ci-macos \ --fat_apk_cpu=x86_64 \ --define envoy_mobile_listener=enabled \ //test/kotlin/apps/experimental:hello_envoy_kt - adb install -r --no-incremental bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk - adb shell am start -n io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity + adb install -r --no-incremental bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk + adb shell am start -n io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity - name: 'Check connectivity' run: | timeout 30 adb logcat -e "received headers with status 200" -m 1 || { diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index e35f4e26fa0e..428a1cba0d15 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -1,5 +1,8 @@ name: android_tests +permissions: + contents: read + on: push: branches: @@ -13,11 +16,16 @@ concurrency: jobs: env: uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write kotlintestsmac: if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} needs: env + permissions: + contents: read + packages: read # revert to //test/kotlin/... once fixed # https://github.com/envoyproxy/envoy-mobile/issues/1932 name: kotlin_tests_mac @@ -33,20 +41,26 @@ jobs: architecture: x64 distribution: zulu - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh + run: | + cd mobile + ./ci/mac_ci_setup.sh - name: 'Run Kotlin library tests' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw test \ - --test_output=all \ + cd mobile + ./bazelw test \ --build_tests_only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ --define=signal_trace=disabled \ //test/kotlin/io/... + javatestsmac: if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} needs: env + permissions: + contents: read + packages: read name: java_tests_mac runs-on: macos-12 timeout-minutes: 120 @@ -60,23 +74,29 @@ jobs: architecture: x64 distribution: zulu - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh + run: | + cd mobile + ./ci/mac_ci_setup.sh - name: 'Run Java library tests' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw test \ - --test_output=all \ + cd mobile + ./bazelw test \ --build_tests_only \ --config test-android \ --define envoy_mobile_listener=enabled \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ --define=signal_trace=disabled \ --define=system-helper=android \ //test/java/... + kotlintestslinux: if: ${{ needs.env.outputs.mobile_android_tests == 'true' }} needs: env + permissions: + contents: read + packages: read # Only kotlin tests are executed since with linux: # https://github.com/envoyproxy/envoy-mobile/issues/1418. name: kotlin_tests_linux @@ -95,10 +115,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw test \ - --test_output=all \ + cd mobile + ./bazelw test \ --build_tests_only \ --config test-android \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-clang") \ + --config=mobile-remote-ci \ --define=signal_trace=disabled \ //test/kotlin/... diff --git a/.github/workflows/mobile-asan.yml b/.github/workflows/mobile-asan.yml index c54a9a028eb4..90c3c8e24175 100644 --- a/.github/workflows/mobile-asan.yml +++ b/.github/workflows/mobile-asan.yml @@ -1,5 +1,8 @@ name: mobile_asan +permissions: + contents: read + on: push: branches: @@ -14,11 +17,16 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write asan: if: ${{ needs.env.outputs.mobile_asan == 'true' }} needs: env + permissions: + contents: read + packages: read name: asan runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 180 @@ -35,7 +43,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw test --test_output=all \ + cd mobile + ./bazelw test \ --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-asan") \ + --config=mobile-remote-ci-linux-asan \ //test/common/... diff --git a/.github/workflows/mobile-cc_tests.yml b/.github/workflows/mobile-cc_tests.yml index b9fb3b5cfad1..940ff56098e8 100644 --- a/.github/workflows/mobile-cc_tests.yml +++ b/.github/workflows/mobile-cc_tests.yml @@ -1,5 +1,8 @@ name: mobile_cc_tests +permissions: + contents: read + on: push: branches: @@ -14,11 +17,16 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write cctests: if: ${{ needs.env.outputs.mobile_cc_tests == 'true' }} needs: env + permissions: + contents: read + packages: read name: cc_tests runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 120 @@ -33,9 +41,9 @@ jobs: name: 'Run tests' # Regression test using the new API listener. TODO(#2711) clean up. run: | - cd mobile && ./bazelw test \ + cd mobile + ./bazelw test \ --action_env=LD_LIBRARY_PATH \ - --test_output=all \ --copt=-DUSE_API_LISTENER \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux") \ + --config=mobile-remote-ci \ //test/cc/... diff --git a/.github/workflows/mobile-compile_time_options.yml b/.github/workflows/mobile-compile_time_options.yml index b1f86bd2ba67..ff16adab15c3 100644 --- a/.github/workflows/mobile-compile_time_options.yml +++ b/.github/workflows/mobile-compile_time_options.yml @@ -1,5 +1,8 @@ name: mobile_compile_time_options +permissions: + contents: read + on: push: branches: @@ -14,10 +17,15 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write cc_test_no_yaml: needs: env + permissions: + contents: read + packages: read name: cc_test_no_yaml runs-on: ubuntu-20.04 timeout-minutes: 120 @@ -34,13 +42,17 @@ jobs: run: | cd mobile ./bazelw test \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux") \ - --config=ci \ + --config=mobile-remote-ci \ --define=envoy_yaml=disabled \ + --define=envoy_full_protos=disabled \ --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - //test/common/integration:client_integration_test --test_output=all + //test/common/integration:client_integration_test + cc_test: needs: env + permissions: + contents: read + packages: read name: cc_test runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 120 @@ -55,25 +67,31 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | cd mobile + TARGETS=$(bazel query --noshow_progress --noshow_loading_progress //test/cc/... + //test/common/... except //test/common/integration:client_integration_test) ./bazelw test \ --test_output=all \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux") \ - --config=ci \ + --config=mobile-remote-ci \ --define=signal_trace=disabled \ --define=envoy_mobile_request_compression=disabled \ --define=envoy_enable_http_datagrams=disabled \ --define=google_grpc=disabled \ --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor= \ - $(bazel query //test/cc/... + //test/common/... except //test/common/integration:client_integration_test) + $TARGETS + swift_build: if: ${{ needs.env.outputs.mobile_compile_time_options == 'true' }} needs: env + permissions: + contents: read + packages: read name: swift_build runs-on: macos-12 timeout-minutes: 120 steps: - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Build Swift library' env: @@ -83,7 +101,7 @@ jobs: ./bazelw shutdown ./bazelw build \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ --define=signal_trace=disabled \ --define=envoy_mobile_request_compression=disabled \ --define=envoy_mobile_stats_reporting=disabled \ @@ -93,9 +111,13 @@ jobs: --@envoy//bazel:http3=False \ --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor= \ //library/swift:ios_framework + kotlin_build: if: ${{ needs.env.outputs.mobile_compile_time_options == 'true' }} needs: env + permissions: + contents: read + packages: read name: kotlin_build runs-on: macos-12 timeout-minutes: 120 @@ -108,14 +130,16 @@ jobs: architecture: x64 distribution: zulu - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh --android + run: | + cd mobile + ./ci/mac_ci_setup.sh --android - name: 'Build Kotlin library' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | cd mobile ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ --fat_apk_cpu=x86_64 \ --define=signal_trace=disabled \ --define=envoy_mobile_request_compression=disabled \ diff --git a/.github/workflows/mobile-core.yml b/.github/workflows/mobile-core.yml index a35a77397178..45ecc49881c2 100644 --- a/.github/workflows/mobile-core.yml +++ b/.github/workflows/mobile-core.yml @@ -1,5 +1,8 @@ name: mobile_core +permissions: + contents: read + on: push: branches: @@ -14,11 +17,16 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write unittests: if: ${{ github.repository == 'envoyproxy/envoy' }} needs: env + permissions: + contents: read + packages: read name: unit_tests runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 120 @@ -34,11 +42,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw test \ + cd mobile + ./bazelw test \ --build_tests_only \ --action_env=LD_LIBRARY_PATH \ --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - --test_output=all \ --define envoy_mobile_listener=disabled \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux") \ + --config=mobile-remote-ci \ //test/common/... diff --git a/.github/workflows/mobile-coverage.yml b/.github/workflows/mobile-coverage.yml index afd6a8943088..01f83e6af038 100644 --- a/.github/workflows/mobile-coverage.yml +++ b/.github/workflows/mobile-coverage.yml @@ -1,5 +1,8 @@ name: mobile_coverage +permissions: + contents: read + on: push: branches: @@ -14,11 +17,16 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write coverage: if: ${{ needs.env.outputs.mobile_coverage == 'true' }} needs: env + permissions: + contents: read + packages: read name: coverage runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 120 @@ -35,13 +43,15 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && BAZEL_BUILD_OPTION_LIST="--config=remote-ci-linux-coverage" \ + cd mobile + export BAZEL_BUILD_OPTION_LIST="--config=mobile-remote-ci-linux-coverage" \ PATH=/opt/llvm/bin:${PATH} \ - COVERAGE_THRESHOLD=76 \ - ../test/run_envoy_bazel_coverage.sh //test/common/... //test/cc/... + COVERAGE_THRESHOLD=76 + ../test/run_envoy_bazel_coverage.sh //test/common/... //test/cc/... - name: 'Package coverage' run: | - cd mobile && tar -czf coverage.tar.gz generated/coverage + cd mobile + tar -czf coverage.tar.gz generated/coverage - name: 'Upload report' uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/mobile-docs.yml b/.github/workflows/mobile-docs.yml index b0180a972aa5..d0ae364808d8 100644 --- a/.github/workflows/mobile-docs.yml +++ b/.github/workflows/mobile-docs.yml @@ -1,5 +1,8 @@ name: mobile_docs +permissions: + contents: read + on: push: branches: @@ -14,11 +17,16 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write docs: if: ${{ github.repository == 'envoyproxy/envoy' }} needs: env + permissions: + contents: read + packages: read runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 20 container: diff --git a/.github/workflows/mobile-format.yml b/.github/workflows/mobile-format.yml index a8f9e19bef06..bc261481ad24 100644 --- a/.github/workflows/mobile-format.yml +++ b/.github/workflows/mobile-format.yml @@ -1,5 +1,8 @@ name: mobile_format +permissions: + contents: read + on: push: branches: @@ -14,11 +17,16 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write formatall: if: ${{ needs.env.outputs.mobile_formatting == 'true' }} needs: env + permissions: + contents: read + packages: read name: format_all runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 45 @@ -34,10 +42,16 @@ jobs: - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy - name: 'Run formatters' - run: cd mobile && ./tools/check_format.sh + run: | + cd mobile + ./tools/check_format.sh + precommit: if: ${{ needs.env.outputs.mobile_formatting == 'true' }} needs: env + permissions: + contents: read + packages: read name: precommit runs-on: macos-12 timeout-minutes: 45 @@ -46,10 +60,16 @@ jobs: - name: 'Install precommit' run: brew install pre-commit - name: 'Run precommit' - run: cd mobile && find mobile/* | pre-commit run --files + run: | + cd mobile + find mobile/* | pre-commit run --files + swiftlint: if: ${{ needs.env.outputs.mobile_formatting == 'true' }} needs: env + permissions: + contents: read + packages: read name: swift_lint runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 5 @@ -60,9 +80,13 @@ jobs: - name: 'Run Swift Lint (SwiftLint)' run: swiftlint lint --strict working-directory: mobile + drstring: if: ${{ needs.env.outputs.mobile_formatting == 'true' }} needs: env + permissions: + contents: read + packages: read name: drstring runs-on: macos-12 timeout-minutes: 10 @@ -71,10 +95,16 @@ jobs: - name: 'Run DrString' env: DEVELOPER_DIR: /Applications/Xcode_14.1.app - run: cd mobile && ./bazelw run @DrString//:drstring check + run: | + cd mobile + ./bazelw run --config=remote-ci @DrString//:drstring check + kotlinlint: if: ${{ needs.env.outputs.mobile_formatting == 'true' }} needs: env + permissions: + contents: read + packages: read name: kotlin_lint runs-on: macos-12 timeout-minutes: 45 @@ -86,15 +116,20 @@ jobs: java-package: jdk architecture: x64 distribution: zulu - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Run Kotlin Lint (Detekt)' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + cd mobile + ./bazelw build \ + --config=mobile-remote-ci-macos \ //library/kotlin/io/envoyproxy/envoymobile:envoy_lib_lint \ //examples/kotlin/hello_world:hello_envoy_kt_lint - name: 'Run Kotlin Formatter (ktlint)' - run: cd mobile && ./bazelw build kotlin_format + run: | + cd mobile + ./bazelw build --config=remote-ci kotlin_format diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 33ef5fbca5b2..d8cb45214a69 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -1,5 +1,8 @@ name: ios_build +permissions: + contents: read + on: push: branches: @@ -14,46 +17,61 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write iosbuild: if: ${{ needs.env.outputs.mobile_ios_build == 'true' }} needs: env + permissions: + contents: read + packages: read name: ios_build runs-on: macos-12 timeout-minutes: 120 steps: - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Build Envoy.framework distributable' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw shutdown - ./bazelw build \ + cd mobile + ./bazelw shutdown + ./bazelw build \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //library/swift:ios_framework + swifthelloworld: if: ${{ needs.env.outputs.mobile_ios_build == 'true' }} name: swift_helloworld needs: - env - iosbuild + permissions: + contents: read + packages: read runs-on: macos-12 timeout-minutes: 50 steps: - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Build app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ + cd mobile + ./bazelw build \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //examples/swift/hello_world:app - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start simulator' @@ -66,34 +84,43 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw run \ + cd mobile + ./bazelw run \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //examples/swift/hello_world:app &> /tmp/envoy.log & - - run: sed '/received headers with status 200/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) + - run: | + sed '/received headers with status 200/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) name: 'Check connectivity' - run: cat /tmp/envoy.log if: ${{ failure() || cancelled() }} name: 'Log app run' + swiftbaselineapp: if: ${{ needs.env.outputs.mobile_ios_build_all == 'true' }} needs: - env - iosbuild + permissions: + contents: read + packages: read name: swift_baseline_app runs-on: macos-12 timeout-minutes: 50 steps: - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Build app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ + cd mobile + ./bazelw build \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //test/swift/apps/baseline:app - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start simulator' @@ -106,34 +133,43 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw run \ + cd mobile + ./bazelw run \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //test/swift/apps/baseline:app &> /tmp/envoy.log & - - run: sed '/received headers with status 301/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) + - run: | + sed '/received headers with status 301/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) name: 'Check connectivity' - run: cat /tmp/envoy.log if: ${{ failure() || cancelled() }} name: 'Log app run' + swiftexperimentalapp: if: ${{ needs.env.outputs.mobile_ios_build_all == 'true' }} needs: - env - iosbuild + permissions: + contents: read + packages: read name: swift_experimental_app runs-on: macos-12 timeout-minutes: 50 steps: - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Build app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ + cd mobile + ./bazelw build \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ --define=admin_functionality=enabled \ --define envoy_mobile_listener=enabled \ //test/swift/apps/experimental:app @@ -148,36 +184,45 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw run \ + cd mobile + ./bazelw run \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ --define=admin_functionality=enabled \ --define envoy_mobile_listener=enabled \ //test/swift/apps/experimental:app &> /tmp/envoy.log & - - run: sed '/received headers with status 200/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) + - run: | + sed '/received headers with status 200/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) name: 'Check connectivity' - run: cat /tmp/envoy.log if: ${{ failure() || cancelled() }} name: 'Log app run' + swiftasyncawait: if: ${{ needs.env.outputs.mobile_ios_build_all == 'true' }} needs: - env - iosbuild + permissions: + contents: read + packages: read name: swift_async_await runs-on: macos-12 timeout-minutes: 50 steps: - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Build app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ + cd mobile + ./bazelw build \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //examples/swift/async_await:app - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start simulator' @@ -190,9 +235,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw run \ + cd mobile + ./bazelw run \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //examples/swift/async_await:app &> /tmp/envoy.log & - run: | checklogs () { @@ -216,25 +262,32 @@ jobs: - run: cat /tmp/envoy.log if: ${{ failure() || cancelled() }} name: 'Log app run' + objchelloworld: if: ${{ needs.env.outputs.mobile_ios_build_all == 'true' }} needs: - env - iosbuild + permissions: + contents: read + packages: read name: objc_helloworld runs-on: macos-12 timeout-minutes: 50 steps: - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Build app' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ + cd mobile + ./bazelw build \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //examples/objective-c/hello_world:app - uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd name: 'Start simulator' @@ -247,9 +300,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw run \ + cd mobile + ./bazelw run \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //examples/objective-c/hello_world:app &> /tmp/envoy.log & - run: sed '/received headers with status 301/q' <(touch /tmp/envoy.log && tail -F /tmp/envoy.log) name: 'Check connectivity' diff --git a/.github/workflows/mobile-ios_tests.yml b/.github/workflows/mobile-ios_tests.yml index 02df1e8d2f6b..791adb18afab 100644 --- a/.github/workflows/mobile-ios_tests.yml +++ b/.github/workflows/mobile-ios_tests.yml @@ -1,5 +1,8 @@ name: ios_tests +permissions: + contents: read + on: push: branches: @@ -14,49 +17,62 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write swifttests: if: ${{ needs.env.outputs.mobile_ios_tests == 'true' }} needs: env + permissions: + contents: read + packages: read name: swift_tests runs-on: macos-12 timeout-minutes: 120 steps: - uses: actions/checkout@v3 - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh + run: | + cd mobile + ./ci/mac_ci_setup.sh - name: 'Run swift library tests' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # runs with the listener enabled due to IdleTimeoutTest not setting up a test backend. run: | - cd mobile && ./bazelw test \ + cd mobile + ./bazelw test \ --experimental_ui_max_stdouterr_bytes=10485760 \ - --test_output=all \ --config=ios \ --define envoy_mobile_listener=enabled \ --build_tests_only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //test/swift/... + objctests: if: ${{ needs.env.outputs.mobile_ios_tests == 'true' }} needs: env + permissions: + contents: read + packages: read name: c_and_objc_tests runs-on: macos-12 timeout-minutes: 120 steps: - uses: actions/checkout@v3 - name: 'Install dependencies' - run: cd mobile && ./ci/mac_ci_setup.sh + run: | + cd mobile + ./ci/mac_ci_setup.sh - name: 'Run Objective-C library tests' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw test \ - --test_output=all \ + cd mobile + ./bazelw test \ --config=ios \ --build_tests_only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //test/objective-c/... \ //test/cc/unit:envoy_config_test diff --git a/.github/workflows/mobile-perf.yml b/.github/workflows/mobile-perf.yml index 754097c2b0aa..da9a880eb09e 100644 --- a/.github/workflows/mobile-perf.yml +++ b/.github/workflows/mobile-perf.yml @@ -1,5 +1,8 @@ name: mobile_perf +permissions: + contents: read + on: push: branches: @@ -13,6 +16,9 @@ concurrency: jobs: sizecurrent: if: ${{ github.repository == 'envoyproxy/envoy' }} + permissions: + contents: read + packages: read name: size_current runs-on: ubuntu-22.04 timeout-minutes: 120 @@ -29,17 +35,20 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ - --config=sizeopt \ - --config=release-common \ - --config=remote-ci-linux-clang \ + cd mobile + ./bazelw build \ + --config=mobile-remote-release-clang \ //test/performance:test_binary_size - uses: actions/upload-artifact@v3 with: name: sizecurrent path: mobile/bazel-bin/test/performance/test_binary_size + sizemain: if: ${{ github.repository == 'envoyproxy/envoy' }} + permissions: + contents: read + packages: read name: size_main runs-on: ubuntu-22.04 timeout-minutes: 90 @@ -50,6 +59,8 @@ jobs: CXX: /opt/llvm/bin/clang++ steps: - uses: actions/checkout@v3 + with: + ref: main - name: Add safe directory run: | git config --global --add safe.directory /__w/envoy/envoy @@ -57,21 +68,23 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - git checkout main && git pull origin main - cd mobile && ./bazelw build \ - --config=sizeopt \ - --config=release-common \ - --config=remote-ci-linux-clang \ + cd mobile + ./bazelw build \ + --config=mobile-remote-release-clang \ //test/performance:test_binary_size - uses: actions/upload-artifact@v3 with: name: sizemain path: mobile/bazel-bin/test/performance/test_binary_size + sizecompare: if: ${{ github.repository == 'envoyproxy/envoy' }} needs: - sizecurrent - sizemain + permissions: + contents: read + packages: read name: size_compare runs-on: ubuntu-22.04 timeout-minutes: 30 @@ -95,4 +108,6 @@ jobs: zip -9 dist/main.zip dist/main.stripped zip -9 dist/current.zip dist/current.stripped - name: 'Test size regression' - run: cd mobile && ./ci/test_size_regression.sh ../dist/main.zip ../dist/current.zip + run: | + cd mobile + ./ci/test_size_regression.sh ../dist/main.zip ../dist/current.zip diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index 3c4d64fbe0e5..3ed908943313 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -1,5 +1,8 @@ name: mobile_release +permissions: + contents: read + on: workflow_dispatch: schedule: @@ -10,16 +13,21 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write android_release_artifacts: - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule || !contains(github.actor, '[bot]')) }} needs: env + permissions: + contents: read + packages: read name: android_release_artifacts runs-on: ubuntu-22.04 timeout-minutes: 120 @@ -41,11 +49,11 @@ jobs: run: | version="0.5.0.$(date '+%Y%m%d')" ./bazelw build \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-clang") \ + --config=mobile-remote-release-clang \ --remote_header="Authorization=Bearer $GITHUB_TOKEN" \ --fat_apk_cpu=x86,x86_64,armeabi-v7a,arm64-v8a \ --define=pom_version="$version" \ - --config=release-android \ + --config=mobile-release-android \ --linkopt=-fuse-ld=lld \ //:android_dist - name: 'Tar artifacts' @@ -60,9 +68,13 @@ jobs: with: name: envoy_android_aar_sources path: mobile/envoy_android_aar_sources.tar.gz + android_release_deploy: name: android_release_deploy needs: android_release_artifacts + permissions: + contents: read + packages: read runs-on: ubuntu-22.04 timeout-minutes: 20 steps: diff --git a/.github/workflows/mobile-release_validation.yml b/.github/workflows/mobile-release_validation.yml index 88286e8a3e81..2ae52f7c6642 100644 --- a/.github/workflows/mobile-release_validation.yml +++ b/.github/workflows/mobile-release_validation.yml @@ -1,5 +1,8 @@ name: mobile_release_validation +permissions: + contents: read + on: push: branches: @@ -14,29 +17,43 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write validate_swiftpm_example: if: ${{ needs.env.outputs.mobile_release_validation == 'true' }} needs: env + permissions: + contents: read + packages: read name: validate_swiftpm_example runs-on: macos-12 timeout-minutes: 120 steps: - uses: actions/checkout@v3 - - run: cd mobile && ./ci/mac_ci_setup.sh + - run: | + cd mobile + ./ci/mac_ci_setup.sh name: 'Install dependencies' - name: 'Build xcframework' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw build \ + cd mobile + ./bazelw build \ --config=ios \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-macos") \ + --config=mobile-remote-ci-macos \ //:ios_xcframework # Ignore errors: Bad CRC when unzipping large files: https://bbs.archlinux.org/viewtopic.php?id=153011 - - run: unzip mobile/bazel-bin/library/swift/Envoy.xcframework.zip -d mobile/examples/swift/swiftpm/Packages || true + - run: | + unzip mobile/bazel-bin/library/swift/Envoy.xcframework.zip \ + -d mobile/examples/swift/swiftpm/Packages \ + || : name: 'Unzip xcframework' - - run: xcodebuild -project mobile/examples/swift/swiftpm/EnvoySwiftPMExample.xcodeproj -scheme EnvoySwiftPMExample -destination platform="iOS Simulator,name=iPhone 14 Pro Max,OS=16.1" + - run: | + xcodebuild -project mobile/examples/swift/swiftpm/EnvoySwiftPMExample.xcodeproj \ + -scheme EnvoySwiftPMExample \ + -destination platform="iOS Simulator,name=iPhone 14 Pro Max,OS=16.1" name: 'Build app' # TODO(jpsim): Run app and inspect logs to validate diff --git a/.github/workflows/mobile-traffic_director.yml b/.github/workflows/mobile-traffic_director.yml index 85a9bdf0b892..fd71352fead6 100644 --- a/.github/workflows/mobile-traffic_director.yml +++ b/.github/workflows/mobile-traffic_director.yml @@ -1,5 +1,8 @@ name: mobile_traffic_director +permissions: + contents: read + on: schedule: # Once a day at midnight. @@ -7,16 +10,13 @@ on: # Allows manual triggering in the UI. Makes it easier to test. workflow_dispatch: -permissions: - contents: read - concurrency: group: ${{ github.head_ref || github.run_id }}-github.workflow cancel-in-progress: true jobs: cc_test: - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule @@ -24,6 +24,7 @@ jobs: }} name: cc_test permissions: + contents: read packages: read runs-on: ubuntu-20.04 timeout-minutes: 120 @@ -35,12 +36,11 @@ jobs: - name: 'Run GcpTrafficDirectorIntegrationTest' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GCP_JWT_PRIVATE_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_JWT_TOKEN }} + GCP_TEST_PROJECT_API_KEY: ${{ secrets.GCP_TEST_PROJECT_API_KEY }} ENVOY_IP_TEST_VERSIONS: v4only run: | cd mobile ./bazelw run \ - --config=remote-ci-linux \ + --config=mobile-remote-ci \ --config=ci \ - --test_output=all \ //test/non_hermetic:gcp_traffic_director_integration_test diff --git a/.github/workflows/mobile-tsan.yml b/.github/workflows/mobile-tsan.yml index f72a907666c8..3a0619d316c9 100644 --- a/.github/workflows/mobile-tsan.yml +++ b/.github/workflows/mobile-tsan.yml @@ -1,5 +1,8 @@ name: mobile_tsan +permissions: + contents: read + on: push: branches: @@ -14,11 +17,16 @@ jobs: env: if: ${{ github.repository == 'envoyproxy/envoy' }} uses: ./.github/workflows/_env.yml - secrets: inherit + permissions: + contents: read + statuses: write tsan: if: ${{ needs.env.outputs.mobile_tsan == 'true' }} needs: env + permissions: + contents: read + packages: read name: tsan runs-on: ${{ needs.env.outputs.agent_ubuntu }} timeout-minutes: 90 @@ -35,8 +43,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd mobile && ./bazelw test \ - --test_output=all \ + cd mobile + ./bazelw test \ --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ - $([ -z $GITHUB_TOKEN ] || echo "--config=remote-ci-linux-tsan") \ + --config=mobile-remote-ci-linux-tsan \ //test/common/... diff --git a/.github/workflows/pr_notifier.yml b/.github/workflows/pr_notifier.yml index f7303a1678d6..df31d16768d9 100644 --- a/.github/workflows/pr_notifier.yml +++ b/.github/workflows/pr_notifier.yml @@ -14,7 +14,7 @@ jobs: pull-requests: read # for pr_notifier.py name: PR Notifier runs-on: ubuntu-22.04 - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index aa6d198d0744..d8f8986bae8a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,3 +1,8 @@ +name: Prune stale + +permissions: + contents: read + on: workflow_dispatch: schedule: @@ -5,17 +10,17 @@ on: jobs: prune_stale: - permissions: - issues: write # for actions/stale to close stale issues - pull-requests: write # for actions/stale to close stale PRs - name: Prune Stale - runs-on: ubuntu-22.04 - if: | + if: >- ${{ github.repository == 'envoyproxy/envoy' && (github.event.schedule || !contains(github.actor, '[bot]')) }} + permissions: + issues: write # for actions/stale to close stale issues + pull-requests: write # for actions/stale to close stale PRs + name: Prune stale + runs-on: ubuntu-22.04 steps: - name: Prune Stale diff --git a/CODEOWNERS b/CODEOWNERS index d169a56f97ad..9151de087d85 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -284,6 +284,8 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 /*/extensions/filters/http/ip_tagging @alyssawilk @JuniorHsu # Header to metadata /*/extensions/filters/http/header_to_metadata @zuercher @JuniorHsu +# Json to metadata +/*/extensions/filters/http/json_to_metadata @JuniorHsu @kbaichoo # zookeeper /*/extensions/filters/network/zookeeper_proxy @JuniorHsu @Winbobob @mattklein123 # Custom response filter diff --git a/SECURITY.md b/SECURITY.md index 6de9ddad4ecd..dceab5e0a447 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -68,8 +68,9 @@ best protect our users. If the vulnerability affects the last point release version, e.g. 1.10, then the full security release process described in this document will be activated. A security point release will be -created for 1.10, e.g. 1.10.1, together with a fix to main if necessary. Older point releases, -e.g. 1.9, are not supported by the Envoy project and will not have any security release created. +created for each currently supported Envoy version, as described in [stable releases](RELEASES.md#stable-releases), +together with a fix to main if necessary. Older point releases, +e.g. 1.5, are not supported by the Envoy project and will not have any security release created. If a security vulnerability affects only these older versions but not main or the last supported point release, the Envoy security team will share this information with the private distributor diff --git a/api/BUILD b/api/BUILD index 201c89aaed00..0cfe5040f790 100644 --- a/api/BUILD +++ b/api/BUILD @@ -188,6 +188,7 @@ proto_library( "//envoy/extensions/filters/http/header_to_metadata/v3:pkg", "//envoy/extensions/filters/http/health_check/v3:pkg", "//envoy/extensions/filters/http/ip_tagging/v3:pkg", + "//envoy/extensions/filters/http/json_to_metadata/v3:pkg", "//envoy/extensions/filters/http/jwt_authn/v3:pkg", "//envoy/extensions/filters/http/kill_request/v3:pkg", "//envoy/extensions/filters/http/local_ratelimit/v3:pkg", diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 24682a66ac10..8e8576be969e 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -17,14 +17,14 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_desc = "protoc plugin to generate polyglot message validators", project_url = "https://github.com/bufbuild/protoc-gen-validate", use_category = ["api"], - sha256 = "f1ec013cfdfffa7a17d75b55d41265dad47d24e0e9d86c02311562e15be52da9", - version = "1.0.1", + sha256 = "0b1b1ea8c248dce8c7592dc1a93e4adebd116f0d68123f8eb34251e7ce410866", + version = "1.0.2", urls = ["https://github.com/bufbuild/protoc-gen-validate/archive/refs/tags/v{version}.zip"], strip_prefix = "protoc-gen-validate-{version}", - release_date = "2023-05-09", + release_date = "2023-06-26", implied_untracked_deps = [ "com_github_iancoleman_strcase", - "com_github_lyft_protoc_gen_star", + "com_github_lyft_protoc_gen_star_v2", "com_github_spf13_afero", "org_golang_google_genproto", "org_golang_x_text", @@ -131,11 +131,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "buf", project_desc = "A new way of working with Protocol Buffers.", # Used for breaking change detection in API protobufs project_url = "https://buf.build", - version = "1.23.1", - sha256 = "6ce820282bfbcd1e8c914616db45111e128491febb6d38fa2267da697b0865db", + version = "1.25.0", + sha256 = "1531e8fa3c02bd397781602cd603a977ed6256cf502f6f78d6ba0058b78c38ac", strip_prefix = "buf", urls = ["https://github.com/bufbuild/buf/releases/download/v{version}/buf-Linux-x86_64.tar.gz"], - release_date = "2023-06-30", + release_date = "2023-07-18", use_category = ["api"], license = "Apache-2.0", license_url = "https://github.com/bufbuild/buf/blob/v{version}/LICENSE", diff --git a/api/buf.yaml b/api/buf.yaml index 60af4cfbc902..245335821f07 100644 --- a/api/buf.yaml +++ b/api/buf.yaml @@ -6,6 +6,7 @@ deps: - buf.build/opentelemetry/opentelemetry - buf.build/gogo/protobuf - buf.build/cncf/xds +- buf.build/envoyproxy/protoc-gen-validate breaking: ignore_unstable_packages: true use: diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index a5fcbef104b3..f12f9819dcf8 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -438,6 +438,7 @@ message Admin { } // Cluster manager :ref:`architecture overview `. +// [#next-free-field: 6] message ClusterManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v2.ClusterManager"; @@ -478,6 +479,11 @@ message ClusterManager { // ` :ref:`GRPC // `. core.v3.ApiConfigSource load_stats_config = 4; + + // Whether the ClusterManager will create clusters on the worker threads + // inline during requests. This will save memory and CPU cycles in cases where + // there are lots of inactive clusters and > 1 worker thread. + bool enable_deferred_cluster_creation = 5; } // Allows you to specify different watchdog configs for different subsystems. diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 17fe88311eae..71b12f7247e0 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -56,7 +56,7 @@ message QuicKeepAliveSettings { } // QUIC protocol options which apply to both downstream and upstream connections. -// [#next-free-field: 6] +// [#next-free-field: 8] message QuicProtocolOptions { // Maximum number of streams that the client can negotiate per connection. 100 // if not specified. @@ -94,6 +94,14 @@ message QuicProtocolOptions { // Probes the peer at the configured interval to solicit traffic, i.e. ACK or PATH_RESPONSE, from the peer to push back connection idle timeout. // If absent, use the default keepalive behavior of which a client connection sends PINGs every 15s, and a server connection doesn't do anything. QuicKeepAliveSettings connection_keepalive = 5; + + // A comma-separated list of strings representing QUIC connection options defined in + // `QUICHE ` and to be sent by upstream connections. + string connection_options = 6; + + // A comma-separated list of strings representing QUIC client connection options defined in + // `QUICHE ` and to be sent by upstream connections. + string client_connection_options = 7; } message UpstreamHttpProtocolOptions { diff --git a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto index db1d83471cf1..fe92e3548f07 100644 --- a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto +++ b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto @@ -51,16 +51,15 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // * Whether subsequent HTTP requests are transmitted synchronously or whether they are // sent asynchronously. // * To modify request or response trailers if they already exist -// * To add request or response trailers where they are not present // // The filter supports up to six different processing steps. Each is represented by // a gRPC stream message that is sent to the external processor. For each message, the // processor must send a matching response. // // * Request headers: Contains the headers from the original HTTP request. -// * Request body: Sent in a single message if the BUFFERED or BUFFERED_PARTIAL -// mode is chosen, in multiple messages if the STREAMED mode is chosen, and not -// at all otherwise. +// * Request body: Delivered if they are present and sent in a single message if +// the BUFFERED or BUFFERED_PARTIAL mode is chosen, in multiple messages if the +// STREAMED mode is chosen, and not at all otherwise. // * Request trailers: Delivered if they are present and if the trailer mode is set // to SEND. // * Response headers: Contains the headers from the HTTP response. Keep in mind @@ -99,7 +98,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // ` object in a namespace matching the filter // name. // -// [#next-free-field: 15] +// [#next-free-field: 16] message ExternalProcessor { // Configuration for the gRPC service that the filter will communicate with. // The filter supports both the "Envoy" and "Google" gRPC clients. @@ -200,6 +199,14 @@ message ExternalProcessor { // :ref:`mode_override `. // If not set, ``mode_override`` API in the response message will be ignored. bool allow_mode_override = 14; + + // If set to true, ignore the + // :ref:`immediate_response ` + // message in an external processor response. In such case, no local reply will be sent. + // Instead, the stream to the external processor will be closed. There will be no + // more external processing for this stream from now on. + // [#not-implemented-hide:] + bool disable_immediate_response = 15; } // The HeaderForwardingRules structure specifies what headers are diff --git a/api/envoy/extensions/filters/http/json_to_metadata/v3/BUILD b/api/envoy/extensions/filters/http/json_to_metadata/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/api/envoy/extensions/filters/http/json_to_metadata/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/envoy/extensions/filters/http/json_to_metadata/v3/json_to_metadata.proto b/api/envoy/extensions/filters/http/json_to_metadata/v3/json_to_metadata.proto new file mode 100644 index 000000000000..3dfb87f97a7d --- /dev/null +++ b/api/envoy/extensions/filters/http/json_to_metadata/v3/json_to_metadata.proto @@ -0,0 +1,116 @@ +syntax = "proto3"; + +package envoy.extensions.filters.http.json_to_metadata.v3; + +import "google/protobuf/struct.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.http.json_to_metadata.v3"; +option java_outer_classname = "JsonToMetadataProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/json_to_metadata/v3;json_to_metadatav3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Json-To-Metadata Filter] +// +// The configuration for transforming json body into metadata. This is useful +// for matching load balancer subsets, logging, etc. +// +// Json to Metadata :ref:`configuration overview `. +// [#extension: envoy.filters.http.json_to_metadata] + +message JsonToMetadata { + enum ValueType { + // The value is a serialized `protobuf.Value + // `_. + PROTOBUF_VALUE = 0; + + STRING = 1; + + NUMBER = 2; + } + + // [#next-free-field: 6] + message KeyValuePair { + // The namespace — if this is empty, the filter's namespace will be used. + string metadata_namespace = 1; + + // The key to use within the namespace. + string key = 2 [(validate.rules).string = {min_len: 1}]; + + oneof value_type { + // The value to pair with the given key. + // + // When used for on_present case, if value is non-empty it'll be used instead + // of the the value of the JSON key. If both are empty, the the value of the + // JSON key is used as-is. + // + // When used for on_missing/on_error case, a non-empty value + // must be provided. + // + // It ignores ValueType, i.e., not type casting. + google.protobuf.Value value = 3; + } + + // The value's type — defaults to protobuf.Value. + ValueType type = 4 [(validate.rules).enum = {defined_only: true}]; + + // False if we want to overwrite the existing metadata value. Default to false. + bool preserve_existing_metadata_value = 5; + } + + message Selector { + // TODO(kuochunghsu): Explore matchers for array handling. + oneof selector { + // key to match + string key = 1 [(validate.rules).string = {min_len: 1}]; + } + } + + // A Rule defines what metadata to apply when a key-value is present, missing in the json + // or fail to parse the payload. + message Rule { + // Specifies that a match will be performed on the value of a property. + // Here's an example to match on 1 in {"foo": {"bar": 1}, "bar": 2} + // + // selectors: + // - key: foo + // - key: bar + repeated Selector selectors = 1 [(validate.rules).repeated = {min_items: 1}]; + + // If the attribute is present, apply this metadata KeyValuePair. + KeyValuePair on_present = 2; + + // If the attribute is missing, apply this metadata KeyValuePair. + // + // The value in the KeyValuePair must be set. + KeyValuePair on_missing = 3; + + // If the body is too large or fail to parse, apply this metadata KeyValuePair. + // + // The value in the KeyValuePair must be set. + KeyValuePair on_error = 4; + } + + message MatchRules { + // The list of rules to apply. + repeated Rule rules = 1 [(validate.rules).repeated = {min_items: 1}]; + + // Allowed content-type for json to metadata transformation. + // Default to {"application/json"}. + // + // Set `allow_empty_content_type` if empty/missing content-type header + // is allowed. + repeated string allow_content_types = 2 + [(validate.rules).repeated = {items {string {min_len: 1}}}]; + + // Allowed empty content-type for json to metadata transformation. + // Default to false. + bool allow_empty_content_type = 3; + } + + // Rules to match json body of requests + MatchRules request_rules = 1 [(validate.rules).message = {required: true}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 52f5060b54d3..8f97a336d71f 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -126,6 +126,7 @@ proto_library( "//envoy/extensions/filters/http/header_to_metadata/v3:pkg", "//envoy/extensions/filters/http/health_check/v3:pkg", "//envoy/extensions/filters/http/ip_tagging/v3:pkg", + "//envoy/extensions/filters/http/json_to_metadata/v3:pkg", "//envoy/extensions/filters/http/jwt_authn/v3:pkg", "//envoy/extensions/filters/http/kill_request/v3:pkg", "//envoy/extensions/filters/http/local_ratelimit/v3:pkg", diff --git a/bazel/BUILD b/bazel/BUILD index 71db4ba301e4..d081f142371f 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -382,6 +382,11 @@ config_setting( values = {"define": "envoy_exceptions=disabled"}, ) +config_setting( + name = "disable_full_protos", + values = {"define": "envoy_full_protos=disabled"}, +) + config_setting( name = "disable_envoy_mobile_listener", values = {"define": "envoy_mobile_listener=disabled"}, diff --git a/bazel/coverage/BUILD b/bazel/coverage/BUILD index 9aa87d086968..56f73dc2ad1d 100644 --- a/bazel/coverage/BUILD +++ b/bazel/coverage/BUILD @@ -1,9 +1,3 @@ licenses(["notice"]) # Apache 2 -# TODO(lizan): Add test for this and upstream to upstream Bazel. -filegroup( - name = "coverage_support", - srcs = ["collect_cc_coverage.sh"], -) - exports_files(["fuzz_coverage_wrapper.sh"]) diff --git a/bazel/coverage/collect_cc_coverage.sh b/bazel/coverage/collect_cc_coverage.sh deleted file mode 100755 index 3f9fd700a8ed..000000000000 --- a/bazel/coverage/collect_cc_coverage.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/bash -e -# -# This is a fork of https://github.com/bazelbuild/bazel/blob/3.1.0/tools/test/collect_cc_coverage.sh -# to cover most of use cases in Envoy. -# TODO(lizan): Move this to upstream Bazel -# -# Copyright 2016 The Bazel Authors. All rights reserved. -# -# 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. - -# This script collects code coverage data for C++ sources, after the tests -# were executed. -# -# Bazel C++ code coverage collection support is poor and limited. There is -# an ongoing effort to improve this (tracking issue #1118). -# -# Bazel uses the lcov tool for gathering coverage data. There is also -# an experimental support for clang llvm coverage, which uses the .profraw -# data files to compute the coverage report. -# -# This script assumes the following environment variables are set: -# - COVERAGE_DIR Directory containing metadata files needed for -# coverage collection (e.g. gcda files, profraw). -# - COVERAGE_MANIFEST Location of the instrumented file manifest. -# - COVERAGE_GCOV_PATH Location of gcov. This is set by the TestRunner. -# - COVERAGE_GCOV_OPTIONS Additional options to pass to gcov. -# - ROOT Location from where the code coverage collection -# was invoked. -# -# The script looks in $COVERAGE_DIR for the C++ metadata coverage files (either -# gcda or profraw) and uses either lcov or gcov to get the coverage data. -# The coverage data is placed in $COVERAGE_OUTPUT_FILE. - -read -ra COVERAGE_GCOV_OPTIONS <<< "${COVERAGE_GCOV_OPTIONS:-}" - -# Checks if clang llvm coverage should be used instead of lcov. -function uses_llvm() { - if stat "${COVERAGE_DIR}"/*.profraw >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -# Returns 0 if gcov must be used, 1 otherwise. -function uses_gcov() { - [[ "$GCOV_COVERAGE" -eq "1" ]] && return 0 - return 1 -} - -function init_gcov() { - # Symlink the gcov tool such with a link called gcov. Clang comes with a tool - # called llvm-cov, which behaves like gcov if symlinked in this way (otherwise - # we would need to invoke it with "llvm-cov gcov"). - # For more details see https://llvm.org/docs/CommandGuide/llvm-cov.html. - GCOV="${COVERAGE_DIR}/gcov" - ln -s "${COVERAGE_GCOV_PATH}" "${GCOV}" -} - -# Computes code coverage data using the clang generated metadata found under -# $COVERAGE_DIR. -# Writes the collected coverage into the given output file. -function llvm_coverage() { - local output_file="${1}" object_file object_files object_param=() - shift - export LLVM_PROFILE_FILE="${COVERAGE_DIR}/%h-%p-%m.profraw" - "${COVERAGE_GCOV_PATH}" merge -output "${output_file}.data" \ - "${COVERAGE_DIR}"/*.profraw - - - object_files="$(find -L "${RUNFILES_DIR}" -type f -exec file -L {} \; \ - | grep ELF | grep -v "LSB core" | sed 's,:.*,,')" - - for object_file in ${object_files}; do - object_param+=(-object "${object_file}") - done - - llvm-cov export -instr-profile "${output_file}.data" -format=lcov \ - -ignore-filename-regex='.*external/.+' \ - -ignore-filename-regex='/tmp/.+' \ - "${object_param[@]}" | sed 's#/proc/self/cwd/##' > "${output_file}" -} - -# Generates a code coverage report in gcov intermediate text format by invoking -# gcov and using the profile data (.gcda) and notes (.gcno) files. -# -# The profile data files are expected to be found under $COVERAGE_DIR. -# The notes file are expected to be found under $ROOT. -# -# - output_file The location of the file where the generated code coverage -# report is written. -function gcov_coverage() { - local gcda gcno_path line output_file="${1}" - shift - - # Copy .gcno files next to their corresponding .gcda files in $COVERAGE_DIR - # because gcov expects them to be in the same directory. - while read -r line; do - if [[ ${line: -4} == "gcno" ]]; then - gcno_path=${line} - gcda="${COVERAGE_DIR}/$(dirname "${gcno_path}")/$(basename "${gcno_path}" .gcno).gcda" - # If the gcda file was not found we skip generating coverage from the gcno - # file. - if [[ -f "$gcda" ]]; then - # gcov expects both gcno and gcda files to be in the same directory. - # We overcome this by copying the gcno to $COVERAGE_DIR where the gcda - # files are expected to be. - if [ ! -f "${COVERAGE_DIR}/${gcno_path}" ]; then - mkdir -p "${COVERAGE_DIR}/$(dirname "${gcno_path}")" - cp "$ROOT/${gcno_path}" "${COVERAGE_DIR}/${gcno_path}" - fi - # Invoke gcov to generate a code coverage report with the flags: - # -i Output gcov file in an intermediate text format. - # The output is a single .gcov file per .gcda file. - # No source code is required. - # -o directory The directory containing the .gcno and - # .gcda data files. - # "${gcda"} The input file name. gcov is looking for data files - # named after the input filename without its extension. - # gcov produces files called .gcov in the current - # directory. These contain the coverage information of the source file - # they correspond to. One .gcov file is produced for each source - # (or header) file containing code which was compiled to produce the - # .gcda files. - # Don't generate branch coverage (-b) because of a gcov issue that - # segfaults when both -i and -b are used (see - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84879). - "${GCOV}" -i "${COVERAGE_GCOV_OPTIONS[@]}" -o "$(dirname "${gcda}")" "${gcda}" - - # Append all .gcov files in the current directory to the output file. - cat ./*.gcov >> "$output_file" - # Delete the .gcov files. - rm ./*.gcov - fi - fi - done < "${COVERAGE_MANIFEST}" -} - -function main() { - init_gcov - - # If llvm code coverage is used, we output the raw code coverage report in - # the $COVERAGE_OUTPUT_FILE. This report will not be converted to any other - # format by LcovMerger. - # TODO(#5881): Convert profdata reports to lcov. - if uses_llvm; then - BAZEL_CC_COVERAGE_TOOL="PROFDATA" - fi - - # When using either gcov or lcov, have an output file specific to the test - # and format used. For lcov we generate a ".dat" output file and for gcov - # a ".gcov" output file. It is important that these files are generated under - # COVERAGE_DIR. - # When this script is invoked by tools/test/collect_coverage.sh either of - # these two coverage reports will be picked up by LcovMerger and their - # content will be converted and/or merged with other reports to an lcov - # format, generating the final code coverage report. - case "$BAZEL_CC_COVERAGE_TOOL" in - ("GCOV") gcov_coverage "$COVERAGE_DIR/_cc_coverage.gcov" ;; - ("PROFDATA") llvm_coverage "$COVERAGE_DIR/_cc_coverage.dat" ;; - (*) echo "Coverage tool $BAZEL_CC_COVERAGE_TOOL not supported" \ - && exit 1 - esac -} - -main diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index f740e591cc20..8a9e154a39fe 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -128,12 +128,8 @@ def envoy_cc_library( visibility = visibility, tags = tags, textual_hdrs = textual_hdrs, - deps = deps + [envoy_external_dep_path(dep) for dep in external_deps] + [ - repository + "//envoy/common:base_includes", - repository + "//source/common/common:fmt_lib", - envoy_external_dep_path("abseil_strings"), - envoy_external_dep_path("fmtlib"), - ] + envoy_pch_deps(repository, "//source/common/common:common_pch"), + deps = deps + [envoy_external_dep_path(dep) for dep in external_deps] + + envoy_pch_deps(repository, "//source/common/common:common_pch"), alwayslink = alwayslink, linkstatic = envoy_linkstatic(), strip_include_prefix = strip_include_prefix, diff --git a/bazel/envoy_mobile_defines.bzl b/bazel/envoy_mobile_defines.bzl index 5bce8ebef8bf..9c2ef5671719 100644 --- a/bazel/envoy_mobile_defines.bzl +++ b/bazel/envoy_mobile_defines.bzl @@ -1,10 +1,23 @@ # DO NOT LOAD THIS FILE. Load envoy_build_system.bzl instead. -load(":envoy_select.bzl", "envoy_select_admin_functionality", "envoy_select_disable_exceptions", "envoy_select_enable_http3", "envoy_select_enable_http_datagrams", "envoy_select_enable_yaml", "envoy_select_envoy_mobile_listener", "envoy_select_envoy_mobile_request_compression", "envoy_select_envoy_mobile_stats_reporting", "envoy_select_google_grpc") +load( + ":envoy_select.bzl", + "envoy_select_admin_functionality", + "envoy_select_disable_exceptions", + "envoy_select_enable_full_protos", + "envoy_select_enable_http3", + "envoy_select_enable_http_datagrams", + "envoy_select_enable_yaml", + "envoy_select_envoy_mobile_listener", + "envoy_select_envoy_mobile_request_compression", + "envoy_select_envoy_mobile_stats_reporting", + "envoy_select_google_grpc", +) # Compute the defines needed for Envoy Mobile libraries that don't use Envoy's main library wrappers. def envoy_mobile_defines(repository): return envoy_select_admin_functionality(["ENVOY_ADMIN_FUNCTIONALITY"], repository) + \ envoy_select_enable_http3(["ENVOY_ENABLE_QUIC"], repository) + \ + envoy_select_enable_full_protos(["ENVOY_ENABLE_FULL_PROTOS"], repository) + \ envoy_select_enable_yaml(["ENVOY_ENABLE_YAML"], repository) + \ envoy_select_disable_exceptions(["ENVOY_DISABLE_EXCEPTIONS"], repository) + \ envoy_select_enable_http_datagrams(["ENVOY_ENABLE_HTTP_DATAGRAMS"], repository) + \ diff --git a/bazel/envoy_select.bzl b/bazel/envoy_select.bzl index 7cd774bd460e..6138ef9aa1dd 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -115,6 +115,20 @@ def envoy_select_hot_restart(xs, repository = ""): "//conditions:default": xs, }) +# Selects the given values if full protos are enabled in the current build. +def envoy_select_enable_full_protos(xs, repository = ""): + return select({ + repository + "//bazel:disable_full_protos": [], + "//conditions:default": xs, + }) + +# Selects the given values if lite protos are enabled in the current build. +def envoy_select_enable_lite_protos(xs, repository = ""): + return select({ + repository + "//bazel:disable_full_protos": xs, + "//conditions:default": [], + }) + # Selects the given values if signal trace is enabled in the current build. def envoy_select_signal_trace(xs, repository = ""): return select({ diff --git a/bazel/envoy_test.bzl b/bazel/envoy_test.bzl index c331735abe53..ad05121a9922 100644 --- a/bazel/envoy_test.bzl +++ b/bazel/envoy_test.bzl @@ -81,7 +81,6 @@ def envoy_cc_fuzz_test( dictionaries = [], repository = "", size = "medium", - shard_count = None, deps = [], tags = [], **kwargs): @@ -121,7 +120,6 @@ def envoy_cc_fuzz_test( "//conditions:default": ["$(locations %s)" % corpus_name], }), data = [corpus_name], - shard_count = shard_count, # No fuzzing on macOS or Windows deps = select({ "@envoy//bazel:apple": [repository + "//test:dummy_main"], diff --git a/bazel/external/boringssl_fips.BUILD b/bazel/external/boringssl_fips.BUILD index 1af9f34b1f02..353b1b43292d 100644 --- a/bazel/external/boringssl_fips.BUILD +++ b/bazel/external/boringssl_fips.BUILD @@ -30,5 +30,5 @@ genrule( "ssl/libssl.a", ], cmd = "$(location {}) $(location crypto/libcrypto.a) $(location ssl/libssl.a)".format("@envoy//bazel/external:boringssl_fips.genrule_cmd"), - exec_tools = ["@envoy//bazel/external:boringssl_fips.genrule_cmd"], + tools = ["@envoy//bazel/external:boringssl_fips.genrule_cmd"], ) diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 9f1e7ab958fc..4832d67aa238 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -198,6 +198,7 @@ envoy_cc_library( visibility = ["//visibility:public"], deps = [ ":quiche_common_platform_export", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:variant", ], ) @@ -5150,6 +5151,7 @@ envoy_quiche_platform_impl_cc_library( ], deps = [ ":quiche_common_platform_export", + "@com_google_absl//absl/synchronization", ], ) diff --git a/bazel/protobuf.patch b/bazel/protobuf.patch index b1a9d81eb4d8..2b917c8c6ad8 100644 --- a/bazel/protobuf.patch +++ b/bazel/protobuf.patch @@ -5,7 +5,7 @@ index 0f6e41e3a..c0d2bbccf 100644 @@ -454,14 +454,79 @@ cc_library( visibility = ["//visibility:public"], ) - + +# Envoy: Patch + cc_binary( @@ -16,7 +16,7 @@ index 0f6e41e3a..c0d2bbccf 100644 visibility = ["//visibility:public"], deps = ["//src/google/protobuf/compiler:protoc_lib"], ) - + +# Lifted from `rules_proto` +config_setting( + name = "linux-aarch_64", @@ -89,8 +89,8 @@ index e7555ee10..a93beb1c5 100644 +++ b/python/google/protobuf/__init__.py @@ -31,3 +31,10 @@ # Copyright 2007 Google Inc. All Rights Reserved. - - __version__ = '4.23.1' + + __version__ = '4.23.4' + + +if __name__ != '__main__': @@ -109,7 +109,7 @@ diff --git a/src/google/protobuf/io/BUILD.bazel b/src/google/protobuf/io/BUILD.b + "//conditions:default": ["//external:zlib"], }), ) - + diff --git a/src/google/protobuf/port_def.inc b/src/google/protobuf/port_def.inc --- a/src/google/protobuf/port_def.inc 2023-06-27 01:17:34.917105764 +0000 +++ b/src/google/protobuf/port_def.inc 2023-06-27 01:18:12.069060142 +0000 @@ -138,12 +138,12 @@ diff --git a/src/google/protobuf/map_field.h b/src/google/protobuf/map_field.h --- a/src/google/protobuf/map_field.h 2023-06-30 17:14:18.934528580 +0000 +++ b/src/google/protobuf/map_field.h 2023-06-30 17:14:52.098500807 +0000 @@ -345,7 +345,7 @@ - + protected: // "protected" stops users from deleting a `MapFieldBase *` - ~MapFieldBase(); + virtual ~MapFieldBase(); - + public: // Returns reference to internal repeated field. Data written using diff --git a/src/google/protobuf/compiler/BUILD.bazel b/src/google/protobuf/compiler/BUILD.bazel @@ -166,7 +166,7 @@ index 1c6a24945..c27d0bf2a 100644 @@ -1062,6 +1062,7 @@ static_assert(PROTOBUF_ABSL_MIN(20230125, 3), #pragma warning(disable: 4125) #endif - + +#pragma GCC diagnostic ignored "-Wundef" #if PROTOBUF_ENABLE_DEBUG_LOGGING_MAY_LEAK_PII #define PROTOBUF_DEBUG true diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index e0134ffdeef3..ae7f06a1d3ce 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1,6 +1,6 @@ # This should match the schema defined in external_deps.bzl. -PROTOBUF_VERSION = "23.1" +PROTOBUF_VERSION = "23.4" # These names of these deps *must* match the names used in `/bazel/protobuf.patch`, # and both must match the names from the protobuf releases (see @@ -8,11 +8,11 @@ PROTOBUF_VERSION = "23.1" # The names change in upcoming versions. # The shas are calculated from the downloads on the releases page. PROTOC_VERSIONS = dict( - linux_aarch_64 = "f174eb3a6bd812e9946be3a9ef3fb8f8ac4a6f8acd0a01c928fb2fecb22b6fb0", - linux_x86_64 = "031f8e7504eb359df58389b31752f8081c01b01132a2f3f768a3792ac4b06f3f", - osx_aarch_64 = "8d0af9adbbde1a9791d10125f4742a4c9fa84f85ee46fe69adde6bf5e8a4a428", - osx_x86_64 = "5d0367dfd58ea894f87d1d6efbd800bf52820842e9151d265db17471bc69fe94", - win64 = "420cd7a1548a9c3ef5b5a7e969b6fcf8ee6a5a09cec99d7a3209406f028e5dce", + linux_aarch_64 = "1c7750b6e038305b5a7fc3d0cda1ebefdf106a4f30a787bf826ed2fc47c3967d", + linux_x86_64 = "0502f286ac9ed860b629a7965a14527b1f2dd131e4283fa23c2d7f184672aa9a", + osx_aarch_64 = "8c7afae8626b6811e7b5897d16d940c2dbf50b1e135ed958a01db6566bdda726", + osx_x86_64 = "07e5fdcf1b0708d3367dc5e6eb8d135de7e407d75316c93155cfd8ab362eec80", + win64 = "a309c39442fb75f0db343cb22c111a00f91cdf0767f332e170644b9378e2bcc6", ) REPOSITORY_LOCATIONS_SPEC = dict( @@ -60,10 +60,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Apple Rules for Bazel", project_desc = "Bazel rules for Apple platforms", project_url = "https://github.com/bazelbuild/rules_apple", - version = "2.4.1", - sha256 = "2a0a35c9f72a0b0ac9238ecb081b0da4bb3e9739e25d2a910cc6b4c4425c01be", + version = "2.5.0", + sha256 = "8ac4c7997d863f3c4347ba996e831b5ec8f7af885ee8d4fe36f1c3c8f0092b2c", urls = ["https://github.com/bazelbuild/rules_apple/releases/download/{version}/rules_apple.{version}.tar.gz"], - release_date = "2023-07-07", + release_date = "2023-07-17", use_category = ["build"], license = "Apache-2.0", license_url = "https://github.com/bazelbuild/rules_apple/blob/{version}/LICENSE", @@ -235,12 +235,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "xxHash", project_desc = "Extremely fast hash algorithm", project_url = "https://github.com/Cyan4973/xxHash", - version = "0.8.1", - sha256 = "3bb6b7d6f30c591dd65aaaff1c8b7a5b94d81687998ca9400082c739a690436c", + version = "0.8.2", + sha256 = "baee0c6afd4f03165de7a4e67988d16f0f2b257b51d0e3cb91909302a26a79c4", strip_prefix = "xxHash-{version}", urls = ["https://github.com/Cyan4973/xxHash/archive/v{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2021-11-29", + release_date = "2023-07-21", cpe = "N/A", license = "BSD-2-Clause", license_url = "https://github.com/Cyan4973/xxHash/blob/v{version}/LICENSE", @@ -825,11 +825,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( # test/common/json:gen_excluded_unicodes to recompute the ranges # excluded from differential fuzzing that are populated in # test/common/json/json_sanitizer_test_util.cc. - sha256 = "72bae766561149f8507a81647f91fc519d2a60309613f004ed307cb5f9b1242b", + sha256 = "a700a49470d301f1190a487a923b5095bf60f08f4ae4cac9f5f7c36883d17971", strip_prefix = "protobuf-{version}", urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v{version}/protobuf-{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2023-05-17", + release_date = "2023-07-06", cpe = "cpe:2.3:a:google:protobuf:*", license = "Protocol Buffers", license_url = "https://github.com/protocolbuffers/protobuf/blob/v{version}/LICENSE", @@ -1065,8 +1065,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "curl", project_desc = "Library for transferring data with URLs", project_url = "https://curl.haxx.se", - version = "8.0.1", - sha256 = "5fd29000a4089934f121eff456101f0a5d09e2a3e89da1d714adf06c4be887cb", + version = "8.2.1", + sha256 = "f98bdb06c0f52bdd19e63c4a77b5eb19b243bcbbd0f5b002b9f3cba7295a3a42", strip_prefix = "curl-{version}", urls = ["https://github.com/curl/curl/releases/download/curl-{underscore_version}/curl-{version}.tar.gz"], use_category = ["dataplane_ext", "observability_ext"], @@ -1076,7 +1076,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.grpc_credentials.aws_iam", "envoy.tracers.opencensus", ], - release_date = "2023-03-20", + release_date = "2023-07-26", cpe = "cpe:2.3:a:haxx:libcurl:*", license = "curl", license_url = "https://github.com/curl/curl/blob/curl-{underscore_version}/COPYING", @@ -1115,12 +1115,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "1fbe4b9eff8435076abffa974a9e34f6578d12b9", - sha256 = "bf6bdfd0ee85afc5db5b0c006fb1c879bb439ff9ff71c85e3312b1b290adea6b", + version = "37e40568cb4e303d963faf105927ffac6ff85792", + sha256 = "615045901e2297b1987ebbbed33a4b7186af2cd4d611599c1c391b6895adaf25", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2023-07-27", + release_date = "2023-08-02", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", @@ -1384,12 +1384,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Bazel rust rules", project_desc = "Bazel rust rules (used by Wasm)", project_url = "https://github.com/bazelbuild/rules_rust", - version = "0.25.1", - sha256 = "4a9cb4fda6ccd5b5ec393b2e944822a62e050c7c06f1ea41607f14c4fdec57a2", + version = "0.26.0", + sha256 = "9d04e658878d23f4b00163a72da3db03ddb451273eb347df7d7c50838d698f49", urls = ["https://github.com/bazelbuild/rules_rust/releases/download/{version}/rules_rust-v{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.wasm.runtime.wasmtime"], - release_date = "2023-07-05", + release_date = "2023-07-28", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/bazelbuild/rules_rust/blob/{version}/LICENSE.txt", @@ -1460,7 +1460,7 @@ def _compiled_protoc_deps(locations, versions): sha256 = sha, urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v{version}/protoc-{version}-%s.zip" % platform.replace("_", "-", 1)], use_category = ["dataplane_core", "controlplane"], - release_date = "2023-05-17", + release_date = "2023-07-06", cpe = "N/A", license = "Protocol Buffers", license_url = "https://github.com/protocolbuffers/protobuf/blob/v{version}/LICENSE", diff --git a/bazel/rules_java.patch b/bazel/rules_java.patch new file mode 100644 index 000000000000..91bd69eb69fa --- /dev/null +++ b/bazel/rules_java.patch @@ -0,0 +1,293 @@ +diff --git a/java/repositories.bzl b/java/repositories.bzl +index 7e5b939..e8d10b3 100644 +--- a/java/repositories.bzl ++++ b/java/repositories.bzl +@@ -88,7 +88,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_linux_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +@@ -103,7 +103,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_linux_s390x", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:s390x", + ], +@@ -117,7 +117,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_linux", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +@@ -132,7 +132,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_macos_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], +@@ -146,7 +146,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_macos", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], +@@ -161,7 +161,7 @@ def remote_jdk8_repos(name = ""): + maybe( + remote_java_repository, + name = "remote_jdk8_windows", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +@@ -189,7 +189,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_linux", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +@@ -205,7 +205,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_linux_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +@@ -221,7 +221,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_linux_ppc64le", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:ppc", + ], +@@ -237,7 +237,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_linux_s390x", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:s390x", + ], +@@ -253,7 +253,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_macos", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], +@@ -269,7 +269,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_macos_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], +@@ -285,7 +285,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_win", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +@@ -301,7 +301,7 @@ def remote_jdk11_repos(): + maybe( + remote_java_repository, + name = "remotejdk11_win_arm64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:arm64", + ], +@@ -318,7 +318,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_linux", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +@@ -334,7 +334,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_linux_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +@@ -350,7 +350,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_linux_s390x", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:s390x", + ], +@@ -366,7 +366,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_linux_ppc64le", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:ppc", + ], +@@ -382,7 +382,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_macos", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], +@@ -398,7 +398,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_macos_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], +@@ -413,7 +413,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_win", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +@@ -428,7 +428,7 @@ def remote_jdk17_repos(): + maybe( + remote_java_repository, + name = "remotejdk17_win_arm64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:arm64", + ], +@@ -446,7 +446,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_linux", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +@@ -462,7 +462,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_linux_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +@@ -478,7 +478,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_macos", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], +@@ -494,7 +494,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_macos_aarch64", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], +@@ -509,7 +509,7 @@ def remote_jdk20_repos(): + maybe( + remote_java_repository, + name = "remotejdk20_win", +- target_compatible_with = [ ++ exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +diff --git a/toolchains/remote_java_repository.bzl b/toolchains/remote_java_repository.bzl +index 86916ec..5521fcf 100644 +--- a/toolchains/remote_java_repository.bzl ++++ b/toolchains/remote_java_repository.bzl +@@ -32,20 +32,20 @@ _toolchain_config = repository_rule( + }, + ) + +-def remote_java_repository(name, version, target_compatible_with = None, prefix = "remotejdk", **kwargs): ++def remote_java_repository(name, version, exec_compatible_with = None, prefix = "remotejdk", **kwargs): + """Imports a JDK from a http archive and creates runtime toolchain definitions for it. + + Register the toolchains defined by this macro via `register_toolchains("@//:all")`, where + `` is the value of the `name` parameter. + +- Toolchain resolution is determined with target_compatible_with ++ Toolchain resolution is determined with exec_compatible_with + parameter and constrained with --java_runtime_version flag either having value + of "version" or "{prefix}_{version}" parameters. + + Args: + name: A unique name for this rule. + version: Version of the JDK imported. +- target_compatible_with: Target platform constraints (CPU and OS) for this JDK. ++ exec_compatible_with: Target platform constraints (CPU and OS) for this JDK. + prefix: Optional alternative prefix for configuration flag value used to determine this JDK. + **kwargs: Refer to http_archive documentation + """ +@@ -77,7 +77,7 @@ alias( + ) + toolchain( + name = "toolchain", +- target_compatible_with = {target_compatible_with}, ++ exec_compatible_with = {exec_compatible_with}, + target_settings = [":version_or_prefix_version_setting"], + toolchain_type = "@bazel_tools//tools/jdk:runtime_toolchain_type", + toolchain = "{toolchain}", +@@ -85,7 +85,7 @@ toolchain( + """.format( + prefix = prefix, + version = version, +- target_compatible_with = target_compatible_with, ++ exec_compatible_with = exec_compatible_with, + toolchain = "@{repo}//:jdk".format(repo = name), + ), + ) diff --git a/bazel/toolchains/BUILD b/bazel/toolchains/BUILD deleted file mode 100644 index e6a683365028..000000000000 --- a/bazel/toolchains/BUILD +++ /dev/null @@ -1,17 +0,0 @@ -licenses(["notice"]) # Apache 2 - -platform( - name = "rbe_ubuntu_clang_platform", - parents = ["@rbe_ubuntu_clang//config:platform"], - remote_execution_properties = """ - {PARENT_REMOTE_EXECUTION_PROPERTIES} - properties: { - name: "dockerAddCapabilities" - value: "SYS_PTRACE,NET_RAW,NET_ADMIN" - } - properties: { - name: "dockerNetwork" - value: "standard" - } - """, -) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9433f898361f..15becc6de9d8 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -2,11 +2,37 @@ date: Pending behavior_changes: # *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +- area: eds + change: | + Introduced caching of EDS assignments when used with ADS. Prior to this change, Envoy required that EDS assignments were sent + after an EDS cluster was updated. If no EDS assignment was received for the cluster, it ended up with an empty assignment. + Following this change, after a cluster update, Envoy waits for an EDS assignment until + :ref:`initial_fetch_timeout ` times out, and will then apply + the cached assignment and finish updating the warmed cluster. This change is disabled by default, and can be enabled by setting + the runtime flag ``envoy.restart_features.use_eds_cache_for_ads`` to true. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: outlier detection + change: | + Outlier detection will always respect max_ejection_percent now. + This behavioral change can be reverted by setting runtime guard + ``envoy.reloadable_features.check_mep_on_first_eject`` to false. +- area: quic + change: | + Enable QUICHE request and response headers validation. This behavior can be reverted by setting runtime flag + ``envoy.reloadable_features.FLAGS_envoy_quic_reloadable_flag_quic_act_upon_invalid_header`` to false. bug_fixes: +- area: connection limit + change: | + fixed a use-after-free bug in the connection limit filter. +- area: subset load balancer + change: | + Fixed a bug where + :ref:`overprovisioning_factor` and + :ref:`weighted_priority_health ` + values were not respected when subset load balacing was enabled. The default values of 140 and false were always used. - area: tracers change: | use unary RPC calls for OpenTelemetry trace exports, rather than client-side streaming connections @@ -15,8 +41,32 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: access_log + change: | + added %RESPONSE_FLAGS_LONG% substitution string, that will output a pascal case string representing the resonse flags. + The output response flags will correspond with %RESPONSE_FLAGS%, only with a long textual string representation. +- area: config + change: | + Added the capability to defer broadcasting of certain cluster (CDS, EDS) to + worker threads from the main thread. This optimization can save significant + amount of memory in cases where there are (1) a large number of workers and + (2) a large amount of config, most of which is unused. This capability is + guarded by :ref:`enable_deferred_cluster_creation + `. - area: extension_discovery_service change: | added ECDS support for :ref:` downstream network filters`. +- area: http + change: | + added :ref:`Json-To-Metadata filter `. +- area: extension_discovery_service + change: | + added metric listener.listener_stat.network_extension_config_missing to track closed connections due to missing config. +- area: redis + change: | + added support for time command (returns a local response). +- area: redis + change: | + added support for lmove command. deprecated: diff --git a/ci/build_setup.sh b/ci/build_setup.sh index f4a94398f1bf..811371612d4e 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -134,7 +134,6 @@ BAZEL_BUILD_OPTIONS=( "${BAZEL_GLOBAL_OPTIONS[@]}" "--verbose_failures" "--experimental_generate_json_trace_profile" - "--test_output=errors" "--action_env=CLANG_FORMAT" "${BAZEL_BUILD_EXTRA_OPTIONS[@]}" "${BAZEL_EXTRA_TEST_OPTIONS[@]}") diff --git a/ci/do_ci.sh b/ci/do_ci.sh index b0a48d6b8aaa..4ad6f70f47ce 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -19,6 +19,18 @@ echo "building for ${ENVOY_BUILD_ARCH}" cd "${SRCDIR}" +FETCH_TARGETS=( + //contrib/... + //distribution/... + //docs/... + //source/... + //test/... + //tools/... + @nodejs//... + @envoy_api//... + @envoy_build_tools//...) + + if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then BUILD_ARCH_DIR="/linux/amd64" elif [[ "${ENVOY_BUILD_ARCH}" == "aarch64" ]]; then @@ -325,26 +337,16 @@ case $CI_TARGET in } ;; + clean|expunge) + setup_clang_toolchain + if [[ "$CI_TARGET" == "expunge" ]]; then + CLEAN_ARGS+=(--expunge) + fi + bazel clean "${BAZEL_GLOBAL_OPTIONS[@]}" "${CLEAN_ARGS[@]}" + ;; + compile_time_options) - # Right now, none of the available compile-time options conflict with each other. If this - # changes, this build type may need to be broken up. - COMPILE_TIME_OPTIONS=( - "--define" "admin_html=disabled" - "--define" "signal_trace=disabled" - "--define" "hot_restart=disabled" - "--define" "google_grpc=disabled" - "--define" "boringssl=fips" - "--define" "log_debug_assert_in_release=enabled" - "--define" "path_normalization_by_default=true" - "--define" "deprecated_features=disabled" - "--define" "tcmalloc=gperftools" - "--define" "zlib=ng" - "--define" "uhv=enabled" - "--@envoy//bazel:http3=False" - "--@envoy//source/extensions/filters/http/kill_request:enabled" - "--test_env=ENVOY_HAS_EXTRA_EXTENSIONS=true" - "--remote_download_minimal" - "--config=libc++20") + # See `compile-time-options` in `.bazelrc` setup_clang_toolchain # This doesn't go into CI but is available for developer convenience. echo "bazel with different compiletime options build with tests..." @@ -354,8 +356,8 @@ case $CI_TARGET in echo "Building and testing with wasm=wamr: ${TEST_TARGETS[*]}" bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wamr \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c fastbuild \ "${TEST_TARGETS[@]}" \ --test_tag_filters=-nofips \ @@ -363,10 +365,9 @@ case $CI_TARGET in echo "Building and testing with wasm=wasmtime: and admin_functionality and admin_html disabled ${TEST_TARGETS[*]}" bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wasmtime \ - --define admin_html=disabled \ --define admin_functionality=disabled \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c fastbuild \ "${TEST_TARGETS[@]}" \ --test_tag_filters=-nofips \ @@ -374,8 +375,8 @@ case $CI_TARGET in echo "Building and testing with wasm=wavm: ${TEST_TARGETS[*]}" bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wavm \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c fastbuild \ "${TEST_TARGETS[@]}" \ --test_tag_filters=-nofips \ @@ -384,28 +385,28 @@ case $CI_TARGET in # these tests under "-c opt" to save time in CI. bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wavm \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c opt \ @envoy//test/common/common:assert_test \ @envoy//test/server:server_test # "--define log_fast_debug_assert_in_release=enabled" must be tested with a release build, so run only these tests under "-c opt" to save time in CI. This option will test only ASSERT()s without SLOW_ASSERT()s, so additionally disable "--define log_debug_assert_in_release" which compiles in both. bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ + --config=compile-time-options \ --define wasm=wavm \ - "${COMPILE_TIME_OPTIONS[@]}" \ -c opt \ @envoy//test/common/common:assert_test \ --define log_fast_debug_assert_in_release=enabled \ --define log_debug_assert_in_release=disabled echo "Building binary with wasm=wavm... and logging disabled" bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ - --define wasm=wavm \ - --define enable_logging=disabled \ - "${COMPILE_TIME_OPTIONS[@]}" \ - -c fastbuild \ - @envoy//source/exe:envoy-static \ - --build_tag_filters=-nofips + --config=compile-time-options \ + --define wasm=wavm \ + --define enable_logging=disabled \ + -c fastbuild \ + @envoy//source/exe:envoy-static \ + --build_tag_filters=-nofips collect_build_profile build ;; @@ -560,10 +561,32 @@ case $CI_TARGET in dockerhub-readme) setup_clang_toolchain bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ + --remote_download_toplevel \ //distribution/dockerhub:readme cat bazel-bin/distribution/dockerhub/readme.md ;; + fetch) + setup_clang_toolchain + echo "Fetching ${FETCH_TARGETS[*]} ..." + FETCH_ARGS=( + --noshow_progress + --noshow_loading_progress) + # TODO(phlax): separate out retry logic + n=0 + until [ "$n" -ge 10 ]; do + bazel fetch "${BAZEL_GLOBAL_OPTIONS[@]}" \ + "${FETCH_ARGS[@]}" \ + "${FETCH_TARGETS[@]}" \ + && break + n=$((n+1)) + if [[ "$n" -ne 10 ]]; then + sleep 15 + echo "Retrying fetch ..." + fi + done + ;; + fix_proto_format) # proto_format.sh needs to build protobuf. setup_clang_toolchain @@ -600,6 +623,11 @@ case $CI_TARGET in bazel_envoy_binary_build fastbuild ;; + info) + setup_clang_toolchain + bazel info "${BAZEL_GLOBAL_OPTIONS[@]}" + ;; + msan) ENVOY_STDLIB=libc++ setup_clang_toolchain @@ -662,7 +690,9 @@ case $CI_TARGET in "${BAZEL_RELEASE_OPTIONS[@]}" \ "${TEST_TARGETS[@]}" # Build release binaries - bazel build "${BAZEL_BUILD_OPTIONS[@]}" "${BAZEL_RELEASE_OPTIONS[@]}" \ + bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ + "${BAZEL_RELEASE_OPTIONS[@]}" \ + --remote_download_outputs=toplevel \ //distribution/binary:release # Copy release binaries to binary export directory cp -a \ @@ -737,6 +767,8 @@ case $CI_TARGET in ;; verify_distro) + # this can be required if any python deps require compilation + setup_clang_toolchain if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then PACKAGE_BUILD=/build/bazel.distribution/x64/packages.x64.tar.gz else diff --git a/ci/filter_example_setup.sh b/ci/filter_example_setup.sh index f0605cadb236..94511ac5babe 100644 --- a/ci/filter_example_setup.sh +++ b/ci/filter_example_setup.sh @@ -16,10 +16,10 @@ ENVOY_FILTER_EXAMPLE_TESTS=( if [[ ! -d "${ENVOY_FILTER_EXAMPLE_SRCDIR}/.git" ]]; then rm -rf "${ENVOY_FILTER_EXAMPLE_SRCDIR}" - git clone https://github.com/envoyproxy/envoy-filter-example.git "${ENVOY_FILTER_EXAMPLE_SRCDIR}" + git clone -q https://github.com/envoyproxy/envoy-filter-example.git "${ENVOY_FILTER_EXAMPLE_SRCDIR}" fi -(cd "${ENVOY_FILTER_EXAMPLE_SRCDIR}" && git fetch origin && git checkout -f "${ENVOY_FILTER_EXAMPLE_GITSHA}") +(cd "${ENVOY_FILTER_EXAMPLE_SRCDIR}" && git fetch -q origin && git checkout -q -f "${ENVOY_FILTER_EXAMPLE_GITSHA}") sed -e "s|{ENVOY_SRCDIR}|${ENVOY_SRCDIR}|" "${ENVOY_SRCDIR}"/ci/WORKSPACE.filter.example > "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/WORKSPACE mkdir -p "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/bazel diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index a158d851b587..e42f4c6ba7f8 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -63,6 +63,12 @@ else && sudo -EHs -u envoybuild bash -c 'cd /source && $*'") fi +if [[ -n "$ENVOY_DOCKER_PLATFORM" ]]; then + echo "Setting Docker platform: ${ENVOY_DOCKER_PLATFORM}" + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + ENVOY_DOCKER_OPTIONS+=(--platform "$ENVOY_DOCKER_PLATFORM") +fi + # The IMAGE_ID defaults to the CI hash but can be set to an arbitrary image ID (found with 'docker # images'). [[ -z "${IMAGE_ID}" ]] && IMAGE_ID="${ENVOY_BUILD_SHA}" diff --git a/ci/setup_cache.sh b/ci/setup_cache.sh index 6c770323eb35..da0b189dd4a8 100755 --- a/ci/setup_cache.sh +++ b/ci/setup_cache.sh @@ -19,7 +19,7 @@ if [[ -n "${GCP_SERVICE_ACCOUNT_KEY:0:1}" ]]; then export BAZEL_BUILD_EXTRA_OPTIONS+=" --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_FILE}" if [[ -n "${GOOGLE_BES_PROJECT_ID}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --config=google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" + export BAZEL_BUILD_EXTRA_OPTIONS+=" --config=rbe-google-bes --bes_instance_name=${GOOGLE_BES_PROJECT_ID}" fi fi @@ -29,10 +29,14 @@ if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then echo "Set up bazel remote read/write cache at ${BAZEL_REMOTE_CACHE}." if [[ -z "${ENVOY_RBE}" ]]; then - export BAZEL_BUILD_EXTRA_OPTIONS+=" --jobs=HOST_CPUS*.99 --remote_timeout=600" + export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_timeout=600" echo "using local build cache." # Normalize branches - `release/vX.xx`, `vX.xx`, `vX.xx.x` -> `vX.xx` - BRANCH_NAME="$(echo "${CI_TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" + TARGET_BRANCH="${CI_TARGET_BRANCH}" + if [[ "$TARGET_BRANCH" =~ ^origin/ ]]; then + TARGET_BRANCH=$(echo "$TARGET_BRANCH" | cut -d/ -f2-) + fi + BRANCH_NAME="$(echo "${TARGET_BRANCH}" | cut -d/ -f2 | cut -d. -f-2)" if [[ "$BRANCH_NAME" == "merge" ]]; then # Manually run PR commit - there is no easy way of telling which branch # it is, so just set it to `main` - otherwise it tries to cache as `branch/merge` @@ -45,6 +49,4 @@ if [[ -n "${BAZEL_REMOTE_CACHE}" ]]; then export BAZEL_BUILD_EXTRA_OPTIONS+=" --remote_instance_name=${BAZEL_REMOTE_INSTANCE}" echo "instance_name: ${BAZEL_REMOTE_INSTANCE}." fi -else - echo "No remote cache is set, skipping setup remote cache." fi diff --git a/contrib/generic_proxy/filters/network/source/BUILD b/contrib/generic_proxy/filters/network/source/BUILD index 457ce37b73c3..db06f8a9e373 100644 --- a/contrib/generic_proxy/filters/network/source/BUILD +++ b/contrib/generic_proxy/filters/network/source/BUILD @@ -20,14 +20,17 @@ envoy_cc_library( deps = [ ":rds_lib", ":route_lib", + ":stats_lib", ":upstream_lib", "//contrib/generic_proxy/filters/network/source/interface:codec_interface", "//contrib/generic_proxy/filters/network/source/interface:proxy_config_interface", "//contrib/generic_proxy/filters/network/source/router:router_lib", "//envoy/network:filter_interface", "//envoy/server:factory_context_interface", + "//envoy/stats:timespan_interface", "//source/common/common:linked_object", "//source/common/common:minimal_logger_lib", + "//source/common/stats:timespan_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:tracer_config_lib", "//source/common/tracing:tracer_lib", @@ -131,3 +134,12 @@ envoy_cc_library( "//envoy/upstream:thread_local_cluster_interface", ], ) + +envoy_cc_library( + name = "stats_lib", + hdrs = ["stats.h"], + deps = [ + "//envoy/stats:stats_interface", + "//envoy/stats:stats_macros", + ], +) diff --git a/contrib/generic_proxy/filters/network/source/interface/proxy_config.h b/contrib/generic_proxy/filters/network/source/interface/proxy_config.h index 6fd666e7f0e8..e3c4b6b2ad9e 100644 --- a/contrib/generic_proxy/filters/network/source/interface/proxy_config.h +++ b/contrib/generic_proxy/filters/network/source/interface/proxy_config.h @@ -6,6 +6,7 @@ #include "contrib/generic_proxy/filters/network/source/interface/codec.h" #include "contrib/generic_proxy/filters/network/source/interface/filter.h" #include "contrib/generic_proxy/filters/network/source/interface/route.h" +#include "contrib/generic_proxy/filters/network/source/stats.h" namespace Envoy { namespace Extensions { @@ -45,6 +46,11 @@ class FilterConfig : public FilterChainFactory { * @return connection manager tracing config. */ virtual OptRef tracingConfig() const PURE; + + /** + * @return stats to use. + */ + virtual GenericFilterStats& stats() PURE; }; } // namespace GenericProxy diff --git a/contrib/generic_proxy/filters/network/source/proxy.cc b/contrib/generic_proxy/filters/network/source/proxy.cc index 7925d27b54a5..554a088aa2b2 100644 --- a/contrib/generic_proxy/filters/network/source/proxy.cc +++ b/contrib/generic_proxy/filters/network/source/proxy.cc @@ -37,7 +37,11 @@ ActiveStream::ActiveStream(Filter& parent, RequestPtr request, ExtendedOptions o : parent_(parent), downstream_request_stream_(std::move(request)), downstream_request_options_(options), stream_info_(parent_.time_source_, - parent_.callbacks_->connection().connectionInfoProviderSharedPtr()) { + parent_.callbacks_->connection().connectionInfoProviderSharedPtr()), + request_timer_(new Stats::HistogramCompletableTimespanImpl(parent_.stats_.request_time_ms_, + parent_.time_source_)) { + parent_.stats_.request_.inc(); + parent_.stats_.request_active_.inc(); connection_manager_tracing_config_ = parent_.config_->tracingConfig(); @@ -165,6 +169,7 @@ void ActiveStream::continueEncoding() { void ActiveStream::onEncodingSuccess(Buffer::Instance& buffer) { ASSERT(parent_.connection().state() == Network::Connection::State::Open); parent_.connection().write(buffer, false); + parent_.stats_.response_.inc(); parent_.deferredStream(*this); } @@ -178,6 +183,9 @@ void ActiveStream::initializeFilterChain(FilterChainFactory& factory) { void ActiveStream::completeRequest() { stream_info_.onRequestComplete(); + request_timer_->complete(); + parent_.stats_.request_active_.dec(); + if (active_span_) { Tracing::TracerUtility::finalizeSpan(*active_span_, *downstream_request_stream_, stream_info_, *this, false); @@ -318,6 +326,8 @@ void UpstreamManagerImpl::onDecodingFailure() { ENVOY_LOG(error, "generic proxy bound upstream manager: decoding failure"); + parent_.stats_.response_decoding_error_.inc(); + while (!registered_response_callbacks_.empty()) { auto it = registered_response_callbacks_.begin(); auto cb = it->second; @@ -355,6 +365,8 @@ void Filter::onDecodingSuccess(RequestPtr request, ExtendedOptions options) { } void Filter::onDecodingFailure() { + stats_.request_decoding_error_.inc(); + resetStreamsForUnexpectedError(); closeDownstreamConnection(); } diff --git a/contrib/generic_proxy/filters/network/source/proxy.h b/contrib/generic_proxy/filters/network/source/proxy.h index 964815908dc3..9e9e250628d1 100644 --- a/contrib/generic_proxy/filters/network/source/proxy.h +++ b/contrib/generic_proxy/filters/network/source/proxy.h @@ -8,11 +8,13 @@ #include "envoy/network/connection.h" #include "envoy/network/filter.h" #include "envoy/server/factory_context.h" +#include "envoy/stats/timespan.h" #include "envoy/tracing/tracer_manager.h" #include "source/common/buffer/buffer_impl.h" #include "source/common/common/linked_object.h" #include "source/common/common/logger.h" +#include "source/common/stats/timespan_impl.h" #include "source/common/stream_info/stream_info_impl.h" #include "source/common/tracing/tracer_config_impl.h" #include "source/common/tracing/tracer_impl.h" @@ -27,7 +29,9 @@ #include "contrib/generic_proxy/filters/network/source/rds.h" #include "contrib/generic_proxy/filters/network/source/rds_impl.h" #include "contrib/generic_proxy/filters/network/source/route.h" +#include "contrib/generic_proxy/filters/network/source/stats.h" #include "contrib/generic_proxy/filters/network/source/upstream.h" +#include "stats.h" namespace Envoy { namespace Extensions { @@ -53,10 +57,12 @@ class FilterConfigImpl : public FilterConfig { std::vector factories, Tracing::TracerSharedPtr tracer, Tracing::ConnectionManagerTracingConfigPtr tracing_config, Envoy::Server::Configuration::FactoryContext& context) - : stat_prefix_(stat_prefix), codec_factory_(std::move(codec)), - route_config_provider_(std::move(route_config_provider)), factories_(std::move(factories)), - drain_decision_(context.drainDecision()), tracer_(std::move(tracer)), - tracing_config_(std::move(tracing_config)) {} + : stat_prefix_(stat_prefix), + stats_(GenericFilterStats::generateStats(stat_prefix_, context.scope())), + codec_factory_(std::move(codec)), route_config_provider_(std::move(route_config_provider)), + factories_(std::move(factories)), drain_decision_(context.drainDecision()), + tracer_(std::move(tracer)), tracing_config_(std::move(tracing_config)), + time_source_(context.timeSource()) {} // FilterConfig RouteEntryConstSharedPtr routeEntry(const Request& request) const override { @@ -72,6 +78,8 @@ class FilterConfigImpl : public FilterConfig { return makeOptRefFromPtr(tracing_config_.get()); } + GenericFilterStats& stats() override { return stats_; } + // FilterChainFactory void createFilterChain(FilterChainManager& manager) override { for (auto& factory : factories_) { @@ -84,6 +92,7 @@ class FilterConfigImpl : public FilterConfig { friend class Filter; const std::string stat_prefix_; + GenericFilterStats stats_; CodecFactoryPtr codec_factory_; @@ -95,6 +104,8 @@ class FilterConfigImpl : public FilterConfig { Tracing::TracerSharedPtr tracer_; Tracing::ConnectionManagerTracingConfigPtr tracing_config_; + + TimeSource& time_source_; }; class ActiveStream : public FilterChainManager, @@ -287,6 +298,8 @@ class ActiveStream : public FilterChainManager, StreamInfo::StreamInfoImpl stream_info_; + Stats::TimespanPtr request_timer_; + OptRef connection_manager_tracing_config_; Tracing::SpanPtr active_span_; }; @@ -327,8 +340,8 @@ class Filter : public Envoy::Network::ReadFilter, public RequestDecoderCallback { public: Filter(FilterConfigSharedPtr config, TimeSource& time_source, Runtime::Loader& runtime) - : config_(std::move(config)), drain_decision_(config_->drainDecision()), - time_source_(time_source), runtime_(runtime) { + : config_(std::move(config)), stats_(config_->stats()), + drain_decision_(config_->drainDecision()), time_source_(time_source), runtime_(runtime) { request_decoder_ = config_->codecFactory().requestDecoder(); request_decoder_->setDecoderCallback(*this); response_encoder_ = config_->codecFactory().responseEncoder(); @@ -426,6 +439,7 @@ class Filter : public Envoy::Network::ReadFilter, bool downstream_connection_closed_{}; FilterConfigSharedPtr config_{}; + GenericFilterStats& stats_; const Network::DrainDecision& drain_decision_; bool stream_drain_decision_{}; TimeSource& time_source_; diff --git a/contrib/generic_proxy/filters/network/source/stats.h b/contrib/generic_proxy/filters/network/source/stats.h new file mode 100644 index 000000000000..ef324098c324 --- /dev/null +++ b/contrib/generic_proxy/filters/network/source/stats.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace GenericProxy { + +/** + * All generic filter stats. @see stats_macros.h + */ +#define ALL_GENERIC_FILTER_STATS(COUNTER, GAUGE, HISTOGRAM) \ + COUNTER(request) \ + COUNTER(request_decoding_error) \ + COUNTER(response) \ + COUNTER(response_decoding_error) \ + GAUGE(request_active, Accumulate) \ + HISTOGRAM(request_time_ms, Milliseconds) + +/** + * Struct definition for all generic proxy stats. @see stats_macros.h + */ +struct GenericFilterStats { + ALL_GENERIC_FILTER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, + GENERATE_HISTOGRAM_STRUCT) + + static GenericFilterStats generateStats(const std::string& prefix, Stats::Scope& scope) { + return GenericFilterStats{ALL_GENERIC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, prefix), + POOL_GAUGE_PREFIX(scope, prefix), + POOL_HISTOGRAM_PREFIX(scope, prefix))}; + } +}; + +} // namespace GenericProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/generic_proxy/filters/network/test/integration_test.cc b/contrib/generic_proxy/filters/network/test/integration_test.cc index 60473b74700f..fdca833c741d 100644 --- a/contrib/generic_proxy/filters/network/test/integration_test.cc +++ b/contrib/generic_proxy/filters/network/test/integration_test.cc @@ -22,6 +22,14 @@ namespace NetworkFilters { namespace GenericProxy { namespace { +class GenericProxyIntegrationTest : public BaseIntegrationTest { +public: + GenericProxyIntegrationTest(const std::string config_yaml) + : BaseIntegrationTest(Network::Address::IpVersion::v4, config_yaml) { + skip_tag_extraction_rule_check_ = true; + }; +}; + class IntegrationTest : public testing::TestWithParam { public: struct ConnectionCallbacks : public Network::ConnectionCallbacks { @@ -99,8 +107,7 @@ class IntegrationTest : public testing::TestWithParam; void initialize(const std::string& config_yaml, CodecFactoryPtr codec_factory) { - integration_ = - std::make_unique(Network::Address::IpVersion::v4, config_yaml); + integration_ = std::make_unique(config_yaml); integration_->initialize(); // Create codec for downstream client. @@ -256,7 +263,7 @@ class IntegrationTest : public testing::TestWithParam integration_; + std::unique_ptr integration_; // Callbacks for downstream connection. ConnectionCallbacksSharedPtr connection_callbacks_; diff --git a/contrib/generic_proxy/filters/network/test/proxy_test.cc b/contrib/generic_proxy/filters/network/test/proxy_test.cc index 4020b27542c7..d4d0f6130360 100644 --- a/contrib/generic_proxy/filters/network/test/proxy_test.cc +++ b/contrib/generic_proxy/filters/network/test/proxy_test.cc @@ -1289,6 +1289,23 @@ TEST_F(FilterTest, BindUpstreamConnectionSuccessAndWriteSomethinToConnection) { response_decoder_callback->onDecodingFailure(); } +TEST_F(FilterTest, TestStats) { + initializeFilter(false, true); + auto request = std::make_unique(); + + filter_->newDownstreamRequest(std::move(request), ExtendedOptions(123, true, false, false)); + EXPECT_EQ(1, filter_->activeStreamsForTest().size()); + EXPECT_EQ(1, filter_config_->stats().request_.value()); + EXPECT_EQ(1, filter_config_->stats().request_active_.value()); + + auto active_stream = filter_->activeStreamsForTest().begin()->get(); + Buffer::OwnedImpl buffer; + buffer.add("123"); + active_stream->onEncodingSuccess(buffer); + EXPECT_EQ(1, filter_config_->stats().response_.value()); + EXPECT_EQ(0, filter_config_->stats().request_active_.value()); +} + } // namespace } // namespace GenericProxy } // namespace NetworkFilters diff --git a/contrib/golang/common/go/api/BUILD b/contrib/golang/common/go/api/BUILD index f77587290be1..2a9245ed571d 100644 --- a/contrib/golang/common/go/api/BUILD +++ b/contrib/golang/common/go/api/BUILD @@ -1,15 +1,36 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) licenses(["notice"]) # Apache 2 go_library( name = "api", srcs = [ + "api.h", "capi.go", "cgocheck.go", "filter.go", + "logger.go", "type.go", ], + cgo = True, + clinkopts = select({ + "@io_bazel_rules_go//go/platform:android": [ + "-Wl,-unresolved-symbols=ignore-all", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "-Wl,-undefined,dynamic_lookup", + ], + "@io_bazel_rules_go//go/platform:ios": [ + "-Wl,-undefined,dynamic_lookup", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "-Wl,-unresolved-symbols=ignore-all", + ], + "//conditions:default": [], + }), importpath = "github.com/envoyproxy/envoy/contrib/golang/common/go/api", visibility = ["//visibility:public"], deps = [ diff --git a/contrib/golang/common/go/api/api.h b/contrib/golang/common/go/api/api.h index 126beb21714f..2a4484fd3144 100644 --- a/contrib/golang/common/go/api/api.h +++ b/contrib/golang/common/go/api/api.h @@ -65,8 +65,8 @@ CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, void* value); CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name, void* hand); CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name, void* key, void* buf); -void envoyGoFilterHttpLog(uint32_t level, void* message); -uint32_t envoyGoFilterHttpLogLevel(); +void envoyGoFilterLog(uint32_t level, void* message); +uint32_t envoyGoFilterLogLevel(); void envoyGoFilterHttpFinalize(void* r, int reason); diff --git a/contrib/golang/common/go/api/logger.go b/contrib/golang/common/go/api/logger.go new file mode 100644 index 000000000000..03dc81d9668a --- /dev/null +++ b/contrib/golang/common/go/api/logger.go @@ -0,0 +1,91 @@ +/* + * 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. + */ + +package api + +/* +// ref https://github.com/golang/go/issues/25832 + +#cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all +#cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup + +#include +#include + +#include "api.h" + +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +// The default log format is: +// [2023-08-09 03:04:15.985][1390][critical][golang] [contrib/golang/common/log/cgo.cc:27] msg + +func LogTrace(message string) { + C.envoyGoFilterLog(C.uint32_t(Trace), unsafe.Pointer(&message)) +} + +func LogDebug(message string) { + C.envoyGoFilterLog(C.uint32_t(Debug), unsafe.Pointer(&message)) +} + +func LogInfo(message string) { + C.envoyGoFilterLog(C.uint32_t(Info), unsafe.Pointer(&message)) +} + +func LogWarn(message string) { + C.envoyGoFilterLog(C.uint32_t(Warn), unsafe.Pointer(&message)) +} + +func LogError(message string) { + C.envoyGoFilterLog(C.uint32_t(Error), unsafe.Pointer(&message)) +} + +func LogCritical(message string) { + C.envoyGoFilterLog(C.uint32_t(Critical), unsafe.Pointer(&message)) +} + +func LogTracef(format string, v ...any) { + LogTrace(fmt.Sprintf(format, v...)) +} + +func LogDebugf(format string, v ...any) { + LogDebug(fmt.Sprintf(format, v...)) +} + +func LogInfof(format string, v ...any) { + LogInfo(fmt.Sprintf(format, v...)) +} + +func LogWarnf(format string, v ...any) { + LogWarn(fmt.Sprintf(format, v...)) +} + +func LogErrorf(format string, v ...any) { + LogError(fmt.Sprintf(format, v...)) +} + +func LogCriticalf(format string, v ...any) { + LogCritical(fmt.Sprintf(format, v...)) +} + +func GetLogLevel() LogType { + return LogType(C.envoyGoFilterLogLevel()) +} diff --git a/contrib/golang/common/log/BUILD b/contrib/golang/common/log/BUILD new file mode 100644 index 000000000000..afd36321b4b2 --- /dev/null +++ b/contrib/golang/common/log/BUILD @@ -0,0 +1,22 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "log_lib", + srcs = ["cgo.cc"], + hdrs = [ + "cgo.h", + ], + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/dso:dso_lib", + "//source/common/common:utility_lib", + ], +) diff --git a/contrib/golang/common/log/cgo.cc b/contrib/golang/common/log/cgo.cc new file mode 100644 index 000000000000..97b26b9aeaf5 --- /dev/null +++ b/contrib/golang/common/log/cgo.cc @@ -0,0 +1,72 @@ +#include "contrib/golang/common/log/cgo.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Golang { + +/* FilterLogger */ +void FilterLogger::log(uint32_t level, absl::string_view message) const { + switch (static_cast(level)) { + case spdlog::level::trace: + ENVOY_LOG(trace, "{}", message); + return; + case spdlog::level::debug: + ENVOY_LOG(debug, "{}", message); + return; + case spdlog::level::info: + ENVOY_LOG(info, "{}", message); + return; + case spdlog::level::warn: + ENVOY_LOG(warn, "{}", message); + return; + case spdlog::level::err: + ENVOY_LOG(error, "{}", message); + return; + case spdlog::level::critical: + ENVOY_LOG(critical, "{}", message); + return; + case spdlog::level::off: + // means not logging + return; + case spdlog::level::n_levels: + PANIC("not implemented"); + } + + ENVOY_LOG(error, "undefined log level {} with message '{}'", level, message); + + PANIC_DUE_TO_CORRUPT_ENUM; +} + +uint32_t FilterLogger::level() const { return static_cast(ENVOY_LOGGER().level()); } + +const FilterLogger& getFilterLogger() { CONSTRUCT_ON_FIRST_USE(FilterLogger); } + +// The returned absl::string_view only refer to the GoString, won't copy the string content into +// C++, should not use it after the current cgo call returns. +absl::string_view referGoString(void* str) { + if (str == nullptr) { + return ""; + } + auto go_str = reinterpret_cast(str); + return {go_str->p, static_cast(go_str->n)}; +} + +#ifdef __cplusplus +extern "C" { +#endif + +void envoyGoFilterLog(uint32_t level, void* message) { + auto mesg = referGoString(message); + getFilterLogger().log(level, mesg); +} + +uint32_t envoyGoFilterLogLevel() { return getFilterLogger().level(); } + +#ifdef __cplusplus +} +#endif +} // namespace Golang +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/golang/common/log/cgo.h b/contrib/golang/common/log/cgo.h new file mode 100644 index 000000000000..1ec4e8713301 --- /dev/null +++ b/contrib/golang/common/log/cgo.h @@ -0,0 +1,23 @@ +#pragma once + +#include "source/common/common/utility.h" + +#include "contrib/golang/common/dso/dso.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Golang { + +class FilterLogger : Logger::Loggable { +public: + FilterLogger() = default; + + void log(uint32_t level, absl::string_view message) const; + uint32_t level() const; +}; + +} // namespace Golang +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/golang/filters/http/source/BUILD b/contrib/golang/filters/http/source/BUILD index 5b89151eb067..35c187b84911 100644 --- a/contrib/golang/filters/http/source/BUILD +++ b/contrib/golang/filters/http/source/BUILD @@ -66,6 +66,7 @@ envoy_cc_library( ], deps = [ "//contrib/golang/common/dso:dso_lib", + "//contrib/golang/common/log:log_lib", "//envoy/http:codes_interface", "//envoy/http:filter_interface", "//source/common/buffer:watermark_buffer_lib", diff --git a/contrib/golang/filters/http/source/cgo.cc b/contrib/golang/filters/http/source/cgo.cc index 8cf85a9fd2a4..45c7d3f1c2cf 100644 --- a/contrib/golang/filters/http/source/cgo.cc +++ b/contrib/golang/filters/http/source/cgo.cc @@ -58,8 +58,6 @@ std::vector stringsFromGoSlice(void* slice) { return list; } -const FilterLogger& getFilterLogger() { CONSTRUCT_ON_FIRST_USE(FilterLogger); } - #ifdef __cplusplus extern "C" { #endif @@ -198,11 +196,6 @@ CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, void* value) { }); } -void envoyGoFilterHttpLog(uint32_t level, void* message) { - auto mesg = referGoString(message); - getFilterLogger().log(level, mesg); -} - CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name, void* buf) { return envoyGoFilterHandlerWrapper(r, [name, buf](std::shared_ptr& filter) -> CAPIStatus { auto name_str = copyGoString(name); @@ -210,7 +203,6 @@ CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name, void* buf) { return filter->getDynamicMetadata(name_str, buf_slice); }); } -uint32_t envoyGoFilterHttpLogLevel() { return getFilterLogger().level(); } CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name, void* key, void* buf) { return envoyGoFilterHandlerWrapper( diff --git a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go index 6aa9dc401dc2..ebafecb871dc 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go +++ b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go @@ -279,11 +279,11 @@ func (c *httpCApiImpl) HttpSetDynamicMetadata(r unsafe.Pointer, filterName strin } func (c *httpCApiImpl) HttpLog(level api.LogType, message string) { - C.envoyGoFilterHttpLog(C.uint32_t(level), unsafe.Pointer(&message)) + C.envoyGoFilterLog(C.uint32_t(level), unsafe.Pointer(&message)) } func (c *httpCApiImpl) HttpLogLevel() api.LogType { - return api.LogType(C.envoyGoFilterHttpLogLevel()) + return api.GetLogLevel() } func (c *httpCApiImpl) HttpFinalize(r unsafe.Pointer, reason int) { diff --git a/contrib/golang/filters/http/source/go/pkg/http/filter.go b/contrib/golang/filters/http/source/go/pkg/http/filter.go index ecaccab8bf57..7fa03b5c6dca 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/filter.go +++ b/contrib/golang/filters/http/source/go/pkg/http/filter.go @@ -116,7 +116,9 @@ func (r *httpRequest) SendLocalReply(responseCode int, bodyText string, headers func (r *httpRequest) Log(level api.LogType, message string) { // TODO performance optimization points: // Add a new goroutine to write logs asynchronously and avoid frequent cgo calls - cAPI.HttpLog(level, fmt.Sprintf("[go_plugin_http][%v] %v", r.pluginName(), message)) + cAPI.HttpLog(level, fmt.Sprintf("[http][%v] %v", r.pluginName(), message)) + // The default log format is: + // [2023-08-09 03:04:16.179][1390][error][golang] [contrib/golang/common/log/cgo.cc:24] [http][plugin_name] msg } func (r *httpRequest) LogLevel() api.LogType { diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index f1b4e99b77c2..d8e87f078a37 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -1382,41 +1382,6 @@ ProcessorState& Filter::getProcessorState() { : dynamic_cast(decoding_state_); }; -/* FilterLogger */ -void FilterLogger::log(uint32_t level, absl::string_view message) const { - switch (static_cast(level)) { - case spdlog::level::trace: - ENVOY_LOG(trace, "{}", message); - return; - case spdlog::level::debug: - ENVOY_LOG(debug, "{}", message); - return; - case spdlog::level::info: - ENVOY_LOG(info, "{}", message); - return; - case spdlog::level::warn: - ENVOY_LOG(warn, "{}", message); - return; - case spdlog::level::err: - ENVOY_LOG(error, "{}", message); - return; - case spdlog::level::critical: - ENVOY_LOG(critical, "{}", message); - return; - case spdlog::level::off: - // means not logging - return; - case spdlog::level::n_levels: - PANIC("not implemented"); - } - - ENVOY_LOG(error, "undefined log level {} with message '{}'", level, message); - - PANIC_DUE_TO_CORRUPT_ENUM; -} - -uint32_t FilterLogger::level() const { return static_cast(ENVOY_LOGGER().level()); } - } // namespace Golang } // namespace HttpFilters } // namespace Extensions diff --git a/contrib/golang/filters/http/source/golang_filter.h b/contrib/golang/filters/http/source/golang_filter.h index a965c53fd525..82b97fddb57c 100644 --- a/contrib/golang/filters/http/source/golang_filter.h +++ b/contrib/golang/filters/http/source/golang_filter.h @@ -263,14 +263,6 @@ struct httpRequestInternal : httpRequest { std::weak_ptr weakFilter() { return filter_; } }; -class FilterLogger : Logger::Loggable { -public: - FilterLogger() = default; - - void log(uint32_t level, absl::string_view message) const; - uint32_t level() const; -}; - class GoStringFilterState : public StreamInfo::FilterState::Object { public: GoStringFilterState(absl::string_view value) : value_(value) {} diff --git a/contrib/golang/filters/http/test/test_data/basic/config.go b/contrib/golang/filters/http/test/test_data/basic/config.go index c55011d5054d..eed23a10748e 100644 --- a/contrib/golang/filters/http/test/test_data/basic/config.go +++ b/contrib/golang/filters/http/test/test_data/basic/config.go @@ -8,6 +8,9 @@ import ( const Name = "basic" func init() { + api.LogCritical("init") + api.LogCritical(api.GetLogLevel().String()) + http.RegisterHttpFilterConfigFactoryAndParser(Name, ConfigFactory, nil) } diff --git a/contrib/golang/filters/http/test/test_data/basic/filter.go b/contrib/golang/filters/http/test/test_data/basic/filter.go index ad5c8c839c34..9280dfdbce5d 100644 --- a/contrib/golang/filters/http/test/test_data/basic/filter.go +++ b/contrib/golang/filters/http/test/test_data/basic/filter.go @@ -106,6 +106,24 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. f.callbacks.Log(api.Error, "log test") f.callbacks.Log(api.Critical, "log test") + api.LogTrace("log test") + api.LogDebug("log test") + api.LogInfo("log test") + api.LogWarn("log test") + api.LogError("log test") + api.LogCritical("log test") + + api.LogTracef("log test %v", endStream) + api.LogDebugf("log test %v", endStream) + api.LogInfof("log test %v", endStream) + api.LogWarnf("log test %v", endStream) + api.LogErrorf("log test %v", endStream) + api.LogCriticalf("log test %v", endStream) + + if f.callbacks.LogLevel() != api.GetLogLevel() { + return f.fail("log level mismatch") + } + if f.sleep { time.Sleep(time.Millisecond * 100) // sleep 100 ms } diff --git a/contrib/golang/filters/network/source/BUILD b/contrib/golang/filters/network/source/BUILD index e4a1151e8a9c..fec216df0a6d 100644 --- a/contrib/golang/filters/network/source/BUILD +++ b/contrib/golang/filters/network/source/BUILD @@ -43,10 +43,11 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/common:minimal_logger_lib", "//source/common/common:thread_lib", - "//source/common/http:header_map_lib", - "//source/common/http:headers_lib", "//source/common/memory:utils_lib", "//source/common/network:connection_lib", + "//source/common/network:filter_state_dst_address_lib", + "//source/common/network:utility_lib", + "//source/common/stream_info:stream_info_lib", "//source/common/tcp:conn_pool_lib", "//source/common/upstream:load_balancer_lib", "//source/extensions/filters/network/common:factory_base_lib", @@ -81,6 +82,7 @@ envoy_cc_contrib_extension( ], deps = [ "//contrib/golang/common/dso:dso_lib", + "//contrib/golang/common/log:log_lib", "//envoy/buffer:buffer_interface", "//envoy/event:dispatcher_interface", "//envoy/network:connection_interface", @@ -93,8 +95,6 @@ envoy_cc_contrib_extension( "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", "//source/common/common:minimal_logger_lib", - "//source/common/http:header_map_lib", - "//source/common/http:headers_lib", "//source/common/memory:utils_lib", "//source/common/network:connection_lib", "//source/common/upstream:load_balancer_lib", diff --git a/contrib/golang/filters/network/source/golang.h b/contrib/golang/filters/network/source/golang.h index 2e7cb9f071cc..4118fbaf88a0 100644 --- a/contrib/golang/filters/network/source/golang.h +++ b/contrib/golang/filters/network/source/golang.h @@ -2,7 +2,6 @@ #include "envoy/buffer/buffer.h" #include "envoy/event/dispatcher.h" -#include "envoy/http/header_map.h" #include "envoy/network/connection.h" #include "envoy/network/filter.h" #include "envoy/ssl/connection.h" @@ -11,8 +10,6 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/common/logger.h" -#include "source/common/http/header_map_impl.h" -#include "source/common/http/headers.h" #include "source/common/network/connection_impl.h" #include "source/common/upstream/load_balancer_impl.h" #include "source/extensions/filters/network/common/factory_base.h" @@ -27,7 +24,7 @@ namespace NetworkFilters { namespace Golang { /** - * Configuration for the HTTP golang extension filter. + * Configuration for the Golang network filter. */ class FilterConfig { public: diff --git a/contrib/golang/filters/network/source/upstream.cc b/contrib/golang/filters/network/source/upstream.cc index 7eb9d9ff613f..2824c1a962a9 100644 --- a/contrib/golang/filters/network/source/upstream.cc +++ b/contrib/golang/filters/network/source/upstream.cc @@ -6,6 +6,8 @@ #include "envoy/tcp/conn_pool.h" #include "source/common/common/assert.h" +#include "source/common/network/filter_state_dst_address.h" +#include "source/common/network/utility.h" namespace Envoy { namespace Extensions { @@ -53,8 +55,14 @@ UpstreamConn::UpstreamConn(std::string addr, Dso::NetworkFilterDsoPtr dynamic_li ASSERT(!store.dispatchers_.empty()); dispatcher_ = &store.dispatchers_[store.dispatcher_idx_++ % store.dispatchers_.size()].get(); } - header_map_ = Http::createHeaderMap( - {{Http::Headers::get().EnvoyOriginalDstHost, addr}}); + stream_info_ = std::make_unique( + dispatcher_->timeSource(), nullptr, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info_->filterState()->setData( + Network::DestinationAddress::key(), + std::make_shared( + Network::Utility::parseInternetAddressAndPort(addr, false)), + StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain, + StreamInfo::StreamSharingMayImpactPooling::None); } void UpstreamConn::connect() { diff --git a/contrib/golang/filters/network/source/upstream.h b/contrib/golang/filters/network/source/upstream.h index 39712ab68460..d278c3e18c5e 100644 --- a/contrib/golang/filters/network/source/upstream.h +++ b/contrib/golang/filters/network/source/upstream.h @@ -5,7 +5,6 @@ #include "envoy/buffer/buffer.h" #include "envoy/event/dispatcher.h" -#include "envoy/http/header_map.h" #include "envoy/network/connection.h" #include "envoy/network/filter.h" #include "envoy/tcp/conn_pool.h" @@ -14,10 +13,9 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/common/logger.h" #include "source/common/common/thread.h" -#include "source/common/http/header_map_impl.h" -#include "source/common/http/headers.h" #include "source/common/memory/utils.h" #include "source/common/network/connection_impl.h" +#include "source/common/stream_info/stream_info_impl.h" #include "source/common/upstream/load_balancer_impl.h" #include "source/extensions/filters/network/common/factory_base.h" @@ -63,7 +61,7 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, void onEvent(Network::ConnectionEvent event) override; // Upstream::LoadBalancerContextBase - const Http::RequestHeaderMap* downstreamHeaders() const override { return header_map_.get(); }; + const StreamInfo::StreamInfo* requestStreamInfo() const override { return stream_info_.get(); } void connect(); void write(Buffer::Instance& buf, bool end_stream); @@ -100,7 +98,7 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, unsigned long long int goConnID_{0}; UpstreamConnWrapper* wrapper_{nullptr}; Event::Dispatcher* dispatcher_{nullptr}; - std::unique_ptr header_map_{nullptr}; + std::unique_ptr stream_info_{nullptr}; Tcp::ConnectionPool::ConnectionDataPtr conn_{nullptr}; Upstream::HostDescriptionConstSharedPtr host_{nullptr}; Tcp::ConnectionPool::Cancellable* handler_{nullptr}; diff --git a/contrib/golang/filters/network/test/BUILD b/contrib/golang/filters/network/test/BUILD index 16d212c1bfdb..359cf1b3bbb1 100644 --- a/contrib/golang/filters/network/test/BUILD +++ b/contrib/golang/filters/network/test/BUILD @@ -45,6 +45,7 @@ envoy_cc_test( deps = [ "//contrib/golang/common/dso/test:dso_mocks", "//contrib/golang/filters/network/source:upstream", + "//source/common/network:filter_state_dst_address_lib", "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", diff --git a/contrib/golang/filters/network/test/upstream_test.cc b/contrib/golang/filters/network/test/upstream_test.cc index 7819a28b509d..77d62a9b45fd 100644 --- a/contrib/golang/filters/network/test/upstream_test.cc +++ b/contrib/golang/filters/network/test/upstream_test.cc @@ -2,6 +2,8 @@ #include "envoy/registry/registry.h" +#include "source/common/network/filter_state_dst_address.h" + #include "test/mocks/server/factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" @@ -57,6 +59,11 @@ class UpstreamConnTest : public testing::Test { TEST_F(UpstreamConnTest, ConnectUpstream) { initialize(); + const auto* dst_addr = + upConn_->requestStreamInfo()->filterState().getDataReadOnly( + Network::DestinationAddress::key()); + EXPECT_EQ(dst_addr->address()->asString(), addr_); + EXPECT_CALL(context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_, newConnection(_)) .WillOnce( Invoke([&](Tcp::ConnectionPool::Callbacks& cb) -> Tcp::ConnectionPool::Cancellable* { diff --git a/contrib/kafka/filters/network/source/mesh/BUILD b/contrib/kafka/filters/network/source/mesh/BUILD index 7ce691225bdf..316c0f138d08 100644 --- a/contrib/kafka/filters/network/source/mesh/BUILD +++ b/contrib/kafka/filters/network/source/mesh/BUILD @@ -252,6 +252,8 @@ envoy_cc_library( ], tags = ["skip_on_windows"], deps = [ + "//envoy/common:base_includes", + "@com_google_absl//absl/strings", envoy_external_dep_path("librdkafka"), ], ) diff --git a/contrib/sip_proxy/filters/network/test/tra_test.cc b/contrib/sip_proxy/filters/network/test/tra_test.cc index db8b3b2998a8..16780b483908 100644 --- a/contrib/sip_proxy/filters/network/test/tra_test.cc +++ b/contrib/sip_proxy/filters/network/test/tra_test.cc @@ -49,11 +49,11 @@ class SipTraTest : public testing::Test { auto config = std::make_shared>(); EXPECT_CALL(*config, stats()).WillRepeatedly(ReturnRef(stat)); auto context = std::make_shared>(); - auto filter = std::make_shared>(*config, random_, time_source_, - *context, nullptr); + filter_ = std::make_shared>(*config, random_, time_source_, + *context, nullptr); auto tra_handler = std::make_shared>( - *filter, dispatcher_, *tra_config, *context, stream_info_); + *filter_, dispatcher_, *tra_config, *context, stream_info_); auto async_client = std::make_shared>(); @@ -78,6 +78,7 @@ class SipTraTest : public testing::Test { StreamInfo::StreamInfoImpl stream_info_; std::unique_ptr> async_stream_; TrafficRoutingAssistant::ClientPtr tra_client_; + std::shared_ptr filter_; }; TEST_F(SipTraTest, TraUpdate) { diff --git a/contrib/vcl/source/BUILD b/contrib/vcl/source/BUILD index a731070d020f..74b837b27921 100644 --- a/contrib/vcl/source/BUILD +++ b/contrib/vcl/source/BUILD @@ -85,7 +85,7 @@ genrule( && find . -name "*.a" | xargs -I{} cp -a {} $$EXTERNAL_DIR \ && find . -name "vppcom.h" | xargs -I{} cp -a {} $$EXTERNAL_DIR """, - exec_tools = [":build"], + tools = [":build"], ) envoy_cc_library( diff --git a/distribution/BUILD b/distribution/BUILD index 5c260de7fe6b..a709fa7dd2cb 100644 --- a/distribution/BUILD +++ b/distribution/BUILD @@ -113,12 +113,12 @@ genrule( -m arm64/envoy-contrib:bin/envoy-contrib-$${VERSION}-linux-aarch_64 \ --out $@ """ % VERSION, - exec_tools = [ + tags = ["no-remote"], + tools = [ ":arm64-packages", - ":x64-packages", ":arm64-release", + ":x64-packages", ":x64-release", "//tools/distribution:sign", ], - tags = ["no-remote"], ) diff --git a/docs/BUILD b/docs/BUILD index 2517d3de973b..c8fddf3935b5 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -71,7 +71,7 @@ genrule( $(location //source/extensions:extensions_metadata.yaml) \\ $(location //contrib:extensions_metadata.yaml) $@ """, - exec_tools = ["//tools/docs:generate_extensions_security_rst"], + tools = ["//tools/docs:generate_extensions_security_rst"], ) genrule( @@ -82,7 +82,7 @@ genrule( $(location //bazel:all_repository_locations) \ $@ """, - exec_tools = [ + tools = [ "//bazel:all_repository_locations", "//tools/docs:generate_external_deps_rst", ], @@ -108,7 +108,7 @@ genrule( cmd = """ cat $(location :v3_proto_srcs) $(location :xds_proto_srcs) > $@ """, - exec_tools = [ + tools = [ ":v3_proto_srcs", ":xds_proto_srcs", ], @@ -122,7 +122,7 @@ genrule( $(location //tools/protodoc:generate_empty) \\ $(location empty_extensions.json) $@ """, - exec_tools = ["//tools/protodoc:generate_empty"], + tools = ["//tools/protodoc:generate_empty"], ) genrule( @@ -136,7 +136,7 @@ genrule( $(location //tools/docs:generate_api_rst) \\ $(location proto_srcs) $(locations //tools/protodoc:api_v3_protodoc) $@ """, - exec_tools = ["//tools/docs:generate_api_rst"], + tools = ["//tools/docs:generate_api_rst"], ) pkg_files( @@ -178,7 +178,7 @@ genrule( cmd = """ $(location //tools/docs:generate_version_histories) $@ """, - exec_tools = [ + tools = [ ":versions.yaml", "//:VERSION.txt", "//changelogs", @@ -247,14 +247,14 @@ genrule( $(location rst) \ $@ """, - exec_tools = [ - "//bazel:volatile_env", - "//tools/docs:sphinx_runner", + stamp = 1, + tools = [ ":rst", "//:VERSION.txt", + "//bazel:volatile_env", + "//tools/docs:sphinx_runner", "@envoy_api//:v3_proto_set", ], - stamp = 1, ) # No git stamping, speeds up local dev switching branches @@ -269,10 +269,10 @@ genrule( $(location :rst) \ $@ """, - exec_tools = [ - "//tools/docs:sphinx_runner", + tools = [ ":rst", "//:VERSION.txt", + "//tools/docs:sphinx_runner", "@envoy_api//:v3_proto_set", ], ) diff --git a/docs/root/api-docs/xds_protocol.rst b/docs/root/api-docs/xds_protocol.rst index a9c36ef1f4f6..f543c4700220 100644 --- a/docs/root/api-docs/xds_protocol.rst +++ b/docs/root/api-docs/xds_protocol.rst @@ -24,8 +24,8 @@ transports described below. The following v3 xDS resource types are supported: - :ref:`envoy.config.listener.v3.Listener ` -- :ref:`envoy.config.route.v3.RouteConfiguration ` -- :ref:`envoy.config.route.v3.ScopedRouteConfiguration ` +- :ref:`envoy.config.route.v3.RouteConfiguration `, +- :ref:`envoy.config.route.v3.ScopedRouteConfiguration `, - :ref:`envoy.config.route.v3.VirtualHost ` - :ref:`envoy.config.cluster.v3.Cluster ` - :ref:`envoy.config.endpoint.v3.ClusterLoadAssignment ` @@ -716,6 +716,9 @@ will not take effect until EDS/RDS responses are supplied. - Warming of ``Cluster`` is completed only when a new ``ClusterLoadAssignment`` response is supplied by management server even if there is no change in endpoints. + If the runtime flag ``envoy.restart_features.use_eds_cache_for_ads`` is set to true, + Envoy will use a cached ``ClusterLoadAssignment`` for a cluster, if exists, after + the resource warming times out. - Warming of ``Listener`` is completed even if management server does not send a response for ``RouteConfiguration`` referenced by ``Listener``. Envoy will use the previously sent ``RouteConfiguration`` to finish ``Listener`` warming. Management Server diff --git a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst index be3eab4df5b6..83fd1aea5bd0 100644 --- a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst +++ b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst @@ -5,12 +5,12 @@ The filter uses a few different credentials providers to obtain an AWS access ke It moves through the credentials providers in the order described below, stopping when one of them returns an access key ID and a secret access key (the session token is optional). -1. Environment variables. The environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN are used. +1. Environment variables. The environment variables ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, and ``AWS_SESSION_TOKEN`` are used. -2. The AWS credentials file. The environment variables AWS_SHARED_CREDENTIALS_FILE and AWS_PROFILE are respected if they are set, else - the file '~/.aws/credentials' and profile 'default' are used. The fields 'aws_access_key_id', 'aws_secret_access_key', and - 'aws_session_token' defined for the profile in the credentials file are used. These credentials are cached for 1 hour. +2. The AWS credentials file. The environment variables ``AWS_SHARED_CREDENTIALS_FILE`` and ``AWS_PROFILE`` are respected if they are set, else + the file ``~/.aws/credentials`` and profile ``default`` are used. The fields ``aws_access_key_id``, ``aws_secret_access_key``, and + ``aws_session_token`` defined for the profile in the credentials file are used. These credentials are cached for 1 hour. -3. Either EC2 instance metadata or ECS task metadata. For EC2 instance metadata, the fields 'AccessKeyId', 'SecretAccessKey', and - 'Token' are used, and credentials are cached for 1 hour. For ECS task metadata, the fields AccessKeyId', 'SecretAccessKey', and - 'Token' are used, and credentials are cached for 1 hour or until they expire (according to the field 'Expiration'). +3. Either EC2 instance metadata or ECS task metadata. For EC2 instance metadata, the fields ``AccessKeyId``, ``SecretAccessKey``, and + ``Token`` are used, and credentials are cached for 1 hour. For ECS task metadata, the fields ``AccessKeyId``, ``SecretAccessKey``, and + ``Token`` are used, and credentials are cached for 1 hour or until they expire (according to the field ``Expiration``). diff --git a/docs/root/configuration/http/http_filters/_include/json-to-metadata-filter.yaml b/docs/root/configuration/http/http_filters/_include/json-to-metadata-filter.yaml new file mode 100644 index 000000000000..d0cb79f4ce0c --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/json-to-metadata-filter.yaml @@ -0,0 +1,83 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: versioned-cluster + http_filters: + - name: envoy.filters.http.json_to_metadata + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.json_to_metadata.v3.JsonToMetadata + request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + on_missing: + metadata_namespace: envoy.lb + key: default + value: "true" + preserve_existing_metadata_value: true + on_error: + metadata_namespace: envoy.lb + key: default + value: "true" + preserve_existing_metadata_value: true + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + + clusters: + - name: versioned-cluster + type: STRICT_DNS + lb_policy: ROUND_ROBIN + lb_subset_config: + fallback_policy: ANY_ENDPOINT + subset_selectors: + - keys: + - default + - keys: + - version + load_assignment: + cluster_name: versioned-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + metadata: + filter_metadata: + envoy.lb: + default: "true" + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8081 + metadata: + filter_metadata: + envoy.lb: + version: "1.0" diff --git a/docs/root/configuration/http/http_filters/aws_lambda_filter.rst b/docs/root/configuration/http/http_filters/aws_lambda_filter.rst index 6dbad84fc713..8f0a7b153244 100644 --- a/docs/root/configuration/http/http_filters/aws_lambda_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_lambda_filter.rst @@ -39,8 +39,8 @@ is set to ``false``, then the HTTP request is transformed to a JSON payload with - ``headers`` are the HTTP request headers. If multiple headers share the same name, their values are coalesced into a single comma-separated value. - ``query_string_parameters`` are the HTTP request query string parameters. If multiple parameters share the same name, - the last one wins. That is, parameters are _not_ coalesced into a single value if they share the same key name. -- ``body`` the body of the HTTP request is base64-encoded by the filter if the ``content-type`` header exists and is _not_ one of the following: + the last one wins. That is, parameters are **not** coalesced into a single value if they share the same key name. +- ``body`` the body of the HTTP request is base64-encoded by the filter if the ``content-type`` header exists and is **not** one of the following: - text/* - application/json @@ -66,7 +66,7 @@ On the other end, the response of the Lambda function must conform to the follow OK``. - The ``headers`` are used as the HTTP response headers. - The ``cookies`` are used as ``Set-Cookie`` response headers. Unlike the request headers, cookies are _not_ part of the - response headers because the ``Set-Cookie`` header cannot contain more than one value per the `RFC`_. Therefore, Each + response headers because the ``Set-Cookie`` header cannot contain more than one value per the `RFC`_. Therefore, each key/value pair in this JSON array will translate to a single ``Set-Cookie`` header. - The ``body`` is base64-decoded if it is marked as base64-encoded and sent as the body of the HTTP response. @@ -200,4 +200,4 @@ comes from the owning HTTP connection manager. :widths: 1, 1, 2 server_error, Counter, Total requests that returned invalid JSON response (see :ref:`payload_passthrough `) - upstream_rq_payload_size, Histogram, Size in bytes of the request after JSON-tranformation (if any). + upstream_rq_payload_size, Histogram, Size in bytes of the request after JSON-transformation (if any). diff --git a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst index 886baee1d27a..208dd5006cb4 100644 --- a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst @@ -23,7 +23,7 @@ policies to determine if this option is appropriate. When :ref:`use_unsigned_payload ` is false (the default), requests which exceed the configured buffer limit will receive a 413 response. See the -ref:`flow control docs ` for details. +:ref:`flow control docs ` for details. The :ref:`match_excluded_headers ` option allows excluding certain request headers from being signed. This usually applies to headers that are likely to mutate or diff --git a/docs/root/configuration/http/http_filters/cache_filter.rst b/docs/root/configuration/http/http_filters/cache_filter.rst index 259c8bf79f72..32aa6b24d16e 100644 --- a/docs/root/configuration/http/http_filters/cache_filter.rst +++ b/docs/root/configuration/http/http_filters/cache_filter.rst @@ -17,14 +17,14 @@ For HTTP Requests: For HTTP Responses: -* HTTP Cache only caches responses with enough data to calculate freshness lifetime as per `RFC7234#calculating.freshness.lifetime `_. +* HTTP Cache only caches responses with enough data to calculate freshness lifetime as per `RFC7234 `_. * HTTP Cache respects ``Cache-Control`` directive from the upstream host. For example, if HTTP response returns status code 200 with ``Cache-Control: max-age=60`` and no ``vary`` header, it will be cached. * HTTP Cache only caches responses with status codes: 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 451, 501. HTTP Cache delegates the actual storage of HTTP responses to implementations of the ``HttpCache`` interface. These implementations can cover all points on the spectrum of persistence, performance, and distribution, from local RAM caches to globally distributed persistent caches. They can be fully custom caches, or wrappers/adapters around local or remote open-source or proprietary caches. -Currently the only available cache storage implementation is :ref:`SimpleHTTPCache ` +Currently the only available cache storage implementation is :ref:`SimpleHTTPCache `. Example configuration --------------------- diff --git a/docs/root/configuration/http/http_filters/composite_filter.rst b/docs/root/configuration/http/http_filters/composite_filter.rst index 4bca323ead03..669865dbde62 100644 --- a/docs/root/configuration/http/http_filters/composite_filter.rst +++ b/docs/root/configuration/http/http_filters/composite_filter.rst @@ -39,7 +39,7 @@ instantiated. Statistics ---------- -The composite filter outputs statistics in the .composite.* namespace. +The composite filter outputs statistics in the ``.composite.*`` namespace. .. csv-table:: :header: Name, Type, Description diff --git a/docs/root/configuration/http/http_filters/compressor_filter.rst b/docs/root/configuration/http/http_filters/compressor_filter.rst index bd68227101bc..9c3c0dba9a31 100644 --- a/docs/root/configuration/http/http_filters/compressor_filter.rst +++ b/docs/root/configuration/http/http_filters/compressor_filter.rst @@ -55,60 +55,75 @@ An example configuration of the filter may look like the following: By *default* request compression is disabled, but when enabled it will be *skipped* if: -- A request does not contain a *content-type* value that matches one of the selected - mime-types, which default to *application/javascript*, *application/json*, - *application/xhtml+xml*, *image/svg+xml*, *text/css*, *text/html*, *text/plain*, - *text/xml*. -- *content-length* header is not present in the request. -- A request contains a *content-encoding* header. -- A request contains a *transfer-encoding* header whose value includes a known +- A request does **not** contain a ``content-type`` value that matches one of the selected + mime-types, which default to the following: + + - ``application/javascript`` + - ``application/json`` + - ``application/xhtml+xml`` + - ``image/svg+xml`` + - ``text/css`` + - ``text/html`` + - ``text/plain`` + - ``text/xml`` + +- A request does **not** contain a ``content-length`` header. +- A request contains a ``content-encoding`` header. +- A request contains a ``transfer-encoding`` header whose value includes a known compression name. By *default* response compression is enabled, but it will be *skipped* when: -- A request does NOT contain *accept-encoding* header. -- A request includes *accept-encoding* header, but it does not contain "gzip" or "\*". -- A request includes *accept-encoding* with "gzip" or "\*" with the weight "q=0". Note - that the "gzip" will have a higher weight then "\*". For example, if *accept-encoding* - is "gzip;q=0,\*;q=1", the filter will not compress. But if the header is set to - "\*;q=0,gzip;q=1", the filter will compress. -- A request whose *accept-encoding* header includes any encoding type with a higher - weight than "gzip"'s given the corresponding compression filter is present in the chain. -- A response contains a *content-encoding* header. -- A response contains a *cache-control* header whose value includes "no-transform". -- A response contains a *transfer-encoding* header whose value includes a known +- A request does **not** contain ``accept-encoding`` header. +- A request contains an ``accept-encoding`` header, but it does not contain ``gzip`` or ``\*``. +- A request contains an ``accept-encoding`` header with ``gzip`` or ``\*```` with the weight ``q=0``. Note + that the ``gzip`` will have a higher weight than ``\*``. For example, if ``accept-encoding`` + is ``gzip;q=0,\*;q=1``, the filter will not compress. But if the header is set to + ``\*;q=0,gzip;q=1``, the filter will compress. +- A request whose ``accept-encoding`` header includes any encoding type with a higher + weight than ``gzip``'s given the corresponding compression filter is present in the chain. +- A response contains a ``content-encoding`` header. +- A response contains a ``cache-control```` header whose value includes ``no-transform``. +- A response contains a ``transfer-encoding```` header whose value includes a known compression name. -- A response does not contain a *content-type* value that matches one of the selected - mime-types, which default to *application/javascript*, *application/json*, - *application/xhtml+xml*, *image/svg+xml*, *text/css*, *text/html*, *text/plain*, - *text/xml*. -- Neither *content-length* nor *transfer-encoding* headers are present in - the response. -- Response size is smaller than 30 bytes (only applicable when *transfer-encoding* +- A response does **not** contain a ``content-type`` value that matches one of the selected + mime-types, which default to: + + - ``application/javascript`` + - ``application/json`` + - ``application/xhtml+xml`` + - ``image/svg+xml`` + - ``text/css`` + - ``text/html`` + - ``text/plain`` + - ``text/xml`` + +- A response does **not** contain a ``content-length`` or ``transfer-encoding`` headers. +- Response size is smaller than 30 bytes (only applicable when ``transfer-encoding`` is not chunked). Please note that in case the filter is configured to use a compression library extension -other than gzip it looks for content encoding in the *accept-encoding* header provided by +other than gzip it looks for content encoding in the ``accept-encoding`` header provided by the extension. When response compression is *applied*: -- The *content-length* is removed from response headers. -- Response headers contain "*transfer-encoding: chunked*", and - "*content-encoding*" with the compression scheme used (e.g., ``gzip``). -- The "*vary: accept-encoding*" header is inserted on every response. +- The ``content-length`` is removed from response headers. +- Response headers contain ``transfer-encoding: chunked``, and + ``content-encoding`` with the compression scheme used (e.g., ``gzip``). +- The ``vary: accept-encoding`` header is inserted on every response. -Also the "*vary: accept-encoding*" header may be inserted even if compression is *not* -applied due to incompatible "*accept-encoding*" header in a request. This happens -when the requested resource still can be compressed given compatible "*accept-encoding*". +Also the ``vary: accept-encoding`` header may be inserted even if compression is **not** +applied due to incompatible ``accept-encoding`` header in a request. This happens +when the requested resource can still be compressed given compatible ``accept-encoding``. Otherwise, if an uncompressed response is cached by a caching proxy in front of Envoy, -the proxy won't know to fetch a new incoming request with compatible "*accept-encoding*" +the proxy won't know to fetch a new incoming request with compatible ``accept-encoding`` from upstream. When request compression is *applied*: -- *content-length* is removed from request headers. -- *content-encoding* with the compression scheme used (e.g., ``gzip``) is added to +- ``content-length`` is removed from request headers. +- ``content-encoding`` with the compression scheme used (e.g., ``gzip``) is added to request headers. Per-Route Configuration @@ -215,12 +230,12 @@ specific to responses only: :widths: 1, 1, 2 no_accept_header, Counter, Number of requests with no accept header sent. - header_identity, Counter, Number of requests sent with "identity" set as the *accept-encoding*. - header_compressor_used, Counter, Number of requests sent with filter's configured encoding set as the *accept-encoding*. + header_identity, Counter, Number of requests sent with "identity" set as the ``accept-encoding``. + header_compressor_used, Counter, Number of requests sent with filter's configured encoding set as the ``accept-encoding``. header_compressor_overshadowed, Counter, Number of requests skipped by this filter instance because they were handled by another filter in the same filter chain. - header_wildcard, Counter, Number of requests sent with "\*" set as the *accept-encoding*. - header_not_valid, Counter, Number of requests sent with a not valid *accept-encoding* header (aka "q=0" or an unsupported encoding type). - not_compressed_etag, Counter, Number of requests that were not compressed due to the etag header. *disable_on_etag_header* must be turned on for this to happen. + header_wildcard, Counter, Number of requests sent with ``\*`` set as the ``accept-encoding``. + header_not_valid, Counter, Number of requests sent with a not valid ``accept-encoding`` header (aka ``q=0`` or an unsupported encoding type). + not_compressed_etag, Counter, Number of requests that were not compressed due to the etag header. ``disable_on_etag_header`` must be turned on for this to happen. .. attention: diff --git a/docs/root/configuration/http/http_filters/connect_grpc_bridge_filter.rst b/docs/root/configuration/http/http_filters/connect_grpc_bridge_filter.rst index 9e70e39f3b18..472734b4e2d9 100644 --- a/docs/root/configuration/http/http_filters/connect_grpc_bridge_filter.rst +++ b/docs/root/configuration/http/http_filters/connect_grpc_bridge_filter.rst @@ -8,7 +8,7 @@ Connect-gRPC Bridge * :ref:`v3 API reference ` This filter enables a Buf Connect client to connect to a compliant gRPC server. -More information on the Buf Connect protocol can be found here https://connect.build/docs/protocol. +More information on the Buf Connect protocol can be found `here `_. HTTP GET support ---------------- diff --git a/docs/root/configuration/http/http_filters/cors_filter.rst b/docs/root/configuration/http/http_filters/cors_filter.rst index b5c7e7051b30..99fb26dd5c2e 100644 --- a/docs/root/configuration/http/http_filters/cors_filter.rst +++ b/docs/root/configuration/http/http_filters/cors_filter.rst @@ -45,7 +45,7 @@ will not enforce any policies. Statistics ---------- -The CORS filter outputs statistics in the .cors.* namespace. +The CORS filter outputs statistics in the ``.cors.*`` namespace. .. note:: Requests that do not have an Origin header will be omitted from statistics. diff --git a/docs/root/configuration/http/http_filters/csrf_filter.rst b/docs/root/configuration/http/http_filters/csrf_filter.rst index 9d1a618a748e..a5e9e87e508e 100644 --- a/docs/root/configuration/http/http_filters/csrf_filter.rst +++ b/docs/root/configuration/http/http_filters/csrf_filter.rst @@ -20,7 +20,8 @@ There are many ways to mitigate CSRF, some of which have been outlined in the This filter employs a stateless mitigation pattern known as origin verification. This pattern relies on two pieces of information used in determining if -a request originated from the same host. +a request originated from the same host: + * The origin that caused the user agent to issue the request (source origin). * The origin that the request is going to (target origin). @@ -39,7 +40,7 @@ requests, the filter only acts on HTTP requests that have a state-changing metho For more information on CSRF please refer to the pages below. -* https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29 +* https://owasp.org/www-community/attacks/csrf * https://seclab.stanford.edu/websec/csrf/csrf.pdf * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy``. * :ref:`v3 API reference ` @@ -52,7 +53,7 @@ Configuration The CSRF filter supports the ability to extend the source origins it will consider valid. The reason it is able to do this while still mitigating cross-site request forgery attempts is because the target origin has already been reached by the time -front-envoy is applying the filter. This means that while endpoints may support +front Envoy is applying the filter. This means that while endpoints may support cross-origin requests they are still protected from malicious third-parties who have not been allowlisted. @@ -92,7 +93,7 @@ to determine if it's valid but will not enforce any policies. Statistics ---------- -The CSRF filter outputs statistics in the .csrf.* namespace. +The CSRF filter outputs statistics in the ``.csrf.`` namespace. .. csv-table:: :header: Name, Type, Description diff --git a/docs/root/configuration/http/http_filters/decompressor_filter.rst b/docs/root/configuration/http/http_filters/decompressor_filter.rst index 2d6684c391f2..7f5ca604212b 100644 --- a/docs/root/configuration/http/http_filters/decompressor_filter.rst +++ b/docs/root/configuration/http/http_filters/decompressor_filter.rst @@ -37,27 +37,27 @@ An example configuration of the filter may look like the following: By *default* decompression will be *skipped* when: -- A request/response does NOT contain *content-encoding* header. -- A request/response includes *content-encoding* header, but it does not contain the configured - decompressor's content-encoding. -- A request/response contains a *cache-control* header whose value includes "no-transform", +- A request/response does **not** contain ``content-encoding`` header. +- A request/response contains a ``content-encoding`` header, but it does not contain the configured + decompressor's ``content-encoding``. +- A request/response contains a ``cache-control`` header whose value includes ``no-transform``, unless :ref:`ignore_no_transform_header ` - is set to *true*. + is set to ``true``. -When decompression is *applied*: +Decompression is *applied* when: -- The *content-length* is removed from headers. +- The ``content-length`` is removed from headers. .. note:: - If an updated *content-length* header is desired, the buffer filter can be installed as part + If an updated ``content-length`` header is desired, the :ref: `buffer filter <_config_http_filters_buffer>` can be installed as part of the filter chain to buffer decompressed frames, and ultimately update the header. Due to :ref:`filter ordering ` a buffer filter needs to be installed after the decompressor for requests and prior to the decompressor for responses. -- The *content-encoding* header is modified to remove the decompression that was applied. +- The ``content-encoding`` header is modified to remove the decompression that was applied. -- *x-envoy-decompressor---bytes* trailers are added to +- ``x-envoy-decompressor---bytes`` trailers are added to the request/response to relay information about decompression. Using different decompressors for requests and responses @@ -106,7 +106,7 @@ Statistics ---------- Every configured Deompressor filter has statistics rooted at -.decompressor...* +``.decompressor...*`` with the following: .. csv-table:: @@ -119,4 +119,4 @@ with the following: total_compressed_bytes, Counter, The total compressed bytes of all the request/responses that were marked for decompression. Additional stats for the decompressor library are rooted at -.decompressor...decompressor_library. +``.decompressor...decompressor_library``. diff --git a/docs/root/configuration/http/http_filters/dynamic_forward_proxy_filter.rst b/docs/root/configuration/http/http_filters/dynamic_forward_proxy_filter.rst index ded0b1cd0369..90b42be44c25 100644 --- a/docs/root/configuration/http/http_filters/dynamic_forward_proxy_filter.rst +++ b/docs/root/configuration/http/http_filters/dynamic_forward_proxy_filter.rst @@ -58,7 +58,7 @@ To use :ref:`AppleDnsResolverConfig.* +The dynamic forward proxy DNS cache outputs statistics in the ``dns_cache..`` namespace. .. csv-table:: @@ -75,7 +75,7 @@ namespace. num_hosts, Gauge, Number of hosts that are currently in the cache. dns_rq_pending_overflow, Counter, Number of dns pending request overflow. -The dynamic forward proxy DNS cache circuit breakers outputs statistics in the dns_cache..circuit_breakers* +The dynamic forward proxy DNS cache circuit breakers outputs statistics in the ``dns_cache..circuit_breakers`` namespace. .. csv-table:: diff --git a/docs/root/configuration/http/http_filters/dynamodb_filter.rst b/docs/root/configuration/http/http_filters/dynamodb_filter.rst index 9a3c5242ee6a..8ed9fa6c6ee8 100644 --- a/docs/root/configuration/http/http_filters/dynamodb_filter.rst +++ b/docs/root/configuration/http/http_filters/dynamodb_filter.rst @@ -10,23 +10,23 @@ DynamoDB Statistics ---------- -The DynamoDB filter outputs statistics in the *http..dynamodb.* namespace. The :ref:`stat prefix +The DynamoDB filter outputs statistics in the ``http..dynamodb.`` namespace. The :ref:`stat prefix ` comes from the owning HTTP connection manager. -Per operation stats can be found in the *http..dynamodb.operation..* +Per operation stats can be found in the ``http..dynamodb.operation..`` namespace. .. csv-table:: :header: Name, Type, Description :widths: 1, 1, 2 - upstream_rq_total, Counter, Total number of requests with - upstream_rq_time, Histogram, Time spent on - upstream_rq_total_xxx, Counter, Total number of requests with per response code (503/2xx/etc) - upstream_rq_time_xxx, Histogram, Time spent on per response code (400/3xx/etc) + upstream_rq_total, Counter, Total number of requests with ```` + upstream_rq_time, Histogram, Time spent on ```` + upstream_rq_total_xxx, Counter, Total number of requests with ```` per response code (503/2xx/etc) + upstream_rq_time_xxx, Histogram, Time spent on ```` per response code (400/3xx/etc) -Per table stats can be found in the *http..dynamodb.table..* namespace. +Per table stats can be found in the ``http..dynamodb.table..`` namespace. Most of the operations to DynamoDB involve a single table, but BatchGetItem and BatchWriteItem can include several tables, Envoy tracks per table stats in this case only if it is the same table used in all operations from the batch. @@ -35,13 +35,13 @@ in all operations from the batch. :header: Name, Type, Description :widths: 1, 1, 2 - upstream_rq_total, Counter, Total number of requests on table - upstream_rq_time, Histogram, Time spent on table - upstream_rq_total_xxx, Counter, Total number of requests on table per response code (503/2xx/etc) - upstream_rq_time_xxx, Histogram, Time spent on table per response code (400/3xx/etc) + upstream_rq_total, Counter, Total number of requests on ```` table + upstream_rq_time, Histogram, Time spent on ```` table + upstream_rq_total_xxx, Counter, Total number of requests on ```` table per response code (503/2xx/etc) + upstream_rq_time_xxx, Histogram, Time spent on ```` table per response code (400/3xx/etc) *Disclaimer: Please note that this is a pre-release Amazon DynamoDB feature that is not yet widely available.* -Per partition and operation stats can be found in the *http..dynamodb.table..* +Per partition and operation stats can be found in the ``http..dynamodb.table..`` namespace. For batch operations, Envoy tracks per partition and operation stats only if it is the same table used in all operations. @@ -49,19 +49,19 @@ table used in all operations. :header: Name, Type, Description :widths: 1, 1, 2 - capacity..__partition_id=, Counter, Total number of capacity for on table for a given + capacity..__partition_id=, Counter, Total number of capacity for ```` on ```` table for a given ```` Additional detailed stats: * For 4xx responses and partial batch operation failures, the total number of failures for a given - table and failure are tracked in the *http..dynamodb.error..* namespace. + table and failure are tracked in the ``http..dynamodb.error..`` namespace. .. csv-table:: :header: Name, Type, Description :widths: 1, 1, 2 - , Counter, Total number of specific for a given - BatchFailureUnprocessedKeys, Counter, Total number of partial batch failures for a given + , Counter, Total number of specific ```` for a given ```` + BatchFailureUnprocessedKeys, Counter, Total number of partial batch failures for a given ```` Runtime ------- diff --git a/docs/root/configuration/http/http_filters/ext_authz_filter.rst b/docs/root/configuration/http/http_filters/ext_authz_filter.rst index 2c167062f0c4..a59cdc1b030d 100644 --- a/docs/root/configuration/http/http_filters/ext_authz_filter.rst +++ b/docs/root/configuration/http/http_filters/ext_authz_filter.rst @@ -4,7 +4,7 @@ External Authorization ====================== * External authorization :ref:`architecture overview ` * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz``. -* :ref:`HTTP filter v3 API reference ` +* :ref:`v3 API reference ` The external authorization filter calls an external gRPC or HTTP service to check whether an incoming HTTP request is authorized or not. @@ -162,7 +162,7 @@ Statistics ---------- .. _config_http_filters_ext_authz_stats: -The HTTP filter outputs statistics in the *cluster..ext_authz.* namespace. +The HTTP filter outputs statistics in the ``cluster..ext_authz.`` namespace. .. csv-table:: :header: Name, Type, Description diff --git a/docs/root/configuration/http/http_filters/ext_proc_filter.rst b/docs/root/configuration/http/http_filters/ext_proc_filter.rst index 9826bbf63115..7b5d45ab8194 100644 --- a/docs/root/configuration/http/http_filters/ext_proc_filter.rst +++ b/docs/root/configuration/http/http_filters/ext_proc_filter.rst @@ -3,7 +3,7 @@ External Processing =================== * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor``. -* :ref:`Http filter v3 API reference ` +* :ref:`v3 API reference ` The external processing filter connects an external service, called an "external processor," to the filter chain. The processing service itself implements a gRPC interface that allows @@ -31,7 +31,7 @@ be found on the :ref:`reference page .ext_proc.* namespace. The :ref:`stat prefix +``http..ext_proc.`` namespace. The :ref:`stat prefix ` comes from the owning HTTP connection manager. diff --git a/docs/root/configuration/http/http_filters/fault_filter.rst b/docs/root/configuration/http/http_filters/fault_filter.rst index 77c65032b12e..619ce9a0e036 100644 --- a/docs/root/configuration/http/http_filters/fault_filter.rst +++ b/docs/root/configuration/http/http_filters/fault_filter.rst @@ -210,7 +210,7 @@ which defaults to the config settings. Statistics ---------- -The fault filter outputs statistics in the *http..fault.* namespace. The :ref:`stat prefix +The fault filter outputs statistics in the ``http..fault.`` namespace. The :ref:`stat prefix ` comes from the owning HTTP connection manager. diff --git a/docs/root/configuration/http/http_filters/file_system_buffer_filter.rst b/docs/root/configuration/http/http_filters/file_system_buffer_filter.rst index dcf87587e17c..8b06acb14a6c 100644 --- a/docs/root/configuration/http/http_filters/file_system_buffer_filter.rst +++ b/docs/root/configuration/http/http_filters/file_system_buffer_filter.rst @@ -23,7 +23,7 @@ Configuration * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.file_system_buffer.v3.FileSystemBufferFilterConfig``. * :ref:`v3 API reference ` -* This filter should not be active on the same stream as *envoy.filters.http.buffer*, or any other +* This filter should not be active on the same stream as :ref:`Buffer filter `, or any other filter that changes the default buffering behavior, as stream watermarks will behave unpredictably with multiple buffer-altering filters. (There is also no need to have both together, as this filter offers the same features.) diff --git a/docs/root/configuration/http/http_filters/gcp_authn_filter.rst b/docs/root/configuration/http/http_filters/gcp_authn_filter.rst index 8a114f9f680f..d38b11aad3d2 100644 --- a/docs/root/configuration/http/http_filters/gcp_authn_filter.rst +++ b/docs/root/configuration/http/http_filters/gcp_authn_filter.rst @@ -20,7 +20,7 @@ The filter configuration :ref:`v3 API reference ` is the URL of the destination service, -which is the receiving service that the calling service is invoking. This information is provided through cluster's metadata field :ref:`Metadata` +which is the receiving service that the calling service is invoking. This information is provided through cluster's metadata field :ref:`Metadata`. The token cache configuration :ref:`v3 API reference ` is used to avoid redundant queries to the authentication server (GCE metadata server in the context of this filter) for duplicated tokens. diff --git a/docs/root/configuration/http/http_filters/geoip_filter.rst b/docs/root/configuration/http/http_filters/geoip_filter.rst index 84507290791e..7c25cbe0114a 100644 --- a/docs/root/configuration/http/http_filters/geoip_filter.rst +++ b/docs/root/configuration/http/http_filters/geoip_filter.rst @@ -3,7 +3,7 @@ IP Geolocation Filter ========================= This filter decorates HTTP requests with the geolocation data. -Filter uses client address to lookup information (eg client's city, country) in the geolocation provider database. +Filter uses client address to lookup information (e.g., client's city, country) in the geolocation provider database. Upon a successful lookup request will be enriched with the configured geolocation header and value from the database. In case the configured geolocation headers are present in the incoming request, they will be overriden by the filter. Geolocation filter emits stats for the number of successful lookups and the number of total lookups. @@ -13,20 +13,6 @@ Configuration * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.geoip.v3.Geoip``. * :ref:`v3 API reference ` -Statistics ----------- -Geolocation filter outputs statistics in the -*http..geoip..* namespace. The :ref:`stat prefix -` -comes from the owning HTTP connection manager. - -.. csv-table:: - :header: Name, Type, Description - :widths: auto - - .hit, Counter, Number of successful lookups within geolocation database for a configured geolocation header. - .total, Counter, Number of total lookups within geolocation database for a configured geolocation header. - Configuration example --------------------- @@ -44,3 +30,17 @@ Configuration example provider: name: "envoy.geoip_providers.maxmind" +Statistics +---------- +Geolocation filter outputs statistics in the +``http..geoip..`` namespace. The :ref:`stat prefix +` +comes from the owning HTTP connection manager. + +.. csv-table:: + :header: Name, Type, Description + :widths: auto + + .hit, Counter, Number of successful lookups within geolocation database for a configured geolocation header. + .total, Counter, Number of total lookups within geolocation database for a configured geolocation header. + diff --git a/docs/root/configuration/http/http_filters/http_filters.rst b/docs/root/configuration/http/http_filters/http_filters.rst index 654f567d99a2..dea21452d056 100644 --- a/docs/root/configuration/http/http_filters/http_filters.rst +++ b/docs/root/configuration/http/http_filters/http_filters.rst @@ -40,6 +40,7 @@ HTTP filters health_check_filter header_to_metadata_filter ip_tagging_filter + json_to_metadata_filter jwt_authn_filter kill_request_filter language_filter diff --git a/docs/root/configuration/http/http_filters/ip_tagging_filter.rst b/docs/root/configuration/http/http_filters/ip_tagging_filter.rst index 068410077b46..48adaacd17bc 100644 --- a/docs/root/configuration/http/http_filters/ip_tagging_filter.rst +++ b/docs/root/configuration/http/http_filters/ip_tagging_filter.rst @@ -10,7 +10,7 @@ the header is not set. The implementation for IP Tagging provides a scalable way to compare an IP address to a large list of CIDR ranges efficiently. The underlying algorithm for storing tags and IP address subnets is a Level-Compressed trie described in the paper `IP-address lookup using -LC-tries `_ by S. Nilsson and +LC-tries `_ by S. Nilsson and G. Karlsson. @@ -22,14 +22,14 @@ Configuration Statistics ---------- -The IP Tagging filter outputs statistics in the *http..ip_tagging.* namespace. The stat prefix comes from +The IP Tagging filter outputs statistics in the ``http..ip_tagging.`` namespace. The stat prefix comes from the owning HTTP connection manager. .. csv-table:: :header: Name, Type, Description :widths: 1, 1, 2 - .hit, Counter, Total number of requests that have the applied to it + .hit, Counter, Total number of requests that have the ```` applied to it no_hit, Counter, Total number of requests with no applicable IP tags total, Counter, Total number of requests the IP Tagging Filter operated on diff --git a/docs/root/configuration/http/http_filters/json_to_metadata_filter.rst b/docs/root/configuration/http/http_filters/json_to_metadata_filter.rst new file mode 100644 index 000000000000..8462061c8281 --- /dev/null +++ b/docs/root/configuration/http/http_filters/json_to_metadata_filter.rst @@ -0,0 +1,52 @@ +.. _config_http_filters_json_to_metadata: + +Envoy Json-To-Metadata Filter +============================= +* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.json_to_metadata.v3.JsonToMetadata``. +* :ref:`v3 API reference ` + +This filter is configured with rules that will be matched against requests. +Each rule has either a series of selectors and can be triggered either when the json attribute +is present or missing. + +When a rule is triggered, dynamic metadata will be added based on the configuration of the rule. +If present, it's value is extracted and used along with the specified key as metadata. If missing, +on missing case is triggered and the value specified is used for adding metadata. + +The metadata can then be used for load balancing decisions, consumed from logs, etc. + +A typical use case for this filter is to dynamically match a specified json attribute of requests +with rate limit. For this, a given value of the JSON keys would be extracted and attached +to the request as dynamic metadata which would then be used to match a rate limit action on metadata. + +The Json to metadata filter stops iterating to next filter until we have the whole payload to extract +the Json attributes or encounter error. + +Example +------- + +A sample filter configuration to route traffic to endpoints based on the presence or +absence of a version attribute could be: + +.. literalinclude:: _include/json-to-metadata-filter.yaml + :language: yaml + :lines: 25-45 + :lineno-start: 25 + :linenos: + :caption: :download:`json-to-metadata-filter.yaml <_include/json-to-metadata-filter.yaml>` + +Statistics +---------- + +The json to metadata filter outputs statistics in the *http..json_to_metadata.* namespace. The :ref:`stat prefix +` +comes from the owning HTTP connection manager. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + rq_success, Counter, Total requests that succeed to parse the json body. Note that a pure string or number body is treated as a successful json body which increases this counter. + rq_mismatched_content_type, Counter, Total requests that mismatch the content type + rq_no_body, Counter, Total requests without content body + rq_invalid_json_body, Counter, Total requests with invalid json body diff --git a/docs/root/configuration/http/http_filters/jwt_authn_filter.rst b/docs/root/configuration/http/http_filters/jwt_authn_filter.rst index 5bdc5915cbd8..e84a23a31400 100644 --- a/docs/root/configuration/http/http_filters/jwt_authn_filter.rst +++ b/docs/root/configuration/http/http_filters/jwt_authn_filter.rst @@ -21,29 +21,30 @@ Configuration ------------- * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication``. +* :ref:`v3 API reference ` This HTTP :ref:`filter config ` has two fields: -* Field *providers* specifies how a JWT should be verified, such as where to extract the token, where to fetch the public key (JWKS) and where to output its payload. -* Field *rules* specifies matching rules and their requirements. If a request matches a rule, its requirement applies. The requirement specifies which JWT providers should be used. +* Field ``providers`` specifies how a JWT should be verified, such as where to extract the token, where to fetch the public key (JWKS) and where to output its payload. +* Field ``rules`` specifies matching rules and their requirements. If a request matches a rule, its requirement applies. The requirement specifies which JWT providers should be used. JwtProvider ~~~~~~~~~~~ :ref:`JwtProvider ` specifies how a JWT should be verified. It has the following fields: -* *issuer*: the principal that issued the JWT, usually a URL or an email address. -* *audiences*: a list of JWT audiences allowed to access. A JWT containing any of these audiences will be accepted. +* ``issuer``: the principal that issued the JWT, usually a URL or an email address. +* ``audiences``: a list of JWT audiences allowed to access. A JWT containing any of these audiences will be accepted. If not specified, the audiences in JWT will not be checked. -* *local_jwks*: fetch JWKS in local data source, either in a local file or embedded in the inline string. -* *remote_jwks*: fetch JWKS from a remote HTTP server, also specify cache duration. -* *forward*: if true, JWT will be forwarded to the upstream. -* *from_headers*: extract JWT from HTTP headers. -* *from_params*: extract JWT from query parameters. -* *from_cookies*: extract JWT from HTTP request cookies. -* *forward_payload_header*: forward the JWT payload in the specified HTTP header. -* *claim_to_headers*: copy JWT claim to HTTP header. -* *jwt_cache_config*: Enables JWT cache, its size can be specified by *jwt_cache_size*. Only valid JWT tokens are cached. +* ``local_jwks``: fetch JWKS in local data source, either in a local file or embedded in the inline string. +* ``remote_jwks``: fetch JWKS from a remote HTTP server, also specify cache duration. +* ``forward``: if true, JWT will be forwarded to the upstream. +* ``from_headers``: extract JWT from HTTP headers. +* ``from_params``: extract JWT from query parameters. +* ``from_cookies``: extract JWT from HTTP request cookies. +* ``forward_payload_header``: forward the JWT payload in the specified HTTP header. +* ``claim_to_headers``: copy JWT claim to HTTP header. +* ``jwt_cache_config``: Enables JWT cache, its size can be specified by ``jwt_cache_size``. Only valid JWT tokens are cached. Default Extract Location ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -53,7 +54,7 @@ If :ref:`from_headers -and query parameter key *access_token* as:: +and query parameter key ``access_token`` as:: /path?access_token= @@ -99,7 +100,7 @@ This means all of the following will return a JWT of ``eyJFbnZveSI6ICJyb2NrcyJ9. The header :ref:`name ` may be ``Authorization``. The :ref:`value_prefix ` must match exactly, i.e., case-sensitively. -If the :ref:`value_prefix ` is not found, the header is skipped: not considered as a source for a JWT token. +If the :ref:`value_prefix ` is not found, the header is skipped, not considered as a source for a JWT token. If there are no JWT-legal characters after the :ref:`value_prefix `, the entire string after it is taken to be the JWT token. This is unlikely to succeed; the error will reported by the JWT parser. @@ -163,11 +164,15 @@ Another config example using inline JWKS: forward: true forward_payload_header: x-jwt-payload -Above example uses config inline string to specify JWKS. The JWT token will be extracted from HTTP headers as:: +Above example uses config inline string to specify JWKS. The JWT token will be extracted from HTTP headers as: - jwt-assertion: . +.. code-block:: + + jwt-assertion: -JWT payload will be added to the request header as following format:: +JWT payload will be added to the request header as following format: + +.. code-block:: x-jwt-payload: base64url_encoded(jwt_payload_in_JSON) @@ -176,12 +181,12 @@ RequirementRule :ref:`RequirementRule ` has two fields: -* Field *match* specifies how a request can be matched; e.g. by HTTP headers, or by query parameters, or by path prefixes. -* Field *requires* specifies the JWT requirement, e.g. which provider is required. +* Field ``match`` specifies how a request can be matched; e.g. by HTTP headers, or by query parameters, or by path prefixes. +* Field ``requires`` specifies the JWT requirement, e.g. which provider is required. .. important:: - **If a request matches multiple rules, the first matched rule will apply**. - - If the matched rule has empty *requires* field, **JWT verification is not required**. + - If the matched rule has empty ``requires`` field, **JWT verification is not required**. - If a request doesn't match any rules, **JWT verification is not required**. Single requirement config example @@ -245,8 +250,8 @@ Group requirement config example Above config uses more complex *group* requirements: -* The first *rule* specifies *requires_any*; if any of **provider1** or **provider2** requirement is satisfied, the request is OK to proceed. -* The second *rule* specifies *requires_all*; only if both **provider1** and **provider2** requirements are satisfied, the request is OK to proceed. +* The first *rule* specifies ``requires_any``; if any of ``provider1`` or ``provider2`` requirement is satisfied, the request is OK to proceed. +* The second *rule* specifies ``requires_all``; only if both ``provider1`` and ``provider2`` requirements are satisfied, the request is OK to proceed. Copy validated JWT claims to HTTP request headers example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -256,8 +261,8 @@ If a JWT is valid, you can add some of its claims of type (string, integer, bool The field :ref:`claim_to_headers ` is a repeat of message :ref:`JWTClaimToHeader ` which has two fields: -* Field *header_name* specifies the name of new http header reserved for jwt claim. If this header is already present with some other value then it will be replaced with the claim value. If the claim value doesn't exist then this header wouldn't be available for any other value. -* Field *claim_name* specifies the claim from verified jwt token. +* Field ``header_name`` specifies the name of new http header reserved for jwt claim. If this header is already present with some other value then it will be replaced with the claim value. If the claim value doesn't exist then this header wouldn't be available for any other value. +* Field ``claim_name`` specifies the claim from verified jwt token. .. code-block:: yaml @@ -270,6 +275,9 @@ The field :ref:`claim_to_headers x-jwt-claim-nested-key: diff --git a/docs/root/configuration/http/http_filters/language_filter.rst b/docs/root/configuration/http/http_filters/language_filter.rst index 1bcfc92f0468..6dfcd31a910a 100644 --- a/docs/root/configuration/http/http_filters/language_filter.rst +++ b/docs/root/configuration/http/http_filters/language_filter.rst @@ -22,10 +22,10 @@ Language The language detection filter (i18n) picks the best match between the desired locales of a client and an application's supported locales and adds a new ``x-language`` header to the request containing an IETF BCP 47 language tag. -The filter parses a list of locales from an ``Accept-Language`` header [RFC 2616 Section 14.4](https://tools.ietf.org/html/rfc2616#section-14.4) +The filter parses a list of locales from an ``Accept-Language`` header `RFC 2616 Section 14.4 `_ to match the desired locale of a client. -[Unicode ICU](https://github.com/unicode-org) is used for ``Accept-Language`` header parsing. +`Unicode ICU `_ is used for ``Accept-Language`` header parsing. .. code-block:: yaml @@ -33,7 +33,7 @@ to match the desired locale of a client. .. code-block:: yaml - // Multiple types, weighted with the quality value syntax: + # Multiple types, weighted with the quality value syntax: Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5 * The client is from the Romandy region and prefers Swiss French, the variety of French spoken in the French-speaking area of Switzerland @@ -65,19 +65,18 @@ Full filter configuration: The above configuration can be understood as follows: * Try to pick the client's desired locale from an ``Accept-Language`` header -* At any point the filter uses [Unicode ICU](https://github.com/unicode-org) for locale parsing +* At any point the filter uses `Unicode ICU `_ for locale parsing * If the client's desired locale can not be picked, for example because the client provided an invalid value, the :ref:`default_language ` option will be used as a fallback Statistics ---------- -The language detection filter outputs statistics in the *http..language.* namespace. The -| :ref:`stat prefix ` +The language detection filter outputs statistics in the ``http..language.`` namespace. The :ref:`stat prefix ` comes from the owning HTTP connection manager. .. csv-table:: :header: Name, Type, Description :widths: 1, 1, 2 - header, Counter, Number of requests for which the language from the Accept-Language header ([RFC 2616 Section 14.4](https://tools.ietf.org/html/rfc2616#section-14.4)) was matched + header, Counter, Number of requests for which the language from the Accept-Language header (`RFC 2616 Section 14.4 `_) was matched default_language, Counter, Number of requests for which the default language was used (fallback) diff --git a/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst b/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst index 70fc57fd3d74..02e82205ddda 100644 --- a/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst +++ b/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst @@ -102,7 +102,7 @@ cluster "foo" for "/foo/bar2" path, then 100 req/min are allowed. Otherwise, Statistics ---------- -The local rate limit filter outputs statistics in the *.http_local_rate_limit.* namespace. +The local rate limit filter outputs statistics in the ``.http_local_rate_limit.`` namespace. 429 responses -- or the configured status code -- are emitted to the normal cluster :ref:`dynamic HTTP statistics `. diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 52ac3e2d615e..925320330cf5 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -26,10 +26,12 @@ The design of the filter and Lua support at a high level is as follows: Currently supported high level features --------------------------------------- -**NOTE:** It is expected that this list will expand over time as the filter is used in production. -The API surface has been kept small on purpose. The goal is to make scripts extremely simple and -safe to write. Very complex or high performance use cases are assumed to use the native C++ filter -API. +.. note:: + + It is expected that this list will expand over time as the filter is used in production. + The API surface has been kept small on purpose. The goal is to make scripts extremely simple and + safe to write. Very complex or high performance use cases are assumed to use the native C++ filter + API. * Inspection of headers, body, and trailers while streaming in either the request flow, response flow, or both. @@ -147,7 +149,7 @@ Statistics ---------- .. _config_http_filters_lua_stats: -The lua filter outputs statistics in the *.lua.* namespace by default. When +The lua filter outputs statistics in the ``.lua.`` namespace by default. When there are multiple lua filters configured in a filter chain, stats from individual filter instance/script can be tracked by providing a per filter :ref:`stat prefix diff --git a/docs/root/configuration/http/http_filters/oauth2_filter.rst b/docs/root/configuration/http/http_filters/oauth2_filter.rst index ad20273eda46..3c1a3975affc 100644 --- a/docs/root/configuration/http/http_filters/oauth2_filter.rst +++ b/docs/root/configuration/http/http_filters/oauth2_filter.rst @@ -16,7 +16,7 @@ The OAuth filter's flow involves: are sent as query string parameters in this first redirect. * After a successful login, the authn server should be configured to redirect the user back to the :ref:`redirect_uri ` - provided in the query string in the first step. In the below code example, we choose /callback as the configured match path. + provided in the query string in the first step. In the below code example, we choose ``/callback`` as the configured match path. An "authorization grant" is included in the query string for this second redirect. * Using this new grant and the :ref:`token_secret `, the filter then attempts to retrieve an access token from @@ -46,11 +46,11 @@ However the encoded character sequences that represent ASCII control characters decoded. The characters without defined meaning in URL according to `RFC 3986 `_ are also left undecoded. Specifically the following characters are left in the encoded form: -* Control characters with values less than or equal 0x1F -* Space (0x20) -* DEL character (0x7F) -* Extended ASCII characters with values grteater than or equal 0x80 -* Characters without defined meaning in URL: `"<>\^{}|` +* Control characters with values less than or equal ``0x1F`` +* Space (``0x20``) +* DEL character (``0x7F``) +* Extended ASCII characters with values greater than or equal ``0x80`` +* Characters without defined meaning in URL: ``"<>\^{}|`` .. note:: By default, OAuth2 filter sets some cookies with the following names: @@ -251,7 +251,7 @@ Generally, allowlisting is inadvisable from a security standpoint. Statistics ---------- -The OAuth2 filter outputs statistics in the *.* namespace. +The OAuth2 filter outputs statistics in the ``.`` namespace. .. csv-table:: :header: Name, Type, Description diff --git a/docs/root/configuration/http/http_filters/original_src_filter.rst b/docs/root/configuration/http/http_filters/original_src_filter.rst index 97b66d93ed6c..232f45a40a0c 100644 --- a/docs/root/configuration/http/http_filters/original_src_filter.rst +++ b/docs/root/configuration/http/http_filters/original_src_filter.rst @@ -4,9 +4,9 @@ Original Source =============== * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.original_src.v3.OriginalSrc``. -* :ref:`HTTP filter v3 API reference ` +* :ref:`v3 API reference ` -The original source http filter replicates the downstream remote address of the connection on +The original source HTTP filter replicates the downstream remote address of the connection on the upstream side of Envoy. For example, if a downstream connection connects to Envoy with IP address ``10.1.2.3``, then Envoy will connect to the upstream with source IP ``10.1.2.3``. The downstream remote address is determined based on the logic for the "trusted client address" diff --git a/docs/root/configuration/http/http_filters/rate_limit_filter.rst b/docs/root/configuration/http/http_filters/rate_limit_filter.rst index b016b3f9e8bb..5e62c2b49b6d 100644 --- a/docs/root/configuration/http/http_filters/rate_limit_filter.rst +++ b/docs/root/configuration/http/http_filters/rate_limit_filter.rst @@ -158,7 +158,7 @@ no entry is added. Statistics ---------- -The rate limit filter outputs statistics in the *cluster..ratelimit.* namespace. +The rate limit filter outputs statistics in the ``cluster..ratelimit.`` namespace. 429 responses or the configured :ref:`rate_limited_status ` are emitted to the normal cluster :ref:`dynamic HTTP statistics `. diff --git a/docs/root/configuration/http/http_filters/rbac_filter.rst b/docs/root/configuration/http/http_filters/rbac_filter.rst index 7ed505ac8c97..db30b5123a01 100644 --- a/docs/root/configuration/http/http_filters/rbac_filter.rst +++ b/docs/root/configuration/http/http_filters/rbac_filter.rst @@ -29,12 +29,12 @@ the virtual host, route, or weighted cluster. Statistics ---------- -The RBAC filter outputs statistics in the *http..rbac.* namespace. The :ref:`stat prefix +The RBAC filter outputs statistics in the ``http..rbac.`` namespace. The :ref:`stat prefix ` comes from the owning HTTP connection manager. For the shadow rule statistics ``shadow_allowed`` and ``shadow_denied``, the :ref:`shadow_rules_stat_prefix ` -can be used to add an extra prefix to output the statistics in the *http..rbac..* namespace. +can be used to add an extra prefix to output the statistics in the ``http..rbac..`` namespace. .. csv-table:: :header: Name, Type, Description @@ -63,4 +63,4 @@ can be used to add an extra prefix to the corresponding dynamic metadata key. shadow_effective_policy_id, string, The effective shadow policy ID matching the action (if any). shadow_engine_result, string, The engine result for the shadow rules (i.e. either ``allowed`` or ``denied``). - access_log_hint, boolean, Whether the request should be logged. This metadata is shared and set under the key namespace 'envoy.common' (See :ref:`Shared Dynamic Metadata`). + access_log_hint, boolean, Whether the request should be logged. This metadata is shared and set under the key namespace ``envoy.common`` (See :ref:`Shared Dynamic Metadata`). diff --git a/docs/root/configuration/http/http_filters/router_filter.rst b/docs/root/configuration/http/http_filters/router_filter.rst index c36adbc92fa6..d354df7c7c25 100644 --- a/docs/root/configuration/http/http_filters/router_filter.rst +++ b/docs/root/configuration/http/http_filters/router_filter.rst @@ -436,7 +436,7 @@ Virtual Clusters ^^^^^^^^^^^^^^^^ Virtual cluster statistics are output in the -*vhost..vcluster..* namespace and include the following +``vhost..vcluster..`` namespace and include the following statistics: .. csv-table:: diff --git a/docs/root/configuration/http/http_filters/sxg_filter.rst b/docs/root/configuration/http/http_filters/sxg_filter.rst index edd012eff687..ec04964560f2 100644 --- a/docs/root/configuration/http/http_filters/sxg_filter.rst +++ b/docs/root/configuration/http/http_filters/sxg_filter.rst @@ -63,7 +63,7 @@ Instructions for generating a self-signed certificate and private key for testin Statistics ---------- -The SXG filter outputs statistics in the *.sxg.* namespace. +The SXG filter outputs statistics in the ``.sxg.`` namespace. .. csv-table:: :header: Name, Type, Description diff --git a/docs/root/configuration/http/http_filters/tap_filter.rst b/docs/root/configuration/http/http_filters/tap_filter.rst index 81e20e25a916..776ed19d5992 100644 --- a/docs/root/configuration/http/http_filters/tap_filter.rst +++ b/docs/root/configuration/http/http_filters/tap_filter.rst @@ -307,7 +307,7 @@ Etc. Statistics ---------- -The tap filter outputs statistics in the *http..tap.* namespace. The :ref:`stat prefix +The tap filter outputs statistics in the ``http..tap.`` namespace. The :ref:`stat prefix ` comes from the owning HTTP connection manager. diff --git a/docs/root/configuration/listeners/stats.rst b/docs/root/configuration/listeners/stats.rst index 42200239aa08..288a23344592 100644 --- a/docs/root/configuration/listeners/stats.rst +++ b/docs/root/configuration/listeners/stats.rst @@ -26,6 +26,7 @@ with the following statistics: downstream_pre_cx_timeout, Counter, Sockets that timed out during listener filter processing downstream_pre_cx_active, Gauge, Sockets currently undergoing listener filter processing extension_config_missing, Counter, Total connections closed due to missing listener filter extension configuration + network_extension_config_missing, Counter, Total connections closed due to missing network filter extension configuration global_cx_overflow, Counter, Total connections rejected due to enforcement of the global connection limit no_filter_chain_match, Counter, Total connections that didn't match any filter chain downstream_listener_filter_remote_close, Counter, Total connections closed by remote when peek data for listener filters diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index 5815ba665fc2..f8114386fa1a 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -450,41 +450,52 @@ The following command operators are supported: .. _config_access_log_format_response_flags: -%RESPONSE_FLAGS% +%RESPONSE_FLAGS% / %RESPONSE_FLAGS_LONG% Additional details about the response or connection, if any. For TCP connections, the response codes mentioned in - the descriptions do not apply. Possible values are: - - HTTP and TCP - * **UH**: No healthy upstream hosts in upstream cluster in addition to 503 response code. - * **UF**: Upstream connection failure in addition to 503 response code. - * **UO**: Upstream overflow (:ref:`circuit breaking `) in addition to 503 response code. - * **NR**: No :ref:`route configured ` for a given request in addition to 404 response code, or no matching filter chain for a downstream connection. - * **URX**: The request was rejected because the :ref:`upstream retry limit (HTTP) ` or :ref:`maximum connect attempts (TCP) ` was reached. - * **NC**: Upstream cluster not found. - * **DT**: When a request or connection exceeded :ref:`max_connection_duration ` or :ref:`max_downstream_connection_duration `. - HTTP only - * **DC**: Downstream connection termination. - * **LH**: Local service failed :ref:`health check request ` in addition to 503 response code. - * **UT**: Upstream request timeout in addition to 504 response code. - * **LR**: Connection local reset in addition to 503 response code. - * **UR**: Upstream remote reset in addition to 503 response code. - * **UC**: Upstream connection termination in addition to 503 response code. - * **DI**: The request processing was delayed for a period specified via :ref:`fault injection `. - * **FI**: The request was aborted with a response code specified via :ref:`fault injection `. - * **RL**: The request was ratelimited locally by the :ref:`HTTP rate limit filter ` in addition to 429 response code. - * **UAEX**: The request was denied by the external authorization service. - * **RLSE**: The request was rejected because there was an error in rate limit service. - * **IH**: The request was rejected because it set an invalid value for a - :ref:`strictly-checked header ` in addition to 400 response code. - * **SI**: Stream idle timeout in addition to 408 or 504 response code. - * **DPE**: The downstream request had an HTTP protocol error. - * **UPE**: The upstream response had an HTTP protocol error. - * **UMSDR**: The upstream request reached max stream duration. - * **OM**: Overload Manager terminated the request. - * **DF**: The request was terminated due to DNS resolution failure. - - UDP - Not implemented ("-"). + the descriptions do not apply. %RESPONSE_FLAGS% will outout a short string. %RESPONSE_FLAGS% will outout a Pascal case string. + Possible values are: + +HTTP and TCP + +.. csv-table:: + :header: Long name, Short name, Description + :widths: 1, 1, 3 + + **NoHealthyUpstream**, **UH**, No healthy upstream hosts in upstream cluster in addition to 503 response code. + **UpstreamConnectionFailure**, **UF**, Upstream connection failure in addition to 503 response code. + **UpstreamOverflow**, **UO**, Upstream overflow (:ref:`circuit breaking `) in addition to 503 response code. + **NoRouteFound**, **NR**, No :ref:`route configured ` for a given request in addition to 404 response code or no matching filter chain for a downstream connection. + **UpstreamRetryLimitExceeded**, **URX**, The request was rejected because the :ref:`upstream retry limit (HTTP) ` or :ref:`maximum connect attempts (TCP) ` was reached. + **NoClusterFound**, **NC**, Upstream cluster not found. + **DurationTimeout**, **DT**, When a request or connection exceeded :ref:`max_connection_duration ` or :ref:`max_downstream_connection_duration `. + +HTTP only + +.. csv-table:: + :header: Long name, Short name, Description + :widths: 1, 1, 3 + + **DownstreamConnectionTermination**, **DC**, Downstream connection termination. + **FailedLocalHealthCheck**, **LH**, Local service failed :ref:`health check request ` in addition to 503 response code. + **UpstreamRequestTimeout**, **UT**, Upstream request timeout in addition to 504 response code. + **LocalReset**, **LR**, Connection local reset in addition to 503 response code. + **UpstreamRemoteReset**, **UR**, Upstream remote reset in addition to 503 response code. + **UpstreamConnectionTermination**, **UC**, Upstream connection termination in addition to 503 response code. + **DelayInjected**, **DI**, The request processing was delayed for a period specified via :ref:`fault injection `. + **FaultInjected**, **FI**, The request was aborted with a response code specified via :ref:`fault injection `. + **RateLimited**, **RL**, The request was ratelimited locally by the :ref:`HTTP rate limit filter ` in addition to 429 response code. + **UnauthorizedExternalService**, **UAEX**, The request was denied by the external authorization service. + **RateLimitServiceError**, **RLSE**, The request was rejected because there was an error in rate limit service. + **InvalidEnvoyRequestHeaders**, **IH**, The request was rejected because it set an invalid value for a :ref:`strictly-checked header ` in addition to 400 response code. + **StreamIdleTimeout**, **SI**, Stream idle timeout in addition to 408 or 504 response code. + **DownstreamProtocolError**, **DPE**, The downstream request had an HTTP protocol error. + **UpstreamProtocolError**, **UPE**, The upstream response had an HTTP protocol error. + **UpstreamMaxStreamDurationReached**, **UMSDR**, The upstream request reached max stream duration. + **OverloadManagerTerminated**, **OM**, Overload Manager terminated the request. + **DnsResolutionFailed**, **DF**, The request was terminated due to DNS resolution failure. + +UDP + Not implemented ("-"). %ROUTE_NAME% HTTP/TCP diff --git a/docs/root/configuration/overview/extension.rst b/docs/root/configuration/overview/extension.rst index 578de372caba..bca5169fb0a2 100644 --- a/docs/root/configuration/overview/extension.rst +++ b/docs/root/configuration/overview/extension.rst @@ -88,3 +88,4 @@ configuration, rooted at listener.
(or listener.. if stat_ :widths: 1, 1, 2 extension_config_missing, Counter, Total connections closed due to missing listener filter extension configuration + network_extension_config_missing, Counter, Total connections closed due to missing network filter extension configuration diff --git a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst index 3dd58c0d7a0c..7deafe39415b 100644 --- a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst +++ b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst @@ -28,6 +28,17 @@ upstreams and control plane xDS clusters. active_clusters, Gauge, Number of currently active (warmed) clusters warming_clusters, Gauge, Number of currently warming (not active) clusters + +In addition to the cluster manager stats, there are per worker thread local +cluster manager statistics tree rooted at +*thread_local_cluster_manager..* with the following statistics. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + clusters_inflated, Gauge, Number of clusters the worker has initialized. If using cluster deferral this number should be <= (cluster_added - clusters_removed). + .. _config_cluster_stats: Every cluster has a statistics tree rooted at *cluster..* with the following statistics: diff --git a/docs/root/intro/arch_overview/http/upgrades.rst b/docs/root/intro/arch_overview/http/upgrades.rst index 88ab933de74a..f32bfe9e919f 100644 --- a/docs/root/intro/arch_overview/http/upgrades.rst +++ b/docs/root/intro/arch_overview/http/upgrades.rst @@ -177,7 +177,7 @@ An example set up proxying HTTP would look something like this: .. code-block:: console $ envoy -c configs/encapsulate_in_http1_connect.yaml --base-id 1 - $ envoy -c configs/terminate_connect.yaml --base-id 1 + $ envoy -c configs/terminate_http1_connect.yaml --base-id 1 For HTTP/2 ``CONNECT``, try either: diff --git a/docs/root/intro/arch_overview/upstream/outlier.rst b/docs/root/intro/arch_overview/upstream/outlier.rst index 467bcd5b1f89..5da8a3ac4f12 100644 --- a/docs/root/intro/arch_overview/upstream/outlier.rst +++ b/docs/root/intro/arch_overview/upstream/outlier.rst @@ -64,8 +64,7 @@ consecutive 5xx) or at a specified interval (for example in the case of periodic ejection algorithm works as follows: #. A host is determined to be an outlier. -#. If no hosts have been ejected, Envoy will eject the host immediately. Otherwise, it checks to make - sure the number of ejected hosts is below the allowed threshold (specified via the +#. It checks to make sure the number of ejected hosts is below the allowed threshold (specified via the :ref:`outlier_detection.max_ejection_percent` setting). If the number of ejected hosts is above the threshold, the host is not ejected. #. The host is ejected for some number of milliseconds. Ejection means that the host is marked diff --git a/envoy/common/BUILD b/envoy/common/BUILD index 1a71c7f60968..e8cb6b47e2ff 100644 --- a/envoy/common/BUILD +++ b/envoy/common/BUILD @@ -43,6 +43,7 @@ envoy_cc_library( envoy_cc_library( name = "resource_interface", hdrs = ["resource.h"], + deps = [":base_includes"], ) envoy_cc_library( diff --git a/envoy/common/io/BUILD b/envoy/common/io/BUILD new file mode 100644 index 000000000000..5e082e96ad97 --- /dev/null +++ b/envoy/common/io/BUILD @@ -0,0 +1,20 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_library( + name = "io_uring_interface", + hdrs = [ + "io_uring.h", + ], + deps = [ + "//envoy/buffer:buffer_interface", + "//envoy/network:address_interface", + ], +) diff --git a/source/common/io/io_uring.h b/envoy/common/io/io_uring.h similarity index 64% rename from source/common/io/io_uring.h rename to envoy/common/io/io_uring.h index c664cd4630a0..53aaed54d35d 100644 --- a/source/common/io/io_uring.h +++ b/envoy/common/io/io_uring.h @@ -1,19 +1,35 @@ #pragma once -#include "envoy/common/pure.h" +#include -#include "source/common/network/address_impl.h" +#include "envoy/common/pure.h" +#include "envoy/network/address.h" namespace Envoy { namespace Io { +/** + * Abstract for io_uring I/O Request. + */ +class Request { +public: + virtual ~Request() = default; +}; + /** * Callback invoked when iterating over entries in the completion queue. * @param user_data is any data attached to an entry submitted to the submission * queue. * @param result is a return code of submitted system call. + * @param injected indicates whether the completion is injected or not. */ -using CompletionCb = std::function; +using CompletionCb = std::function; + +/** + * Callback for releasing the user data. + * @param user_data the pointer to the user data. + */ +using InjectedCompletionUserDataReleasor = std::function; enum class IoUringResult { Ok, Busy, Failed }; @@ -44,7 +60,7 @@ class IoUring { * Iterates over entries in the completion queue, calls the given callback for * every entry and marks them consumed. */ - virtual void forEveryCompletion(CompletionCb completion_cb) PURE; + virtual void forEveryCompletion(const CompletionCb& completion_cb) PURE; /** * Prepares an accept system call and puts it into the submission queue. @@ -52,7 +68,7 @@ class IoUring { * and IoUringResult::Ok otherwise. */ virtual IoUringResult prepareAccept(os_fd_t fd, struct sockaddr* remote_addr, - socklen_t* remote_addr_len, void* user_data) PURE; + socklen_t* remote_addr_len, Request* user_data) PURE; /** * Prepares a connect system call and puts it into the submission queue. @@ -61,7 +77,7 @@ class IoUring { */ virtual IoUringResult prepareConnect(os_fd_t fd, const Network::Address::InstanceConstSharedPtr& address, - void* user_data) PURE; + Request* user_data) PURE; /** * Prepares a readv system call and puts it into the submission queue. @@ -69,7 +85,7 @@ class IoUring { * and IoUringResult::Ok otherwise. */ virtual IoUringResult prepareReadv(os_fd_t fd, const struct iovec* iovecs, unsigned nr_vecs, - off_t offset, void* user_data) PURE; + off_t offset, Request* user_data) PURE; /** * Prepares a writev system call and puts it into the submission queue. @@ -77,14 +93,14 @@ class IoUring { * and IoUringResult::Ok otherwise. */ virtual IoUringResult prepareWritev(os_fd_t fd, const struct iovec* iovecs, unsigned nr_vecs, - off_t offset, void* user_data) PURE; + off_t offset, Request* user_data) PURE; /** * Prepares a close system call and puts it into the submission queue. * Returns IoUringResult::Failed in case the submission queue is full already * and IoUringResult::Ok otherwise. */ - virtual IoUringResult prepareClose(os_fd_t fd, void* user_data) PURE; + virtual IoUringResult prepareClose(os_fd_t fd, Request* user_data) PURE; /** * Submits the entries in the submission queue to the kernel using the @@ -95,8 +111,29 @@ class IoUring { * with the forEveryCompletion() method and try again. */ virtual IoUringResult submit() PURE; + + /** + * Inject a request completion into the io_uring. Those completions will be iterated + * when calling the `forEveryCompletion`. This is used to inject an emulated iouring + * request completion by the upper-layer, then trigger the request completion processing. + * it is used to emulate an activation of READ/WRITE/CLOSED event on the specific file + * descriptor by the IoSocketHandle. + * @param fd is the file descriptor of this completion refer to. + * @param user_data is the user data related to this completion. + * @param result is request result for this completion. + */ + virtual void injectCompletion(os_fd_t fd, Request* user_data, int32_t result) PURE; + + /** + * Remove all the injected completions for the specific file descriptor. This is used + * to cleanup all the injected completions when a socket closed and remove from the iouring. + * @param fd is used to refer to the completions will be removed. + */ + virtual void removeInjectedCompletion(os_fd_t fd) PURE; }; +using IoUringPtr = std::unique_ptr; + /** * Abstract factory for IoUring wrappers. */ diff --git a/envoy/config/BUILD b/envoy/config/BUILD index 1944cb4ff14d..5ef3fea5f5cf 100644 --- a/envoy/config/BUILD +++ b/envoy/config/BUILD @@ -66,6 +66,7 @@ envoy_cc_library( name = "grpc_mux_interface", hdrs = ["grpc_mux.h"], deps = [ + ":eds_resources_cache_interface", ":subscription_interface", "//envoy/stats:stats_macros", "//source/common/common:cleanup_lib", diff --git a/envoy/config/grpc_mux.h b/envoy/config/grpc_mux.h index e945fe5e3d32..a08724fcdf6c 100644 --- a/envoy/config/grpc_mux.h +++ b/envoy/config/grpc_mux.h @@ -4,6 +4,7 @@ #include "envoy/common/exception.h" #include "envoy/common/pure.h" +#include "envoy/config/eds_resources_cache.h" #include "envoy/config/subscription.h" #include "envoy/stats/stats_macros.h" @@ -105,6 +106,12 @@ class GrpcMux { virtual void requestOnDemandUpdate(const std::string& type_url, const absl::flat_hash_set& for_update) PURE; + + /** + * Returns an EdsResourcesCache for this GrpcMux if there is one. + * @return EdsResourcesCacheOptRef optional eds resources cache for the gRPC-mux. + */ + virtual EdsResourcesCacheOptRef edsResourcesCache() PURE; }; using GrpcMuxPtr = std::unique_ptr; diff --git a/envoy/config/subscription_factory.h b/envoy/config/subscription_factory.h index c59a648a44d3..de9d02100ef3 100644 --- a/envoy/config/subscription_factory.h +++ b/envoy/config/subscription_factory.h @@ -121,7 +121,7 @@ class MuxFactory : public Config::UntypedFactory { const LocalInfo::LocalInfo& local_info, std::unique_ptr&& config_validators, BackOffStrategyPtr&& backoff_strategy, OptRef xds_config_tracker, - OptRef xds_resources_delegate) PURE; + OptRef xds_resources_delegate, bool use_eds_resources_cache) PURE; }; } // namespace Config diff --git a/envoy/config/typed_config.h b/envoy/config/typed_config.h index 6e20b8cc60f0..b843525f9d2b 100644 --- a/envoy/config/typed_config.h +++ b/envoy/config/typed_config.h @@ -52,7 +52,8 @@ class TypedFactory : public UntypedFactory { std::set configTypes() override { auto ptr = createEmptyConfigProto(); ASSERT(ptr != nullptr); - return {ptr->GetDescriptor()->full_name()}; + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*ptr); + return {reflectable_message->GetDescriptor()->full_name()}; } }; diff --git a/envoy/filter/BUILD b/envoy/filter/BUILD index 1ecbfc0ab243..0598ea9a2297 100644 --- a/envoy/filter/BUILD +++ b/envoy/filter/BUILD @@ -15,6 +15,7 @@ envoy_cc_library( "//envoy/config:dynamic_extension_config_provider_interface", "//envoy/init:manager_interface", "//envoy/server:filter_config_interface", + "//envoy/upstream:cluster_manager_interface", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) diff --git a/envoy/filter/config_provider_manager.h b/envoy/filter/config_provider_manager.h index fbf12d3cd353..39fe6273e5a8 100644 --- a/envoy/filter/config_provider_manager.h +++ b/envoy/filter/config_provider_manager.h @@ -4,6 +4,7 @@ #include "envoy/config/dynamic_extension_config_provider.h" #include "envoy/init/manager.h" #include "envoy/server/filter_config.h" +#include "envoy/upstream/cluster_manager.h" #include "absl/types/optional.h" @@ -50,7 +51,8 @@ template class FilterConfigProviderManager { const envoy::config::core::v3::ExtensionConfigSource& config_source, const std::string& filter_config_name, Server::Configuration::ServerFactoryContext& server_context, FactoryCtx& factory_context, - bool last_filter_in_filter_chain, const std::string& filter_chain_type, + Upstream::ClusterManager& cluster_manager, bool last_filter_in_filter_chain, + const std::string& filter_chain_type, const Network::ListenerFilterMatcherSharedPtr& listener_filter_matcher) PURE; /** diff --git a/envoy/http/header_map.h b/envoy/http/header_map.h index 44dddc49d52f..c4eb4ea915ca 100644 --- a/envoy/http/header_map.h +++ b/envoy/http/header_map.h @@ -101,7 +101,14 @@ using LowerCaseStrPairVector = class HeaderStringValidator { public: - bool operator()(absl::string_view view) { return validHeaderString(view); } + bool operator()(absl::string_view view) { + return disable_validation_for_tests_ ? true : validHeaderString(view); + } + + // This flag allows disabling the check for the NUL, CR and LF characters in the + // header names or values in the DEBUG builds to prevent the `ASSERT(valid())` in the + // HeaderString constructor from failing tests. + static bool disable_validation_for_tests_; }; class HeaderString : public UnionStringBase { diff --git a/envoy/http/header_validator_errors.h b/envoy/http/header_validator_errors.h index 0703d3872970..201ae7d29e29 100644 --- a/envoy/http/header_validator_errors.h +++ b/envoy/http/header_validator_errors.h @@ -22,6 +22,7 @@ struct UhvResponseCodeDetailValues { const std::string InvalidHostDeprecatedUserInfo = "uhv.invalid_host_deprecated_user_info"; const std::string FragmentInUrlPath = "uhv.fragment_in_url_path"; const std::string EscapedSlashesInPath = "uhv.escaped_slashes_in_url_path"; + const std::string Percent00InPath = "uhv.percent_00_in_url_path"; }; using UhvResponseCodeDetail = ConstSingleton; diff --git a/envoy/server/BUILD b/envoy/server/BUILD index 6361b6958a9f..eb2bd7f99c3e 100644 --- a/envoy/server/BUILD +++ b/envoy/server/BUILD @@ -167,6 +167,7 @@ envoy_cc_library( name = "watchdog_interface", hdrs = ["watchdog.h"], deps = [ + "//envoy/common:base_includes", "//envoy/thread:thread_interface", ], ) diff --git a/envoy/server/filter_config.h b/envoy/server/filter_config.h index 4ce60c523085..09e9f5727342 100644 --- a/envoy/server/filter_config.h +++ b/envoy/server/filter_config.h @@ -227,7 +227,7 @@ class HttpFilterConfigFactoryBase : public ProtocolOptionsFactory { auto config_types = TypedFactory::configTypes(); if (auto message = createEmptyRouteConfigProto(); message != nullptr) { - config_types.insert(message->GetDescriptor()->full_name()); + config_types.insert(createReflectableMessage(*message)->GetDescriptor()->full_name()); } return config_types; diff --git a/envoy/stream_info/BUILD b/envoy/stream_info/BUILD index ca692a7ceb45..49014a375d98 100644 --- a/envoy/stream_info/BUILD +++ b/envoy/stream_info/BUILD @@ -34,6 +34,7 @@ envoy_cc_library( hdrs = ["filter_state.h"], external_deps = ["abseil_optional"], deps = [ + "//source/common/common:fmt_lib", "//source/common/common:utility_lib", "//source/common/protobuf", ], diff --git a/envoy/upstream/cluster_manager.h b/envoy/upstream/cluster_manager.h index 319cbec9c754..538ced140360 100644 --- a/envoy/upstream/cluster_manager.h +++ b/envoy/upstream/cluster_manager.h @@ -13,6 +13,7 @@ #include "envoy/config/core/v3/address.pb.h" #include "envoy/config/core/v3/config_source.pb.h" #include "envoy/config/core/v3/protocol.pb.h" +#include "envoy/config/eds_resources_cache.h" #include "envoy/config/grpc_mux.h" #include "envoy/config/subscription_factory.h" #include "envoy/grpc/async_client_manager.h" @@ -43,6 +44,7 @@ namespace Upstream { * ClusterUpdateCallbacks provide a way to expose Cluster lifecycle events in the * ClusterManager. */ +using ThreadLocalClusterCommand = std::function; class ClusterUpdateCallbacks { public: virtual ~ClusterUpdateCallbacks() = default; @@ -50,11 +52,12 @@ class ClusterUpdateCallbacks { /** * onClusterAddOrUpdate is called when a new cluster is added or an existing cluster * is updated in the ClusterManager. - * @param cluster is the ThreadLocalCluster that represents the updated - * cluster. + * @param cluster_name the name of the changed cluster. + * @param get_cluster is a callable that will provide the ThreadLocalCluster that represents the + * updated cluster. It should be used within the call or discarded. */ - virtual void onClusterAddOrUpdate(ThreadLocalCluster& cluster) PURE; - + virtual void onClusterAddOrUpdate(absl::string_view cluster_name, + ThreadLocalClusterCommand& get_cluster) PURE; /** * onClusterRemoval is called when a cluster is removed; the argument is the cluster name. * @param cluster_name is the name of the removed cluster. @@ -444,6 +447,11 @@ class ClusterManager { virtual std::shared_ptr getCommonLbConfigPtr( const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_lb_config) PURE; + + /** + * Returns an EdsResourcesCache that is unique for the cluster manager. + */ + virtual Config::EdsResourcesCacheOptRef edsResourcesCache() PURE; }; using ClusterManagerPtr = std::unique_ptr; diff --git a/envoy/upstream/upstream.h b/envoy/upstream/upstream.h index a57a66cb8b4e..cc43c573c0f2 100644 --- a/envoy/upstream/upstream.h +++ b/envoy/upstream/upstream.h @@ -321,7 +321,10 @@ class HostsPerLocality { /** * @return const std::vector& list of hosts organized per * locality. The local locality is the first entry if - * hasLocalLocality() is true. + * hasLocalLocality() is true. All hosts within the same entry have the same locality + * and all hosts with a given locality are in the same entry. With the exception of + * the local locality entry (if present), all entries are sorted by locality with + * those considered less by the LocalityLess comparator ordered earlier in the list. */ virtual const std::vector& get() const PURE; @@ -622,6 +625,7 @@ class PrioritySet { #define ALL_CLUSTER_CONFIG_UPDATE_STATS(COUNTER, GAUGE, HISTOGRAM, TEXT_READOUT, STATNAME) \ COUNTER(assignment_stale) \ COUNTER(assignment_timeout_received) \ + COUNTER(assignment_use_cached) \ COUNTER(update_attempt) \ COUNTER(update_empty) \ COUNTER(update_failure) \ diff --git a/examples/golang-http/simple/filter.go b/examples/golang-http/simple/filter.go index 0a08ff62f0ef..5d162077c1c3 100644 --- a/examples/golang-http/simple/filter.go +++ b/examples/golang-http/simple/filter.go @@ -26,6 +26,8 @@ func (f *filter) sendLocalReplyInternal() api.StatusType { // Callbacks which are called in request path func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { f.path, _ = header.Get(":path") + api.LogDebugf("get path %s", f.path) + if f.path == "/localreply_by_config" { return f.sendLocalReplyInternal() } diff --git a/examples/golang-network/envoy.yaml b/examples/golang-network/envoy.yaml index 8c70ec813d32..4a96a9091f31 100644 --- a/examples/golang-network/envoy.yaml +++ b/examples/golang-network/envoy.yaml @@ -23,5 +23,3 @@ static_resources: - name: plainText type: ORIGINAL_DST lb_policy: CLUSTER_PROVIDED - original_dst_lb_config: - use_http_header: true diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index b2bada3a01ab..3f8ec6c5ca70 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -12,120 +12,120 @@ charset-normalizer==2.0.6 \ --hash=sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6 \ --hash=sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f # via requests -grpcio==1.56.2 \ - --hash=sha256:06e84ad9ae7668a109e970c7411e7992751a116494cba7c4fb877656527f9a57 \ - --hash=sha256:0ff789ae7d8ddd76d2ac02e7d13bfef6fc4928ac01e1dcaa182be51b6bcc0aaa \ - --hash=sha256:10954662f77dc36c9a1fb5cc4a537f746580d6b5734803be1e587252682cda8d \ - --hash=sha256:139f66656a762572ae718fa0d1f2dce47c05e9fbf7a16acd704c354405b97df9 \ - --hash=sha256:1c31e52a04e62c8577a7bf772b3e7bed4df9c9e0dd90f92b6ffa07c16cab63c9 \ - --hash=sha256:33971197c47965cc1d97d78d842163c283e998223b151bab0499b951fd2c0b12 \ - --hash=sha256:345356b307cce5d14355e8e055b4ca5f99bc857c33a3dc1ddbc544fca9cd0475 \ - --hash=sha256:373b48f210f43327a41e397391715cd11cfce9ded2fe76a5068f9bacf91cc226 \ - --hash=sha256:3ccb621749a81dc7755243665a70ce45536ec413ef5818e013fe8dfbf5aa497b \ - --hash=sha256:42a3bbb2bc07aef72a7d97e71aabecaf3e4eb616d39e5211e2cfe3689de860ca \ - --hash=sha256:42e63904ee37ae46aa23de50dac8b145b3596f43598fa33fe1098ab2cbda6ff5 \ - --hash=sha256:4eb37dd8dd1aa40d601212afa27ca5be255ba792e2e0b24d67b8af5e012cdb7d \ - --hash=sha256:51173e8fa6d9a2d85c14426bdee5f5c4a0654fd5fddcc21fe9d09ab0f6eb8b35 \ - --hash=sha256:5144feb20fe76e73e60c7d73ec3bf54f320247d1ebe737d10672480371878b48 \ - --hash=sha256:5344be476ac37eb9c9ad09c22f4ea193c1316bf074f1daf85bddb1b31fda5116 \ - --hash=sha256:6108e5933eb8c22cd3646e72d5b54772c29f57482fd4c41a0640aab99eb5071d \ - --hash=sha256:6a007a541dff984264981fbafeb052bfe361db63578948d857907df9488d8774 \ - --hash=sha256:6ee26e9dfb3996aff7c870f09dc7ad44a5f6732b8bdb5a5f9905737ac6fd4ef1 \ - --hash=sha256:750de923b456ca8c0f1354d6befca45d1f3b3a789e76efc16741bd4132752d95 \ - --hash=sha256:7c5ede2e2558f088c49a1ddda19080e4c23fb5d171de80a726b61b567e3766ed \ - --hash=sha256:830215173ad45d670140ff99aac3b461f9be9a6b11bee1a17265aaaa746a641a \ - --hash=sha256:8391cea5ce72f4a12368afd17799474015d5d3dc00c936a907eb7c7eaaea98a5 \ - --hash=sha256:8940d6de7068af018dfa9a959a3510e9b7b543f4c405e88463a1cbaa3b2b379a \ - --hash=sha256:89a49cc5ad08a38b6141af17e00d1dd482dc927c7605bc77af457b5a0fca807c \ - --hash=sha256:900bc0096c2ca2d53f2e5cebf98293a7c32f532c4aeb926345e9747452233950 \ - --hash=sha256:97e0efaebbfd222bcaac2f1735c010c1d3b167112d9d237daebbeedaaccf3d1d \ - --hash=sha256:9e04d4e4cfafa7c5264e535b5d28e786f0571bea609c3f0aaab13e891e933e9c \ - --hash=sha256:a4c60abd950d6de3e4f1ddbc318075654d275c29c846ab6a043d6ed2c52e4c8c \ - --hash=sha256:a6ff459dac39541e6a2763a4439c4ca6bc9ecb4acc05a99b79246751f9894756 \ - --hash=sha256:a72797549935c9e0b9bc1def1768c8b5a709538fa6ab0678e671aec47ebfd55e \ - --hash=sha256:af4063ef2b11b96d949dccbc5a987272f38d55c23c4c01841ea65a517906397f \ - --hash=sha256:b975b85d1d5efc36cf8b237c5f3849b64d1ba33d6282f5e991f28751317504a1 \ - --hash=sha256:bf0b9959e673505ee5869950642428046edb91f99942607c2ecf635f8a4b31c9 \ - --hash=sha256:c0c85c5cbe8b30a32fa6d802588d55ffabf720e985abe9590c7c886919d875d4 \ - --hash=sha256:c3f3237a57e42f79f1e560726576aedb3a7ef931f4e3accb84ebf6acc485d316 \ - --hash=sha256:c3fa3ab0fb200a2c66493828ed06ccd1a94b12eddbfb985e7fd3e5723ff156c6 \ - --hash=sha256:c435f5ce1705de48e08fcbcfaf8aee660d199c90536e3e06f2016af7d6a938dd \ - --hash=sha256:c90da4b124647547a68cf2f197174ada30c7bb9523cb976665dfd26a9963d328 \ - --hash=sha256:cbdf2c498e077282cd427cfd88bdce4668019791deef0be8155385ab2ba7837f \ - --hash=sha256:d1fbad1f9077372b6587ec589c1fc120b417b6c8ad72d3e3cc86bbbd0a3cee93 \ - --hash=sha256:d39f5d4af48c138cb146763eda14eb7d8b3ccbbec9fe86fb724cd16e0e914c64 \ - --hash=sha256:ddb4a6061933bd9332b74eac0da25f17f32afa7145a33a0f9711ad74f924b1b8 \ - --hash=sha256:ded637176addc1d3eef35331c39acc598bac550d213f0a1bedabfceaa2244c87 \ - --hash=sha256:f20fd21f7538f8107451156dd1fe203300b79a9ddceba1ee0ac8132521a008ed \ - --hash=sha256:fda2783c12f553cdca11c08e5af6eecbd717280dc8fbe28a110897af1c15a88c +grpcio==1.57.0 \ + --hash=sha256:00258cbe3f5188629828363ae8ff78477ce976a6f63fb2bb5e90088396faa82e \ + --hash=sha256:092fa155b945015754bdf988be47793c377b52b88d546e45c6a9f9579ac7f7b6 \ + --hash=sha256:0f80bf37f09e1caba6a8063e56e2b87fa335add314cf2b78ebf7cb45aa7e3d06 \ + --hash=sha256:20ec6fc4ad47d1b6e12deec5045ec3cd5402d9a1597f738263e98f490fe07056 \ + --hash=sha256:2313b124e475aa9017a9844bdc5eafb2d5abdda9d456af16fc4535408c7d6da6 \ + --hash=sha256:23e7d8849a0e58b806253fd206ac105b328171e01b8f18c7d5922274958cc87e \ + --hash=sha256:2f708a6a17868ad8bf586598bee69abded4996b18adf26fd2d91191383b79019 \ + --hash=sha256:2f7349786da979a94690cc5c2b804cab4e8774a3cf59be40d037c4342c906649 \ + --hash=sha256:34950353539e7d93f61c6796a007c705d663f3be41166358e3d88c45760c7d98 \ + --hash=sha256:40b72effd4c789de94ce1be2b5f88d7b9b5f7379fe9645f198854112a6567d9a \ + --hash=sha256:4b089f7ad1eb00a104078bab8015b0ed0ebcb3b589e527ab009c53893fd4e613 \ + --hash=sha256:4faea2cfdf762a664ab90589b66f416274887641ae17817de510b8178356bf73 \ + --hash=sha256:5371bcd861e679d63b8274f73ac281751d34bd54eccdbfcd6aa00e692a82cd7b \ + --hash=sha256:5613a2fecc82f95d6c51d15b9a72705553aa0d7c932fad7aed7afb51dc982ee5 \ + --hash=sha256:57b183e8b252825c4dd29114d6c13559be95387aafc10a7be645462a0fc98bbb \ + --hash=sha256:5b7a4ce8f862fe32b2a10b57752cf3169f5fe2915acfe7e6a1e155db3da99e79 \ + --hash=sha256:5e5b58e32ae14658085c16986d11e99abd002ddbf51c8daae8a0671fffb3467f \ + --hash=sha256:60fe15288a0a65d5c1cb5b4a62b1850d07336e3ba728257a810317be14f0c527 \ + --hash=sha256:6907b1cf8bb29b058081d2aad677b15757a44ef2d4d8d9130271d2ad5e33efca \ + --hash=sha256:76c44efa4ede1f42a9d5b2fed1fe9377e73a109bef8675fb0728eb80b0b8e8f2 \ + --hash=sha256:7a635589201b18510ff988161b7b573f50c6a48fae9cb567657920ca82022b37 \ + --hash=sha256:7b400807fa749a9eb286e2cd893e501b110b4d356a218426cb9c825a0474ca56 \ + --hash=sha256:82640e57fb86ea1d71ea9ab54f7e942502cf98a429a200b2e743d8672171734f \ + --hash=sha256:871f9999e0211f9551f368612460442a5436d9444606184652117d6a688c9f51 \ + --hash=sha256:9338bacf172e942e62e5889b6364e56657fbf8ac68062e8b25c48843e7b202bb \ + --hash=sha256:a8a8e560e8dbbdf29288872e91efd22af71e88b0e5736b0daf7773c1fecd99f0 \ + --hash=sha256:aed90d93b731929e742967e236f842a4a2174dc5db077c8f9ad2c5996f89f63e \ + --hash=sha256:b363bbb5253e5f9c23d8a0a034dfdf1b7c9e7f12e602fc788c435171e96daccc \ + --hash=sha256:b4098b6b638d9e0ca839a81656a2fd4bc26c9486ea707e8b1437d6f9d61c3941 \ + --hash=sha256:b53333627283e7241fcc217323f225c37783b5f0472316edcaa4479a213abfa6 \ + --hash=sha256:b670c2faa92124b7397b42303e4d8eb64a4cd0b7a77e35a9e865a55d61c57ef9 \ + --hash=sha256:bb396952cfa7ad2f01061fbc7dc1ad91dd9d69243bcb8110cf4e36924785a0fe \ + --hash=sha256:c60b83c43faeb6d0a9831f0351d7787a0753f5087cc6fa218d78fdf38e5acef0 \ + --hash=sha256:c6ebecfb7a31385393203eb04ed8b6a08f5002f53df3d59e5e795edb80999652 \ + --hash=sha256:d78d8b86fcdfa1e4c21f8896614b6cc7ee01a2a758ec0c4382d662f2a62cf766 \ + --hash=sha256:d7f8df114d6b4cf5a916b98389aeaf1e3132035420a88beea4e3d977e5f267a5 \ + --hash=sha256:e1cb52fa2d67d7f7fab310b600f22ce1ff04d562d46e9e0ac3e3403c2bb4cc16 \ + --hash=sha256:e3fdf04e402f12e1de8074458549337febb3b45f21076cc02ef4ff786aff687e \ + --hash=sha256:e503cb45ed12b924b5b988ba9576dc9949b2f5283b8e33b21dcb6be74a7c58d0 \ + --hash=sha256:f19ac6ac0a256cf77d3cc926ef0b4e64a9725cc612f97228cd5dc4bd9dbab03b \ + --hash=sha256:f1fb0fd4a1e9b11ac21c30c169d169ef434c6e9344ee0ab27cfa6f605f6387b2 \ + --hash=sha256:fada6b07ec4f0befe05218181f4b85176f11d531911b64c715d1875c4736d73a \ + --hash=sha256:fd173b4cf02b20f60860dc2ffe30115c18972d7d6d2d69df97ac38dee03be5bf \ + --hash=sha256:fe752639919aad9ffb0dee0d87f29a6467d1ef764f13c4644d212a9a853a078d \ + --hash=sha256:fee387d2fab144e8a34e0e9c5ca0f45c9376b99de45628265cfa9886b1dbe62b # via # -r requirements.in # grpcio-tools -grpcio-tools==1.56.2 \ - --hash=sha256:0059dfc9bea8f7bca69c15ca62c88904c4f907fde1137e0743b5eee054661873 \ - --hash=sha256:014da3ed176beb2b1c8430ccc34c8fe962cdd5480e56fb4ab9de60e60c315f3f \ - --hash=sha256:0a4f9cce5a16613b6d3123c89f9d50e0d13b466799af17bc723dc7d2901a54e4 \ - --hash=sha256:13388a22fcba9a1a87f217130a1a01365716af74bd5d0a8a54fc383b8e048ef2 \ - --hash=sha256:14120fb2c6f7894fac5b689934368c692ec50f50a320e8073277ab7778fd612f \ - --hash=sha256:1f334718eb796799bfadbac5567456fb745cee8c7b438c93b74d1ce676c6ad07 \ - --hash=sha256:2037109c1ce253a8e013c9e3ad3722e887d28a1807acdeb1a51b295c8200137b \ - --hash=sha256:216e86d3a6ccc31b27fa4c12491981a0a39d4787d2358b6df05baffa40084494 \ - --hash=sha256:24fc857252181c9950ed2d8cee3df5bd0f42861c4ad0db2a57400186827f96e5 \ - --hash=sha256:26751f69cbbc8ea19cf0657b7d109a6db7df81f80caf16380ebcd20eea27652c \ - --hash=sha256:28444615b7a76b3d9267f81d1487fcad21a581d00564164d9e25ccc28635a811 \ - --hash=sha256:31d1183d28ffc8da242333cb9f683f5093941da80dd5281db0fa93077aecb518 \ - --hash=sha256:355204d1b33c7a19e7d69afda411e6595d39ba1e9cbf561770ac1d5403296554 \ - --hash=sha256:380985b8d95ea2469e103945bd83a815d1213e370f580631fdd5a3dbaa17e446 \ - --hash=sha256:3a74a5e4fc8121a51401665f96f9a70aee50a2f1221e4a199e67b3b8f55881e8 \ - --hash=sha256:4056ff13e30813d42a30ce1cdfeaeb6bbee915515c161c1df896dac3143ae643 \ - --hash=sha256:41af279cf5359b123138236c0980440f4cb4d3d18f03b5c1c314cc1512048351 \ - --hash=sha256:42272376e9a5a1c631863cda056c143c98d21e5b670db5c8c5b7ed0ba3a1a6eb \ - --hash=sha256:45d8b5ad6716848d5b68d9cee29a1a9c5c4baa1824ec5b92d9e35acedddba076 \ - --hash=sha256:483256d5f5be6a77b24d8a5f06ca152d1571c62bf5c738834da61107c7563afe \ - --hash=sha256:493775d17ea09cea6047ba81e4d3f0eb82e34d2fbd3b96e43f72b44ce74726ee \ - --hash=sha256:506d00a86950adf4017395551a4547c0b7fcefa90e4c220135fc3e34e31be81b \ - --hash=sha256:5223668649172d879ee780253b8e4a79144c56a3cc1bb021847f583508c2b0be \ - --hash=sha256:54da410124547bacb97a54546c1a95f1af0125e48edc8b5679412ef8b2844f81 \ - --hash=sha256:68ef3aa7509e5e7a6e7c0ecc183e28118e73da4bef0fc77079648601ce35e58f \ - --hash=sha256:6bfb375eb4f1946d68b8bc7b963c756defa31aa573a35c152a7233d06c0ad6ad \ - --hash=sha256:6dc43300189a69807857c52a3d782e9d3bbfb1cb72dcb27b4043c25161919601 \ - --hash=sha256:778224fcbc1cc7eaf222ce94676afbac8d72b4f84cf4239e30b01d2450a46126 \ - --hash=sha256:7a26160bc0ea5b464715789d4d2a66f01816271677673d65da39bac65b9ea838 \ - --hash=sha256:7d86e24eb6e3973c55e9c74412ff755d1b9d15518c4eaf95676acff49d0162a2 \ - --hash=sha256:82af2f4040084141a732f0ef1ecf3f14fdf629923d74d850415e4d09a077e77a \ - --hash=sha256:857d72e991d449ec4d2f8337e5e24ddf77b4539965f5cabc84d4b63585832982 \ - --hash=sha256:878b9269ceb0dd934b61697a9dd9a5c3e9552521e8f46ab32cf4d72a223f7b6c \ - --hash=sha256:8da04f033b8f4c597e8fc990e2f626bad2b269227bdd554592ea618f624f1aa9 \ - --hash=sha256:8febb4f90b8fab3179f5bdaa159f1d2a20523ea17ec0d66bdec7732f9532de91 \ - --hash=sha256:a8735d7aa34be99dddfbd476eff6005e684bb2c893c0f62a5811528b84c5b371 \ - --hash=sha256:bec47db5d8b5c3b2a44afdbc3f3bf306e34279289a206d20222824381ca3cb13 \ - --hash=sha256:c0640728d63c9fa56e9a1679943ae4e33ad43a10802dd7a93255870731f44d07 \ - --hash=sha256:c0dbaac63a25c088f864295f394230eeb7be48dac2264433fda2603f86c36b25 \ - --hash=sha256:c7ca2272022f90b73efe900244aaebe9dd7cf3b379e99e08a88984e2fdd229c2 \ - --hash=sha256:e7009623635ebcd3dd7fe974883fc2d9a3ff0fcef419bfc0a2da8071b372d9f5 \ - --hash=sha256:ea5d108d28b4cd2e28539241c6aee96bda83086d8888c36785d9f84ea690d896 \ - --hash=sha256:ea5fc1b49514b44a3e5a45156c025002f172ade4c509e58c51967865c7c6fa45 \ - --hash=sha256:ff16dd0b086e75f574dbc122e018a44dbd1c6dae3f3621ea99e8e5a6b2706e12 \ - --hash=sha256:ffae7df3318266614f7aa440acb2098c064b6b5ae061fc22125092386349e526 +grpcio-tools==1.57.0 \ + --hash=sha256:02d78c034109f46032c7217260066d49d41e6bcaf588fa28fa40fe2f83445347 \ + --hash=sha256:0cf5fc0a1c23f8ea34b408b72fb0e90eec0f404ad4dba98e8f6da3c9ce34e2ed \ + --hash=sha256:1c0e8a1a32973a5d59fbcc19232f925e5c48116e9411f788033a31c5ca5130b4 \ + --hash=sha256:1f9e917a9f18087f6c14b4d4508fb94fca5c2f96852363a89232fb9b2124ac1f \ + --hash=sha256:26e69d08a515554e0cfe1ec4d31568836f4b17f0ff82294f957f629388629eb9 \ + --hash=sha256:2b417c97936d94874a3ce7ed8deab910f2233e3612134507cfee4af8735c38a6 \ + --hash=sha256:2db25f15ed44327f2e02d0c4fe741ac966f9500e407047d8a7c7fccf2df65616 \ + --hash=sha256:2f16130d869ce27ecd623194547b649dd657333ec7e8644cc571c645781a9b85 \ + --hash=sha256:34b36217b17b5bea674a414229913e1fd80ede328be51e1b531fcc62abd393b0 \ + --hash=sha256:35bf0dad8a3562043345236c26d0053a856fb06c04d7da652f2ded914e508ae7 \ + --hash=sha256:495e2946406963e0b9f063f76d5af0f2a19517dac2b367b5b044432ac9194296 \ + --hash=sha256:4a7ad7f328e28fc97c356d0f10fb10d8b5151bb65aa7cf14bf8084513f0b7306 \ + --hash=sha256:4fb8a8468031f858381a576078924af364a08833d8f8f3237018252c4573a802 \ + --hash=sha256:5bc3e6d338aefb052e19cedabe00452be46d0c10a4ed29ee77abb00402e438fe \ + --hash=sha256:6fa52972c9647876ea35f6dc2b51002a74ed900ec7894586cbb2fe76f64f99de \ + --hash=sha256:76c0eea89d7542719594e50e2283f51a072978b953e8b3e9fd7c59a2c762d4c1 \ + --hash=sha256:784574709b9690dc28696617ea69352e2132352fdfc9bc89afa8e39f99ae538e \ + --hash=sha256:7b46fc6aa8eb7edd18cafcd21fd98703cb6c09e46b507de335fca7f0161dfccb \ + --hash=sha256:81ec4dbb696e095057b2528d11a8da04be6bbe2b967fa07d4ea9ba6354338cbf \ + --hash=sha256:850cbda0ec5d24c39e7215ede410276040692ca45d105fbbeada407fa03f0ac0 \ + --hash=sha256:85ac4e62eb44428cde025fd9ab7554002315fc7880f791c553fc5a0015cc9931 \ + --hash=sha256:8a42dc220eb5305f470855c9284f4c8e85ae59d6d742cd07946b0cbe5e9ca186 \ + --hash=sha256:9053c2f655589545be08b9d6a673e92970173a4bf11a4b9f18cd6e9af626b587 \ + --hash=sha256:90d10d9038ba46a595a223a34f136c9230e3d6d7abc2433dbf0e1c31939d3a8b \ + --hash=sha256:9867f2817b1a0c93c523f89ac6c9d8625548af4620a7ce438bf5a76e23327284 \ + --hash=sha256:9a3d60fb8d46ede26c1907c146561b3a9caa20a7aff961bc661ef8226f85a2e9 \ + --hash=sha256:9f2aefa8a37bd2c4db1a3f1aca11377e2766214520fb70e67071f4ff8d8b0fa5 \ + --hash=sha256:a0256f8786ac9e4db618a1aa492bb3472569a0946fd3ee862ffe23196323da55 \ + --hash=sha256:aac98ecad8f7bd4301855669d42a5d97ef7bb34bec2b1e74c7a0641d47e313cf \ + --hash=sha256:c026bdf5c1366ce88b7bbe2d8207374d675afd3fd911f60752103de3da4a41d2 \ + --hash=sha256:c39a3656576b6fdaaf28abe0467f7a7231df4230c1bee132322dbc3209419e7f \ + --hash=sha256:cdd020cb68b51462983b7c2dfbc3eb6ede032b8bf438d4554df0c3f08ce35c76 \ + --hash=sha256:d2a134756f4db34759a5cc7f7e43f7eb87540b68d1cca62925593c6fb93924f7 \ + --hash=sha256:dbde4004a0688400036342ff73e3706e8940483e2871547b1354d59e93a38277 \ + --hash=sha256:dc771d4db5701f280957bbcee91745e0686d00ed1c6aa7e05ba30a58b02d70a1 \ + --hash=sha256:dfb6f6120587b8e228a3cae5ee4985b5bdc18501bad05c49df61965dfc9d70a9 \ + --hash=sha256:e868cd6feb3ef07d4b35be104fe1fd0657db05259ff8f8ec5e08f4f89ca1191d \ + --hash=sha256:ec9aab2fb6783c7fc54bc28f58eb75f1ca77594e6b0fd5e5e7a8114a95169fe0 \ + --hash=sha256:ed85a0291fff45b67f2557fe7f117d3bc7af8b54b8619d27bf374b5c8b7e3ca2 \ + --hash=sha256:f3ac06703c412f8167a9062eaf6099409967e33bf98fa5b02be4b4689b6bdf39 \ + --hash=sha256:f3da5240211252fc70a6451fe00c143e2ab2f7bfc2445695ad2ed056b8e48d96 \ + --hash=sha256:f54081b08419a39221cd646363b5708857c696b3ad4784f1dcf310891e33a5f7 \ + --hash=sha256:f64f8ab22d27d4a5693310748d35a696061c3b5c7b8c4fb4ab3b4bc1068b6b56 \ + --hash=sha256:f717cce5093e6b6049d9ea6d12fdf3658efdb1a80772f7737db1f8510b876df6 \ + --hash=sha256:fb81ff861692111fa81bd85f64584e624cb4013bd66fbce8a209b8893f5ce398 # via -r requirements.in idna==3.2 \ --hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \ --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3 # via requests -protobuf==4.23.4 \ - --hash=sha256:0a5759f5696895de8cc913f084e27fd4125e8fb0914bb729a17816a33819f474 \ - --hash=sha256:351cc90f7d10839c480aeb9b870a211e322bf05f6ab3f55fcb2f51331f80a7d2 \ - --hash=sha256:5fea3c64d41ea5ecf5697b83e41d09b9589e6f20b677ab3c48e5f242d9b7897b \ - --hash=sha256:6dd9b9940e3f17077e820b75851126615ee38643c2c5332aa7a359988820c720 \ - --hash=sha256:7b19b6266d92ca6a2a87effa88ecc4af73ebc5cfde194dc737cf8ef23a9a3b12 \ - --hash=sha256:8547bf44fe8cec3c69e3042f5c4fb3e36eb2a7a013bb0a44c018fc1e427aafbd \ - --hash=sha256:9053df6df8e5a76c84339ee4a9f5a2661ceee4a0dab019e8663c50ba324208b0 \ - --hash=sha256:c3e0939433c40796ca4cfc0fac08af50b00eb66a40bbbc5dee711998fb0bbc1e \ - --hash=sha256:ccd9430c0719dce806b93f89c91de7977304729e55377f872a92465d548329a9 \ - --hash=sha256:e1c915778d8ced71e26fcf43c0866d7499891bca14c4368448a82edc61fdbc70 \ - --hash=sha256:e9d0be5bf34b275b9f87ba7407796556abeeba635455d036c7351f7c183ef8ff \ - --hash=sha256:effeac51ab79332d44fba74660d40ae79985901ac21bca408f8dc335a81aa597 \ - --hash=sha256:fee88269a090ada09ca63551bf2f573eb2424035bcf2cb1b121895b01a46594a +protobuf==4.24.0 \ + --hash=sha256:44825e963008f8ea0d26c51911c30d3e82e122997c3c4568fd0385dd7bacaedf \ + --hash=sha256:567fe6b0647494845d0849e3d5b260bfdd75692bf452cdc9cb660d12457c055d \ + --hash=sha256:5ab19ee50037d4b663c02218a811a5e1e7bb30940c79aac385b96e7a4f9daa61 \ + --hash=sha256:5d0ceb9de6e08311832169e601d1fc71bd8e8c779f3ee38a97a78554945ecb85 \ + --hash=sha256:6c817cf4a26334625a1904b38523d1b343ff8b637d75d2c8790189a4064e51c3 \ + --hash=sha256:81cb9c4621d2abfe181154354f63af1c41b00a4882fb230b4425cbaed65e8f52 \ + --hash=sha256:82e6e9ebdd15b8200e8423676eab38b774624d6a1ad696a60d86a2ac93f18201 \ + --hash=sha256:8bb52a2be32db82ddc623aefcedfe1e0eb51da60e18fcc908fb8885c81d72109 \ + --hash=sha256:a38400a692fd0c6944c3c58837d112f135eb1ed6cdad5ca6c5763336e74f1a04 \ + --hash=sha256:a6b1ca92ccabfd9903c0c7dde8876221dc7d8d87ad5c42e095cc11b15d3569c7 \ + --hash=sha256:ae7a1835721086013de193311df858bc12cd247abe4ef9710b715d930b95b33e \ + --hash=sha256:ae97b5de10f25b7a443b40427033e545a32b0e9dda17bcd8330d70033379b3e5 \ + --hash=sha256:e8834ef0b4c88666ebb7c7ec18045aa0f4325481d724daa624a4cf9f28134653 # via # -r requirements.in # grpcio-tools diff --git a/examples/mysql/Dockerfile-mysql b/examples/mysql/Dockerfile-mysql index 39bbedf87b74..ef07da28c81b 100644 --- a/examples/mysql/Dockerfile-mysql +++ b/examples/mysql/Dockerfile-mysql @@ -1 +1 @@ -FROM mysql:8.0.34@sha256:51c4dc55d3abf4517a5a652794d1f0adb2f2ed1d1bedc847d6132d91cdb2ebbf +FROM mysql:8.1.0@sha256:c0455ac041844b5e65cd08571387fa5b50ab2a6179557fd938298cab13acf0dd diff --git a/examples/opentelemetry/Dockerfile-opentelemetry b/examples/opentelemetry/Dockerfile-opentelemetry index faa853ee670e..0efedffe32f3 100644 --- a/examples/opentelemetry/Dockerfile-opentelemetry +++ b/examples/opentelemetry/Dockerfile-opentelemetry @@ -1,7 +1,7 @@ -FROM alpine:3.18@sha256:82d1e9d7ed48a7523bdebc18cf6290bdb97b82302a8a9c27d4fe885949ea94d1 as otelc_curl +FROM alpine:3.18@sha256:7144f7bab3d4c2648d7e59409f15ec52a18006a128c733fcff20d3a4a54ba44a as otelc_curl RUN apk --update add curl -FROM otel/opentelemetry-collector:latest@sha256:6a76e13f1f4a22a6836189b9828e3de64cddae418ff5a15f10715a596ccc08a5 +FROM otel/opentelemetry-collector:latest@sha256:dbf77b00f14fceb02453433c7bf9b3438d5723e8b9bd53d61bfe1522d8f7ba5b COPY --from=otelc_curl / / diff --git a/examples/redis/Dockerfile-redis b/examples/redis/Dockerfile-redis index 36cbb9e285ec..488432d13f14 100644 --- a/examples/redis/Dockerfile-redis +++ b/examples/redis/Dockerfile-redis @@ -1 +1 @@ -FROM redis@sha256:08a82d4bf8a8b4dd94e8f5408cdbad9dd184c1cf311d34176cd3e9972c43f872 +FROM redis@sha256:b0bdc1a83caf43f9eb74afca0fcfd6f09bea38bb87f6add4a858f06ef4617538 diff --git a/examples/shared/golang/Dockerfile b/examples/shared/golang/Dockerfile index 04728cf544ae..2178205adfe8 100644 --- a/examples/shared/golang/Dockerfile +++ b/examples/shared/golang/Dockerfile @@ -3,7 +3,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean \ && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | tee /etc/apt/apt.conf.d/keep-cache -FROM golang:1.20.6-bullseye@sha256:2ae255c69b2e96cac149ca10b02b9fd3ef71033e0187fb8c9c3c333626e5ae50 as golang-base +FROM golang:1.21.0-bullseye@sha256:0ed263861f71db3c4fed4d900b621aca03a3b91b8bc5f1dd56584af191c89d68 as golang-base FROM golang-base as golang-control-plane-builder diff --git a/examples/shared/jaeger/Dockerfile b/examples/shared/jaeger/Dockerfile index 08657a367b49..62b257b9a6b5 100644 --- a/examples/shared/jaeger/Dockerfile +++ b/examples/shared/jaeger/Dockerfile @@ -1,4 +1,4 @@ -FROM jaegertracing/all-in-one@sha256:93ba12f6986a566d12929b29a6117b7f7a9179e49654f9814e0c50e364fd93af +FROM jaegertracing/all-in-one@sha256:738a2c24b533d1f011c4e3549e53835ce1013f489b6dbba2eb0b5072711a53dc HEALTHCHECK \ --interval=1s \ --timeout=1s \ diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index 6e6b1f712009..048b26abceea 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.5-bullseye-slim@sha256:55571ebc48f4dfecfb4d6ec0a056a042ac32ed1ebea44d0fedd78088709b9948 as node-base +FROM node:20.5-bullseye-slim@sha256:a2fe8fd3975d4b378abf721519df7cf0d465b8b8fb29e6bbe4d4a79c871bcded as node-base FROM node-base as node-http-auth diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index b73d78801208..e9c62173416f 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:8775adb39f0db45cf4cdb3601380312ee5e9c4f53af0f89b7dc5cd4c9a78e4e8 +FROM postgres:latest@sha256:86817d5b3096e71d26bb5328e5f5d9d184c02bc3e511b252ed6cffaae9e3f745 COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] diff --git a/examples/shared/python/Dockerfile b/examples/shared/python/Dockerfile index 4bf40354c43e..756105da7c51 100644 --- a/examples/shared/python/Dockerfile +++ b/examples/shared/python/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.4-slim-bullseye@sha256:52c7a54aa5e5068ce76edaf3f8652a64fb99e378fb89fb0bfbe21a8756d0013c as python-base +FROM python:3.11.4-slim-bullseye@sha256:68ff2208aed4c4d9562e4f1e42e9a648430a52bf8b284ac0c8c7d5ef26dc8575 as python-base RUN rm -f /etc/apt/apt.conf.d/docker-clean \ && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | tee /etc/apt/apt.conf.d/keep-cache ARG PYTHON_REQUIREMENTS_FILE=aiohttp/requirements.txt diff --git a/examples/shared/python/postgres/requirements.txt b/examples/shared/python/postgres/requirements.txt index 2100e5a0432d..6e0f8988d7aa 100644 --- a/examples/shared/python/postgres/requirements.txt +++ b/examples/shared/python/postgres/requirements.txt @@ -4,67 +4,65 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -psycopg2-binary==2.9.6 \ - --hash=sha256:02c0f3757a4300cf379eb49f543fb7ac527fb00144d39246ee40e1df684ab514 \ - --hash=sha256:02c6e3cf3439e213e4ee930308dc122d6fb4d4bea9aef4a12535fbd605d1a2fe \ - --hash=sha256:0645376d399bfd64da57148694d78e1f431b1e1ee1054872a5713125681cf1be \ - --hash=sha256:0892ef645c2fabb0c75ec32d79f4252542d0caec1d5d949630e7d242ca4681a3 \ - --hash=sha256:0d236c2825fa656a2d98bbb0e52370a2e852e5a0ec45fc4f402977313329174d \ - --hash=sha256:0e0f754d27fddcfd74006455b6e04e6705d6c31a612ec69ddc040a5468e44b4e \ - --hash=sha256:15e2ee79e7cf29582ef770de7dab3d286431b01c3bb598f8e05e09601b890081 \ - --hash=sha256:1876843d8e31c89c399e31b97d4b9725a3575bb9c2af92038464231ec40f9edb \ - --hash=sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c \ - --hash=sha256:2ab652e729ff4ad76d400df2624d223d6e265ef81bb8aa17fbd63607878ecbee \ - --hash=sha256:30637a20623e2a2eacc420059be11527f4458ef54352d870b8181a4c3020ae6b \ - --hash=sha256:34b9ccdf210cbbb1303c7c4db2905fa0319391bd5904d32689e6dd5c963d2ea8 \ - --hash=sha256:38601cbbfe600362c43714482f43b7c110b20cb0f8172422c616b09b85a750c5 \ - --hash=sha256:441cc2f8869a4f0f4bb408475e5ae0ee1f3b55b33f350406150277f7f35384fc \ - --hash=sha256:498807b927ca2510baea1b05cc91d7da4718a0f53cb766c154c417a39f1820a0 \ - --hash=sha256:4ac30da8b4f57187dbf449294d23b808f8f53cad6b1fc3623fa8a6c11d176dd0 \ - --hash=sha256:4c727b597c6444a16e9119386b59388f8a424223302d0c06c676ec8b4bc1f963 \ - --hash=sha256:4d67fbdaf177da06374473ef6f7ed8cc0a9dc640b01abfe9e8a2ccb1b1402c1f \ - --hash=sha256:4dfb4be774c4436a4526d0c554af0cc2e02082c38303852a36f6456ece7b3503 \ - --hash=sha256:4ea29fc3ad9d91162c52b578f211ff1c931d8a38e1f58e684c45aa470adf19e2 \ - --hash=sha256:51537e3d299be0db9137b321dfb6a5022caaab275775680e0c3d281feefaca6b \ - --hash=sha256:61b047a0537bbc3afae10f134dc6393823882eb263088c271331602b672e52e9 \ - --hash=sha256:6460c7a99fc939b849431f1e73e013d54aa54293f30f1109019c56a0b2b2ec2f \ - --hash=sha256:65bee1e49fa6f9cf327ce0e01c4c10f39165ee76d35c846ade7cb0ec6683e303 \ - --hash=sha256:65c07febd1936d63bfde78948b76cd4c2a411572a44ac50719ead41947d0f26b \ - --hash=sha256:71f14375d6f73b62800530b581aed3ada394039877818b2d5f7fc77e3bb6894d \ - --hash=sha256:7a40c00dbe17c0af5bdd55aafd6ff6679f94a9be9513a4c7e071baf3d7d22a70 \ - --hash=sha256:7e13a5a2c01151f1208d5207e42f33ba86d561b7a89fca67c700b9486a06d0e2 \ - --hash=sha256:7f0438fa20fb6c7e202863e0d5ab02c246d35efb1d164e052f2f3bfe2b152bd0 \ - --hash=sha256:8122cfc7cae0da9a3077216528b8bb3629c43b25053284cc868744bfe71eb141 \ - --hash=sha256:8338a271cb71d8da40b023a35d9c1e919eba6cbd8fa20a54b748a332c355d896 \ - --hash=sha256:84d2222e61f313c4848ff05353653bf5f5cf6ce34df540e4274516880d9c3763 \ - --hash=sha256:8a6979cf527e2603d349a91060f428bcb135aea2be3201dff794813256c274f1 \ - --hash=sha256:8a76e027f87753f9bd1ab5f7c9cb8c7628d1077ef927f5e2446477153a602f2c \ - --hash=sha256:964b4dfb7c1c1965ac4c1978b0f755cc4bd698e8aa2b7667c575fb5f04ebe06b \ - --hash=sha256:9972aad21f965599ed0106f65334230ce826e5ae69fda7cbd688d24fa922415e \ - --hash=sha256:a8c28fd40a4226b4a84bdf2d2b5b37d2c7bd49486b5adcc200e8c7ec991dfa7e \ - --hash=sha256:ae102a98c547ee2288637af07393dd33f440c25e5cd79556b04e3fca13325e5f \ - --hash=sha256:af335bac6b666cc6aea16f11d486c3b794029d9df029967f9938a4bed59b6a19 \ - --hash=sha256:afe64e9b8ea66866a771996f6ff14447e8082ea26e675a295ad3bdbffdd72afb \ - --hash=sha256:b4b24f75d16a89cc6b4cdff0eb6a910a966ecd476d1e73f7ce5985ff1328e9a6 \ - --hash=sha256:b6c8288bb8a84b47e07013bb4850f50538aa913d487579e1921724631d02ea1b \ - --hash=sha256:b83456c2d4979e08ff56180a76429263ea254c3f6552cd14ada95cff1dec9bb8 \ - --hash=sha256:bfb13af3c5dd3a9588000910178de17010ebcccd37b4f9794b00595e3a8ddad3 \ - --hash=sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848 \ - --hash=sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365 \ - --hash=sha256:c7e62ab8b332147a7593a385d4f368874d5fe4ad4e341770d4983442d89603e3 \ - --hash=sha256:c83a74b68270028dc8ee74d38ecfaf9c90eed23c8959fca95bd703d25b82c88e \ - --hash=sha256:cacbdc5839bdff804dfebc058fe25684cae322987f7a38b0168bc1b2df703fb1 \ - --hash=sha256:cf4499e0a83b7b7edcb8dabecbd8501d0d3a5ef66457200f77bde3d210d5debb \ - --hash=sha256:cfec476887aa231b8548ece2e06d28edc87c1397ebd83922299af2e051cf2827 \ - --hash=sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7 \ - --hash=sha256:d4e6036decf4b72d6425d5b29bbd3e8f0ff1059cda7ac7b96d6ac5ed34ffbacd \ - --hash=sha256:d57c3fd55d9058645d26ae37d76e61156a27722097229d32a9e73ed54819982a \ - --hash=sha256:dfa74c903a3c1f0d9b1c7e7b53ed2d929a4910e272add6700c38f365a6002820 \ - --hash=sha256:e3ed340d2b858d6e6fb5083f87c09996506af483227735de6964a6100b4e6a54 \ - --hash=sha256:e78e6e2a00c223e164c417628572a90093c031ed724492c763721c2e0bc2a8df \ - --hash=sha256:e9182eb20f41417ea1dd8e8f7888c4d7c6e805f8a7c98c1081778a3da2bee3e4 \ - --hash=sha256:e99e34c82309dd78959ba3c1590975b5d3c862d6f279f843d47d26ff89d7d7e1 \ - --hash=sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249 \ - --hash=sha256:f81e65376e52f03422e1fb475c9514185669943798ed019ac50410fb4c4df232 \ - --hash=sha256:ffe9dc0a884a8848075e576c1de0290d85a533a9f6e9c4e564f19adf8f6e54a7 +psycopg2-binary==2.9.7 \ + --hash=sha256:00d8db270afb76f48a499f7bb8fa70297e66da67288471ca873db88382850bf4 \ + --hash=sha256:024eaeb2a08c9a65cd5f94b31ace1ee3bb3f978cd4d079406aef85169ba01f08 \ + --hash=sha256:094af2e77a1976efd4956a031028774b827029729725e136514aae3cdf49b87b \ + --hash=sha256:1011eeb0c51e5b9ea1016f0f45fa23aca63966a4c0afcf0340ccabe85a9f65bd \ + --hash=sha256:11abdbfc6f7f7dea4a524b5f4117369b0d757725798f1593796be6ece20266cb \ + --hash=sha256:122641b7fab18ef76b18860dd0c772290566b6fb30cc08e923ad73d17461dc63 \ + --hash=sha256:17cc17a70dfb295a240db7f65b6d8153c3d81efb145d76da1e4a096e9c5c0e63 \ + --hash=sha256:18f12632ab516c47c1ac4841a78fddea6508a8284c7cf0f292cb1a523f2e2379 \ + --hash=sha256:1b918f64a51ffe19cd2e230b3240ba481330ce1d4b7875ae67305bd1d37b041c \ + --hash=sha256:1c31c2606ac500dbd26381145684d87730a2fac9a62ebcfbaa2b119f8d6c19f4 \ + --hash=sha256:26484e913d472ecb6b45937ea55ce29c57c662066d222fb0fbdc1fab457f18c5 \ + --hash=sha256:2993ccb2b7e80844d534e55e0f12534c2871952f78e0da33c35e648bf002bbff \ + --hash=sha256:2b04da24cbde33292ad34a40db9832a80ad12de26486ffeda883413c9e1b1d5e \ + --hash=sha256:2dec5a75a3a5d42b120e88e6ed3e3b37b46459202bb8e36cd67591b6e5feebc1 \ + --hash=sha256:2df562bb2e4e00ee064779902d721223cfa9f8f58e7e52318c97d139cf7f012d \ + --hash=sha256:3fbb1184c7e9d28d67671992970718c05af5f77fc88e26fd7136613c4ece1f89 \ + --hash=sha256:42a62ef0e5abb55bf6ffb050eb2b0fcd767261fa3faf943a4267539168807522 \ + --hash=sha256:4ecc15666f16f97709106d87284c136cdc82647e1c3f8392a672616aed3c7151 \ + --hash=sha256:4eec5d36dbcfc076caab61a2114c12094c0b7027d57e9e4387b634e8ab36fd44 \ + --hash=sha256:4fe13712357d802080cfccbf8c6266a3121dc0e27e2144819029095ccf708372 \ + --hash=sha256:51d1b42d44f4ffb93188f9b39e6d1c82aa758fdb8d9de65e1ddfe7a7d250d7ad \ + --hash=sha256:59f7e9109a59dfa31efa022e94a244736ae401526682de504e87bd11ce870c22 \ + --hash=sha256:62cb6de84d7767164a87ca97e22e5e0a134856ebcb08f21b621c6125baf61f16 \ + --hash=sha256:642df77484b2dcaf87d4237792246d8068653f9e0f5c025e2c692fc56b0dda70 \ + --hash=sha256:6822c9c63308d650db201ba22fe6648bd6786ca6d14fdaf273b17e15608d0852 \ + --hash=sha256:692df8763b71d42eb8343f54091368f6f6c9cfc56dc391858cdb3c3ef1e3e584 \ + --hash=sha256:6d92e139ca388ccfe8c04aacc163756e55ba4c623c6ba13d5d1595ed97523e4b \ + --hash=sha256:7952807f95c8eba6a8ccb14e00bf170bb700cafcec3924d565235dffc7dc4ae8 \ + --hash=sha256:7db7b9b701974c96a88997d458b38ccb110eba8f805d4b4f74944aac48639b42 \ + --hash=sha256:81d5dd2dd9ab78d31a451e357315f201d976c131ca7d43870a0e8063b6b7a1ec \ + --hash=sha256:8a136c8aaf6615653450817a7abe0fc01e4ea720ae41dfb2823eccae4b9062a3 \ + --hash=sha256:8a7968fd20bd550431837656872c19575b687f3f6f98120046228e451e4064df \ + --hash=sha256:8c721ee464e45ecf609ff8c0a555018764974114f671815a0a7152aedb9f3343 \ + --hash=sha256:8f309b77a7c716e6ed9891b9b42953c3ff7d533dc548c1e33fddc73d2f5e21f9 \ + --hash=sha256:8f94cb12150d57ea433e3e02aabd072205648e86f1d5a0a692d60242f7809b15 \ + --hash=sha256:95a7a747bdc3b010bb6a980f053233e7610276d55f3ca506afff4ad7749ab58a \ + --hash=sha256:9b0c2b466b2f4d89ccc33784c4ebb1627989bd84a39b79092e560e937a11d4ac \ + --hash=sha256:9dcfd5d37e027ec393a303cc0a216be564b96c80ba532f3d1e0d2b5e5e4b1e6e \ + --hash=sha256:a5ee89587696d808c9a00876065d725d4ae606f5f7853b961cdbc348b0f7c9a1 \ + --hash=sha256:a6a8b575ac45af1eaccbbcdcf710ab984fd50af048fe130672377f78aaff6fc1 \ + --hash=sha256:ac83ab05e25354dad798401babaa6daa9577462136ba215694865394840e31f8 \ + --hash=sha256:ad26d4eeaa0d722b25814cce97335ecf1b707630258f14ac4d2ed3d1d8415265 \ + --hash=sha256:ad5ec10b53cbb57e9a2e77b67e4e4368df56b54d6b00cc86398578f1c635f329 \ + --hash=sha256:c82986635a16fb1fa15cd5436035c88bc65c3d5ced1cfaac7f357ee9e9deddd4 \ + --hash=sha256:ced63c054bdaf0298f62681d5dcae3afe60cbae332390bfb1acf0e23dcd25fc8 \ + --hash=sha256:d0b16e5bb0ab78583f0ed7ab16378a0f8a89a27256bb5560402749dbe8a164d7 \ + --hash=sha256:dbbc3c5d15ed76b0d9db7753c0db40899136ecfe97d50cbde918f630c5eb857a \ + --hash=sha256:ded8e15f7550db9e75c60b3d9fcbc7737fea258a0f10032cdb7edc26c2a671fd \ + --hash=sha256:e02bc4f2966475a7393bd0f098e1165d470d3fa816264054359ed4f10f6914ea \ + --hash=sha256:e5666632ba2b0d9757b38fc17337d84bdf932d38563c5234f5f8c54fd01349c9 \ + --hash=sha256:ea5f8ee87f1eddc818fc04649d952c526db4426d26bab16efbe5a0c52b27d6ab \ + --hash=sha256:eb1c0e682138f9067a58fc3c9a9bf1c83d8e08cfbee380d858e63196466d5c86 \ + --hash=sha256:eb3b8d55924a6058a26db69fb1d3e7e32695ff8b491835ba9f479537e14dcf9f \ + --hash=sha256:ee919b676da28f78f91b464fb3e12238bd7474483352a59c8a16c39dfc59f0c5 \ + --hash=sha256:f02f4a72cc3ab2565c6d9720f0343cb840fb2dc01a2e9ecb8bc58ccf95dc5c06 \ + --hash=sha256:f4f37bbc6588d402980ffbd1f3338c871368fb4b1cfa091debe13c68bb3852b3 \ + --hash=sha256:f8651cf1f144f9ee0fa7d1a1df61a9184ab72962531ca99f077bbdcba3947c58 \ + --hash=sha256:f955aa50d7d5220fcb6e38f69ea126eafecd812d96aeed5d5f3597f33fad43bb \ + --hash=sha256:fc10da7e7df3380426521e8c1ed975d22df678639da2ed0ec3244c3dc2ab54c8 \ + --hash=sha256:fdca0511458d26cf39b827a663d7d87db6f32b93efc22442a742035728603d5f # via -r requirements.in diff --git a/examples/zipkin/Dockerfile-zipkin b/examples/zipkin/Dockerfile-zipkin index 4bf28ce777b1..f9c1f8485c77 100644 --- a/examples/zipkin/Dockerfile-zipkin +++ b/examples/zipkin/Dockerfile-zipkin @@ -1 +1 @@ -FROM openzipkin/zipkin:latest@sha256:46ee38f01382e72de617f704441fd935390c6eeb762e88869530e661f1339765 +FROM openzipkin/zipkin:latest@sha256:5fd55e6a109233b36d419d7fd2449588d17a6e4da7ed7a3fd0d09c86f1c75a15 diff --git a/mobile/.bazelrc b/mobile/.bazelrc index f520875f3187..d7a1f2d96fb0 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -1,3 +1,6 @@ +## Any new configs - ie that are not defined in Envoy's bazelrc ... +# **should be prefixed with mobile-** + # Envoy Mobile Bazel build/test options. try-import ../.bazelrc @@ -46,9 +49,9 @@ build:rules_xcodeproj --features=-swift.use_global_index_store # Override PGV validation with NOP functions build --@com_envoyproxy_protoc_gen_validate//bazel:template-flavor=nop -build:dbg-common --compilation_mode=dbg +build:mobile-dbg-common --compilation_mode=dbg # Enable source map for debugging in IDEs -build:dbg-common --copt="-fdebug-compilation-dir" --copt="/proc/self/cwd" +build:mobile-dbg-common --copt="-fdebug-compilation-dir" --copt="/proc/self/cwd" # Default flags for builds targeting iOS # Manual stamping is necessary in order to get versioning information in the iOS @@ -62,12 +65,12 @@ build:ios --test_timeout=390,750,1500,5700 build:android --define=logger=android # Default flags for Android debug builds -build:dbg-android --config=dbg-common -build:dbg-android --config=android +build:mobile-dbg-android --config=mobile-dbg-common +build:mobile-dbg-android --config=android # Default flags for iOS debug builds -build:dbg-ios --config=dbg-common -build:dbg-ios --config=ios +build:mobile-dbg-ios --config=mobile-dbg-common +build:mobile-dbg-ios --config=ios # Default flags for Android tests # TODO(jpsim): Explicitly register test extensions for Android tests @@ -77,193 +80,161 @@ build:test-android --define=static_extension_registration=enabled # Locally-runnable ASAN config for MacOS & Linux with standard dev environment # See also: # https://github.com/bazelbuild/bazel/issues/6932 -build:asan-dev --strip=never -build:asan-dev --copt -Wno-macro-redefined # ASAN sets _FORTIFY_SOURCE=0 -build:asan-dev --copt -DADDRESS_SANITIZER -build:asan-dev --copt -D_LIBCPP_HAS_NO_ASAN -build:asan-dev --copt -g -build:asan-dev --copt -fno-omit-frame-pointer -build:asan-dev --copt -fno-optimize-sibling-calls -build:asan-dev --copt -fsanitize=address -build:asan-dev --linkopt -fsanitize=address -build:asan-dev --platform_suffix=-asan +build:mobile-asan-dev --strip=never +build:mobile-asan-dev --copt -Wno-macro-redefined # ASAN sets _FORTIFY_SOURCE=0 +build:mobile-asan-dev --copt -DADDRESS_SANITIZER +build:mobile-asan-dev --copt -D_LIBCPP_HAS_NO_ASAN +build:mobile-asan-dev --copt -g +build:mobile-asan-dev --copt -fno-omit-frame-pointer +build:mobile-asan-dev --copt -fno-optimize-sibling-calls +build:mobile-asan-dev --copt -fsanitize=address +build:mobile-asan-dev --linkopt -fsanitize=address +build:mobile-asan-dev --platform_suffix=-asan build:clang-asan --linkopt --rtlib=compiler-rt build:clang-asan --linkopt --unwindlib=libgcc # Locally-runnable TSAN config -build:tsan-dev --features=tsan -build:tsan-dev --copt -fsanitize=thread -build:tsan-dev --linkopt -fsanitize=thread -build:tsan-dev --test_env=ENVOY_IP_TEST_VERSIONS=v4only -build:tsan-dev --platform_suffix=-tsan +build:mobile-tsan-dev --features=tsan +build:mobile-tsan-dev --copt -fsanitize=thread +build:mobile-tsan-dev --linkopt -fsanitize=thread +build:mobile-tsan-dev --test_env=ENVOY_IP_TEST_VERSIONS=v4only +build:mobile-tsan-dev --platform_suffix=-tsan # Needed due to https://github.com/libevent/libevent/issues/777 -build:tsan-dev --copt -DEVENT__DISABLE_DEBUG_MODE +build:mobile-tsan-dev --copt -DEVENT__DISABLE_DEBUG_MODE # https://github.com/abseil/abseil-cpp/issues/760 # https://github.com/google/sanitizers/issues/953 -build:tsan-dev --test_env="TSAN_OPTIONS=report_atomic_races=0" +build:mobile-tsan-dev --test_env="TSAN_OPTIONS=report_atomic_races=0" # Exclude debug info from the release binary since it makes it too large to fit # into a zip file. This shouldn't affect crash reports. -build:release-common --define=no_debug_info=1 +build:mobile-release-common --define=no_debug_info=1 + +# order matters here to ensure downloads +build:mobile-remote-release-clang --config=mobile-remote-ci-linux-clang +build:mobile-remote-release-clang --config=mobile-release-common +build:mobile-remote-release-clang --remote_download_toplevel +build:mobile-remote-release-clang --config=ci +build:mobile-remote-release-clang --config=remote # Compile releases optimizing for size (eg -Os, etc). -build:release-common --config=sizeopt +build:mobile-release-common --config=sizeopt # Set default symbols visibility to hidden to reduce .dynstr and the symbol table size -build:release-common --copt=-fvisibility=hidden +build:mobile-release-common --copt=-fvisibility=hidden # Disable google_grpc in production by default -build:release-common --define=google_grpc=disabled +build:mobile-release-common --define=google_grpc=disabled # Enable automatic extension factory registration for release builds -build:release-common --define=static_extension_registration=enabled +build:mobile-release-common --define=static_extension_registration=enabled # Flags for release builds targeting iOS -build:release-ios --config=ios -build:release-ios --config=release-common -build:release-ios --compilation_mode=opt +build:mobile-release-ios --config=ios +build:mobile-release-ios --config=mobile-release-common +build:mobile-release-ios --compilation_mode=opt # Flags for release builds targeting Android or the JVM # Release does not use the option --define=logger=android -build:release-android --config=release-common -build:release-android --compilation_mode=opt +build:mobile-release-android --config=mobile-release-common +build:mobile-release-android --compilation_mode=opt # Instrument Envoy Mobile's C++ code for coverage -build:coverage --instrumentation_filter="//library/common[/:]" +coverage --instrumentation_filter="//library/common[/:]" ############################################################################# # Experimental EngFlow Remote Execution Configs ############################################################################# -# remote-ci-common: These options are valid for any platform, use the configs below +# mobile-remote-ci-common: These options are valid for any platform, use the configs below # to add platform-specific options. Avoid using this config directly and # instead use a platform-specific config ############################################################################# -build:remote-ci-common --config=ci -build:remote-ci-common --config=remote -build:remote-ci-common --google_default_credentials=false -build:remote-ci-common --remote_cache=grpcs://envoy.cluster.engflow.com -build:remote-ci-common --remote_executor=grpcs://envoy.cluster.engflow.com -build:remote-ci-common --bes_backend=grpcs://envoy.cluster.engflow.com/ -build:remote-ci-common --bes_results_url=https://envoy.cluster.engflow.com/invocation/ -build:remote-ci-common --experimental_credential_helper=%workspace%/bazel/engflow-bazel-credential-helper.sh -build:remote-ci-common --jobs=40 -build:remote-ci-common --verbose_failures -build:remote-ci-common --spawn_strategy=remote,sandboxed,local -build:remote-ci-common --grpc_keepalive_time=30s -build:remote-ci-common --remote_timeout=3600s -build:remote-ci-common --bes_timeout=3600s -build:remote-ci-common --bes_upload_mode=fully_async +build:mobile-remote-ci-common --config=rbe-engflow +build:mobile-remote-ci-common --jobs=40 +build:mobile-remote-ci-common --verbose_failures +build:mobile-remote-ci-common --spawn_strategy=remote,sandboxed,local + ############################################################################# -# remote-ci-linux: These options are linux-only using GCC by default +# mobile-remote-ci-linux: These options are linux-only using GCC by default ############################################################################# # Common Engflow flags -build:remote-ci-linux --define=EXECUTOR=remote -build:remote-ci-linux --disk_cache= -build:remote-ci-linux --incompatible_strict_action_env=true +build:mobile-remote-ci-linux --define=EXECUTOR=remote +build:mobile-remote-ci-linux --disk_cache= +build:mobile-remote-ci-linux --incompatible_strict_action_env=true # GCC toolchain options -build:remote-ci-linux --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux --extra_execution_platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux --host_platform=//third_party/rbe_configs/config:platform -build:remote-ci-linux --platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux --config=remote-ci-common +build:mobile-remote-ci-linux --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +build:mobile-remote-ci-linux --crosstool_top=//third_party/rbe_configs/cc:toolchain +build:mobile-remote-ci-linux --extra_execution_platforms=//third_party/rbe_configs/config:platform +build:mobile-remote-ci-linux --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain +build:mobile-remote-ci-linux --host_platform=//third_party/rbe_configs/config:platform +build:mobile-remote-ci-linux --platforms=//third_party/rbe_configs/config:platform +build:mobile-remote-ci-linux --config=mobile-remote-ci-common + ############################################################################# -# remote-ci-linux-clang: These options are linux-only using Clang by default +# mobile-remote-ci-linux-clang: These options are linux-only using Clang by default ############################################################################# -build:remote-ci-linux-clang --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux-clang --action_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-clang --action_env=CXX=/opt/llvm/bin/clang++ -build:remote-ci-linux-clang --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux-clang --extra_execution_platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-clang --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux-clang --host_platform=//third_party/rbe_configs/config:platform -build:remote-ci-linux-clang --platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-clang --config=remote-ci-common +build:mobile-remote-ci-linux-clang --action_env=CC=/opt/llvm/bin/clang +build:mobile-remote-ci-linux-clang --action_env=CXX=/opt/llvm/bin/clang++ +build:mobile-remote-ci-linux-clang --config=mobile-remote-ci-linux + ############################################################################# -# remote-ci-linux-asan: These options are Linux-only using Clang and AddressSanitizer +# mobile-remote-ci-linux-asan: These options are Linux-only using Clang and AddressSanitizer ############################################################################# -build:remote-ci-linux-asan --config=clang-asan -build:remote-ci-linux-asan --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux-asan --action_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-asan --action_env=CXX=/opt/llvm/bin/clang++ -build:remote-ci-linux-asan --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux-asan --extra_execution_platforms=//third_party/rbe_configs/config:platform-asan -build:remote-ci-linux-asan --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux-asan --host_platform=//third_party/rbe_configs/config:platform-asan -build:remote-ci-linux-asan --platforms=//third_party/rbe_configs/config:platform-asan -build:remote-ci-linux-asan --config=remote-ci-common +build:mobile-remote-ci-linux-asan --config=clang-asan +build:mobile-remote-ci-linux-asan --config=mobile-remote-ci-linux-clang +build:mobile-remote-ci-linux-asan --config=remote-ci + ############################################################################# -# remote-ci-linux-tsan: These options are Linux-only using Clang and ThreadSanitizer +# mobile-remote-ci-linux-tsan: These options are Linux-only using Clang and ThreadSanitizer ############################################################################# -build:remote-ci-linux-tsan --config=clang-tsan -build:remote-ci-linux-tsan --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux-tsan --action_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-tsan --action_env=CXX=/opt/llvm/bin/clang++ -build:remote-ci-linux-tsan --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux-tsan --extra_execution_platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-tsan --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux-tsan --host_platform=//third_party/rbe_configs/config:platform -build:remote-ci-linux-tsan --platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-tsan --config=remote-ci-common +build:mobile-remote-ci-linux-tsan --config=clang-tsan +build:mobile-remote-ci-linux-tsan --config=mobile-remote-ci-linux-clang +build:mobile-remote-ci-linux-tsan --config=remote-ci + ############################################################################# -# remote-ci-linux-coverage: These options are Linux-only using Clang and LLVM coverage +# ci-linux-coverage: These options are Linux-only using Clang and LLVM coverage +############################################################################# +# Clang environment variables (keep in sync with //third_party/rbe_configs) +# Coverage environment variables (keep in sync with //third_party/rbe_configs) +build:mobile-ci-linux-coverage --action_env=GCOV=/opt/llvm/bin/llvm-profdata +build:mobile-ci-linux-coverage --test_env=GCOV=/opt/llvm/bin/llvm-profdata +build:mobile-ci-linux-coverage --action_env=BAZEL_LLVM_COV=/opt/llvm/bin/llvm-cov +build:mobile-ci-linux-coverage --test_env=BAZEL_LLVM_COV=/opt/llm/bin/llvm-cov +build:mobile-ci-linux-coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 +build:mobile-ci-linux-coverage --test_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 + +############################################################################# +# mobile-remote-ci-linux-coverage: These options are Linux-only using Clang and LLVM coverage ############################################################################# # Clang environment variables (keep in sync with //third_party/rbe_configs) -build:remote-ci-linux-coverage --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote-ci-linux-coverage --action_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-coverage --test_env=CC=/opt/llvm/bin/clang -build:remote-ci-linux-coverage --action_env=CXX=/opt/llvm/bin/clang++ -build:remote-ci-linux-coverage --test_env=CXX=/opt/llvm/bin/clang++ # Coverage environment variables (keep in sync with //third_party/rbe_configs) -build:remote-ci-linux-coverage --action_env=GCOV=/opt/llvm/bin/llvm-profdata -build:remote-ci-linux-coverage --test_env=GCOV=/opt/llvm/bin/llvm-profdata -build:remote-ci-linux-coverage --action_env=BAZEL_LLVM_COV=/opt/llvm/bin/llvm-cov -build:remote-ci-linux-coverage --test_env=BAZEL_LLVM_COV=/opt/llvm/bin/llvm-cov -build:remote-ci-linux-coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 -build:remote-ci-linux-coverage --test_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 -# Toolchain flags (Java is required for C++ coverage due to Bazel's LCOV merger) -build:remote-ci-linux-coverage --crosstool_top=//third_party/rbe_configs/cc:toolchain -build:remote-ci-linux-coverage --extra_execution_platforms=//third_party/rbe_configs/config:platform -build:remote-ci-linux-coverage --extra_toolchains=//third_party/rbe_configs/config:cc-toolchain -build:remote-ci-linux-coverage --host_platform=//third_party/rbe_configs/config:platform -build:remote-ci-linux-coverage --platforms=//third_party/rbe_configs/config:platform # Flags to run tests locally which are necessary since Bazel C++ LLVM coverage isn't fully supported for remote builds # TODO(lfpino): Reference upstream Bazel issue here on the incompatibility of remote test execution and LLVM coverage. -build:remote-ci-linux-coverage --remote_download_outputs=all -build:remote-ci-linux-coverage --strategy=TestRunner=local,remote -build:remote-ci-linux-coverage --strategy=CoverageReport=local,remote +build:mobile-remote-ci-linux-coverage --config=mobile-ci-linux-coverage +build:mobile-remote-ci-linux-coverage --config=mobile-remote-ci-linux-clang +build:mobile-remote-ci-linux-coverage --strategy=TestRunner=local,remote +build:mobile-remote-ci-linux-coverage --strategy=CoverageReport=local,remote # Bazel remote caching is incompatible with C++ LLVM coverage so we need to deactivate it for coverage builds # TODO(lfpino): Reference upstream Bazel issue here on the incompatibility of remote caching and LLVM coverage. -build:remote-ci-linux-coverage --noremote_accept_cached -build:remote-ci-linux-coverage --config=remote-ci-common -build:remote-ci-linux-coverage --build_runfile_links -build:remote-ci-linux-coverage --legacy_important_outputs=false -build:remote-ci-linux-coverage --test_env=CC_CODE_COVERAGE_SCRIPT=external/envoy/bazel/coverage/collect_cc_coverage.sh -build:remote-ci-linux-coverage --nocache_test_results +build:mobile-remote-ci-linux-coverage --noremote_accept_cached +build:mobile-remote-ci-linux-coverage --legacy_important_outputs=false +build:mobile-remote-ci-linux-coverage --nocache_test_results +build:mobile-remote-ci-linux-coverage --config=ci +build:mobile-remote-ci-linux-coverage --config=remote # IPv6 tests fail on CI -build:remote-ci-linux-coverage --test_env=ENVOY_IP_TEST_VERSIONS=v4only -############################################################################# -# remote-ci-macos: These options are macOS-only +build:mobile-remote-ci-linux-coverage --test_env=ENVOY_IP_TEST_VERSIONS=v4only ############################################################################# -build:remote-ci-macos --config=remote-ci-common -build:remote-ci-macos --host_platform=//ci/platform:macos -build:remote-ci-macos --platforms=//ci/platform:macos -build:remote-ci-macos --extra_execution_platforms=//ci/platform:macos -build:remote-ci-macos --xcode_version_config=//ci:xcode_config -############################################################################# -# remote-ci-debug: Various Bazel debugging flags -############################################################################# -common:remote-ci-debug --announce_rc -common:remote-ci-debug -s -common:remote-ci-debug -c dbg -common:remote-ci-debug --verbose_failures -common:remote-ci-debug --sandbox_debug -common:remote-ci-debug --action_env=VERBOSE_COVERAGE=true -common:remote-ci-debug --test_env=VERBOSE_COVERAGE=true -common:remote-ci-debug --test_env=DISPLAY_LCOV_CMD=true -############################################################################# -# Experimental EngFlow Remote Execution Configs. +# mobile-remote-ci-macos: These options are macOS-only ############################################################################# +build:mobile-remote-ci-macos --config=mobile-remote-ci-common +build:mobile-remote-ci-macos --host_platform=//ci/platform:macos +build:mobile-remote-ci-macos --platforms=//ci/platform:macos +build:mobile-remote-ci-macos --extra_execution_platforms=//ci/platform:macos +build:mobile-remote-ci-macos --xcode_version_config=//ci:xcode_config +build:mobile-remote-ci-macos --remote_download_toplevel +build:mobile-remote-ci-macos --config=ci +build:mobile-remote-ci-macos --config=remote + +build:mobile-remote-ci --config=mobile-remote-ci-linux-clang +build:mobile-remote-ci --config=remote-ci diff --git a/mobile/WORKSPACE b/mobile/WORKSPACE index 2fb069a3f27a..33f8ff94f634 100644 --- a/mobile/WORKSPACE +++ b/mobile/WORKSPACE @@ -5,10 +5,10 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "bazel_gazelle", - sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb", + sha256 = "29218f8e0cebe583643cbf93cae6f971be8a2484cdcfa1e45057658df8d54002", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", - "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.32.0/bazel-gazelle-v0.32.0.tar.gz", + "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.32.0/bazel-gazelle-v0.32.0.tar.gz", ], ) diff --git a/mobile/bazel/envoy_mobile_repositories.bzl b/mobile/bazel/envoy_mobile_repositories.bzl index 923e060bcf1a..86e64ddb9bbd 100644 --- a/mobile/bazel/envoy_mobile_repositories.bzl +++ b/mobile/bazel/envoy_mobile_repositories.bzl @@ -65,10 +65,11 @@ def swift_repos(): def kotlin_repos(): http_archive( name = "rules_java", - sha256 = "19462d64b1586c0d4ea0e87f9325be2514f0eb84e56dbf3245450451b3701581", - strip_prefix = "rules_java-43243982abc76390ef64be62379a1353f9011771", - # TODO(jpsim): Switch back to bazelbuild repo when https://github.com/bazelbuild/rules_java/issues/64 is fixed - url = "https://github.com/jpsim/rules_java/archive/43243982abc76390ef64be62379a1353f9011771.tar.gz", + sha256 = "241822bf5fad614e3e1c42431002abd9af757136fa590a6a7870c6e0640a82e3", + strip_prefix = "rules_java-6.4.0", + url = "https://github.com/bazelbuild/rules_java/archive/6.4.0.tar.gz", + patch_args = ["-p1"], + patches = ["@envoy//bazel:rules_java.patch"], ) http_archive( @@ -80,8 +81,8 @@ def kotlin_repos(): http_archive( name = "io_bazel_rules_kotlin", - sha256 = "f033fa36f51073eae224f18428d9493966e67c27387728b6be2ebbdae43f140e", - urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v1.7.0-RC-3/rules_kotlin_release.tgz"], + sha256 = "01293740a16e474669aba5b5a1fe3d368de5832442f164e4fbfc566815a8bc3a", + urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v1.8/rules_kotlin_release.tgz"], ) http_archive( diff --git a/mobile/bazel/kotlin_test.bzl b/mobile/bazel/kotlin_test.bzl index ae00ac6d08cc..704f06902ed6 100644 --- a/mobile/bazel/kotlin_test.bzl +++ b/mobile/bazel/kotlin_test.bzl @@ -36,7 +36,9 @@ def jvm_flags(lib_name): return [ "-Djava.library.path=library/common/jni:test/common/jni", "-Denvoy_jni_library_name={}".format(lib_name), - "-Xcheck:jni", + # TODO(RyanTheOptimist): Uncomment this when when + # https://github.com/envoyproxy/envoy/issues/28981 is fixed. + # "-Xcheck:jni", ] # A basic macro to make it easier to declare and run kotlin tests which depend on a JNI lib diff --git a/mobile/docs/conf.py b/mobile/docs/conf.py index 80270f95c7af..d4e2de2721b7 100644 --- a/mobile/docs/conf.py +++ b/mobile/docs/conf.py @@ -15,7 +15,7 @@ from datetime import datetime import os from sphinx.directives.code import CodeBlock -import sphinx_rtd_theme +from envoy.docs.sphinx_runner import sphinx_rtd_theme import sys diff --git a/mobile/docs/requirements.in b/mobile/docs/requirements.in new file mode 100644 index 000000000000..49986d8e63cb --- /dev/null +++ b/mobile/docs/requirements.in @@ -0,0 +1,59 @@ +abstracts>=0.0.12 +aio-api-github>=0.2.4 +aio-core>=0.10.0 +aio-run-runner>=0.3.3 +aiohttp>=3.8.5 +aiosignal>=1.3.1 +alabaster>=0.7.13 +async-timeout>=4.0.2 +attrs>=23.1.0 +babel>=2.12.1 +certifi>=2023.7.22 +cffi>=1.15.1 +charset-normalizer>=3.2.0 +colorama>=0.4.6 +coloredlogs>=15.0.1 +cryptography>=41.0.3 +docutils>=0.19 +envoy-base-utils>=0.4.11 +envoy-docs-sphinx-runner>=0.2.6 +frozendict>=2.3.8 +frozenlist>=1.4.0 +gidgethub>=5.3.0 +humanfriendly>=10.0 +idna>=3.4 +imagesize>=1.4.1 +jinja2>=3.1.2 +markupsafe>=2.1.3 +multidict>=6.0.4 +orjson>=3.9.2 +packaging>=23.1 +protobuf>=4.23.4 +pycparser>=2.21 +pygments>=2.15.1 +pyjwt[crypto]>=2.8.0 +pyparsing>=3.1.1 +pytz>=2023.3 +pyyaml>=6.0.1 +requests>=2.31.0 +six>=1.16.0 +snowballstemmer>=2.2.0 +sphinx>=7.1.2 +sphinx-copybutton>=0.5.2 +sphinxcontrib-applehelp>=1.0.4 +sphinxcontrib-devhelp>=1.0.2 +sphinxcontrib-googleanalytics>=0.4 +sphinxcontrib-htmlhelp>=2.0.1 +sphinxcontrib-httpdomain>=1.8.1 +sphinxcontrib-jquery>=4.1 +sphinxcontrib-jsmath>=1.0.1 +sphinxcontrib-qthelp>=1.0.3 +sphinxcontrib-serializinghtml>=1.1.5 +sphinxext-rediraffe>=0.2.7 +trycast>=1.0.0 +uritemplate>=4.1.1 +urllib3>=2.0.4 +uvloop>=0.17.0 +verboselogs>=1.7 +yarl>=1.9.2 +zstandard>=0.21.0 diff --git a/mobile/docs/requirements.txt b/mobile/docs/requirements.txt index 535a68bca954..2a4c54fe8a84 100644 --- a/mobile/docs/requirements.txt +++ b/mobile/docs/requirements.txt @@ -1,202 +1,1080 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes requirements.in +# +abstracts==0.0.12 \ + --hash=sha256:acc01ff56c8a05fb88150dff62e295f9071fc33388c42f1dfc2787a8d1c755ff + # via + # -r requirements.in + # aio-api-github + # aio-core + # aio-run-runner + # envoy-base-utils +aio-api-github==0.2.4 \ + --hash=sha256:ccbc7c6c61b25994e87474d78c48549e9fbc98c2cc04314b50b80ba1f40fd521 \ + --hash=sha256:eccfccd1503f50384de3f6526bd780ca02107cb440a666b2c1ab978d99c7db5e + # via + # -r requirements.in + # envoy-base-utils +aio-core==0.10.0 \ + --hash=sha256:57e2d8dd8ee8779b0ebc2e2447492c0db8d7ed782e9ad1bb2662593740751acb \ + --hash=sha256:cb00d530b1e16228b36174c23d446c66e3f2a50ef91aa527c0e58a654316f635 + # via + # -r requirements.in + # aio-api-github + # aio-run-runner + # envoy-base-utils +aio-run-runner==0.3.3 \ + --hash=sha256:0a783260838a660b4df085d163781fdb35991febf4818524e8f2a13d9f999e07 \ + --hash=sha256:ce904917303723b5495b951e3b6aaacbac2373d92b7a0bd8f562f1d920cfac22 + # via + # -r requirements.in + # envoy-base-utils + # envoy-docs-sphinx-runner +aiohttp==3.8.5 \ + --hash=sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67 \ + --hash=sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c \ + --hash=sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda \ + --hash=sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755 \ + --hash=sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d \ + --hash=sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5 \ + --hash=sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548 \ + --hash=sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690 \ + --hash=sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84 \ + --hash=sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4 \ + --hash=sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a \ + --hash=sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a \ + --hash=sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9 \ + --hash=sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef \ + --hash=sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b \ + --hash=sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a \ + --hash=sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d \ + --hash=sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945 \ + --hash=sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634 \ + --hash=sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7 \ + --hash=sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691 \ + --hash=sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802 \ + --hash=sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c \ + --hash=sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0 \ + --hash=sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8 \ + --hash=sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82 \ + --hash=sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a \ + --hash=sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975 \ + --hash=sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b \ + --hash=sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d \ + --hash=sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3 \ + --hash=sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7 \ + --hash=sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e \ + --hash=sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5 \ + --hash=sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649 \ + --hash=sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff \ + --hash=sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e \ + --hash=sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c \ + --hash=sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22 \ + --hash=sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df \ + --hash=sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e \ + --hash=sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780 \ + --hash=sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905 \ + --hash=sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51 \ + --hash=sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543 \ + --hash=sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6 \ + --hash=sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873 \ + --hash=sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f \ + --hash=sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35 \ + --hash=sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938 \ + --hash=sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b \ + --hash=sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d \ + --hash=sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8 \ + --hash=sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c \ + --hash=sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af \ + --hash=sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42 \ + --hash=sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3 \ + --hash=sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc \ + --hash=sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8 \ + --hash=sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410 \ + --hash=sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c \ + --hash=sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825 \ + --hash=sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9 \ + --hash=sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53 \ + --hash=sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a \ + --hash=sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc \ + --hash=sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8 \ + --hash=sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c \ + --hash=sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a \ + --hash=sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b \ + --hash=sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd \ + --hash=sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14 \ + --hash=sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2 \ + --hash=sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c \ + --hash=sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9 \ + --hash=sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692 \ + --hash=sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1 \ + --hash=sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa \ + --hash=sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a \ + --hash=sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de \ + --hash=sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91 \ + --hash=sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761 \ + --hash=sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd \ + --hash=sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced \ + --hash=sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28 \ + --hash=sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8 \ + --hash=sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824 + # via + # -r requirements.in + # aio-api-github + # envoy-base-utils +aiosignal==1.3.1 \ + --hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \ + --hash=sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17 + # via + # -r requirements.in + # aiohttp alabaster==0.7.13 \ --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \ --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2 -Babel==2.12.1 \ + # via + # -r requirements.in + # sphinx +async-timeout==4.0.2 \ + --hash=sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15 \ + --hash=sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c + # via + # -r requirements.in + # aiohttp +attrs==23.1.0 \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 + # via + # -r requirements.in + # aiohttp +babel==2.12.1 \ --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \ --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455 + # via + # -r requirements.in + # sphinx certifi==2023.7.22 \ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 -charset-normalizer==3.1.0 \ - --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \ - --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \ - --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \ - --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \ - --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \ - --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \ - --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \ - --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \ - --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \ - --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \ - --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \ - --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \ - --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \ - --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \ - --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \ - --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \ - --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \ - --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \ - --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \ - --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \ - --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \ - --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \ - --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \ - --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \ - --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \ - --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \ - --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \ - --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \ - --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \ - --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \ - --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \ - --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \ - --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \ - --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \ - --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \ - --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \ - --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \ - --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \ - --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \ - --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \ - --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \ - --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \ - --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \ - --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \ - --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \ - --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \ - --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \ - --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \ - --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \ - --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \ - --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \ - --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \ - --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \ - --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \ - --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \ - --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \ - --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \ - --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \ - --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \ - --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \ - --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \ - --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \ - --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \ - --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \ - --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \ - --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \ - --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \ - --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \ - --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \ - --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \ - --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \ - --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \ - --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \ - --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ - --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab + # via + # -r requirements.in + # requests +cffi==1.15.1 \ + --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ + --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \ + --hash=sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104 \ + --hash=sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426 \ + --hash=sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405 \ + --hash=sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375 \ + --hash=sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a \ + --hash=sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e \ + --hash=sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc \ + --hash=sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf \ + --hash=sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185 \ + --hash=sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497 \ + --hash=sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3 \ + --hash=sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35 \ + --hash=sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c \ + --hash=sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83 \ + --hash=sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21 \ + --hash=sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca \ + --hash=sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984 \ + --hash=sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac \ + --hash=sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd \ + --hash=sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee \ + --hash=sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a \ + --hash=sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2 \ + --hash=sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192 \ + --hash=sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7 \ + --hash=sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585 \ + --hash=sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f \ + --hash=sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e \ + --hash=sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27 \ + --hash=sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b \ + --hash=sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e \ + --hash=sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e \ + --hash=sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d \ + --hash=sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c \ + --hash=sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415 \ + --hash=sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82 \ + --hash=sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02 \ + --hash=sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314 \ + --hash=sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325 \ + --hash=sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c \ + --hash=sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3 \ + --hash=sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914 \ + --hash=sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045 \ + --hash=sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d \ + --hash=sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9 \ + --hash=sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5 \ + --hash=sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2 \ + --hash=sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c \ + --hash=sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3 \ + --hash=sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2 \ + --hash=sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8 \ + --hash=sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d \ + --hash=sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d \ + --hash=sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9 \ + --hash=sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162 \ + --hash=sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76 \ + --hash=sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4 \ + --hash=sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e \ + --hash=sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9 \ + --hash=sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6 \ + --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \ + --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ + --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 + # via + # -r requirements.in + # cryptography +charset-normalizer==3.2.0 \ + --hash=sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96 \ + --hash=sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c \ + --hash=sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710 \ + --hash=sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706 \ + --hash=sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020 \ + --hash=sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252 \ + --hash=sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad \ + --hash=sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329 \ + --hash=sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a \ + --hash=sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f \ + --hash=sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6 \ + --hash=sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4 \ + --hash=sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a \ + --hash=sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46 \ + --hash=sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2 \ + --hash=sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23 \ + --hash=sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace \ + --hash=sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd \ + --hash=sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982 \ + --hash=sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10 \ + --hash=sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2 \ + --hash=sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea \ + --hash=sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09 \ + --hash=sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5 \ + --hash=sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149 \ + --hash=sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489 \ + --hash=sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9 \ + --hash=sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80 \ + --hash=sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592 \ + --hash=sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3 \ + --hash=sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6 \ + --hash=sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed \ + --hash=sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c \ + --hash=sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200 \ + --hash=sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a \ + --hash=sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e \ + --hash=sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d \ + --hash=sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6 \ + --hash=sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623 \ + --hash=sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669 \ + --hash=sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3 \ + --hash=sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa \ + --hash=sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9 \ + --hash=sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2 \ + --hash=sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f \ + --hash=sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1 \ + --hash=sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4 \ + --hash=sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a \ + --hash=sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8 \ + --hash=sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3 \ + --hash=sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029 \ + --hash=sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f \ + --hash=sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959 \ + --hash=sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22 \ + --hash=sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7 \ + --hash=sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952 \ + --hash=sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346 \ + --hash=sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e \ + --hash=sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d \ + --hash=sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299 \ + --hash=sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd \ + --hash=sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a \ + --hash=sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3 \ + --hash=sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037 \ + --hash=sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94 \ + --hash=sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c \ + --hash=sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858 \ + --hash=sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a \ + --hash=sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449 \ + --hash=sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c \ + --hash=sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918 \ + --hash=sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1 \ + --hash=sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c \ + --hash=sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac \ + --hash=sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa + # via + # -r requirements.in + # aiohttp + # requests +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via + # -r requirements.in + # envoy-docs-sphinx-runner +coloredlogs==15.0.1 \ + --hash=sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934 \ + --hash=sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0 + # via + # -r requirements.in + # aio-run-runner +cryptography==41.0.3 \ + --hash=sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306 \ + --hash=sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84 \ + --hash=sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47 \ + --hash=sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d \ + --hash=sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116 \ + --hash=sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207 \ + --hash=sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81 \ + --hash=sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087 \ + --hash=sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd \ + --hash=sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507 \ + --hash=sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858 \ + --hash=sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae \ + --hash=sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34 \ + --hash=sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906 \ + --hash=sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd \ + --hash=sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922 \ + --hash=sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7 \ + --hash=sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4 \ + --hash=sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574 \ + --hash=sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1 \ + --hash=sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c \ + --hash=sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e \ + --hash=sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de + # via + # -r requirements.in + # pyjwt docutils==0.19 \ --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \ --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc + # via + # -r requirements.in + # envoy-docs-sphinx-runner + # sphinx +envoy-base-utils==0.4.11 \ + --hash=sha256:5ced696c470b4c3090e6fc3f74e7e33f5fe217e775b1fc1fb56dfc756b781fbe \ + --hash=sha256:97c79177bb89360b7772e58fb20c671c67bf0b6cdee74c0b9f8a80433f0370cc + # via + # -r requirements.in + # envoy-docs-sphinx-runner +envoy-docs-sphinx-runner==0.2.6 \ + --hash=sha256:045b23f182d9760df693e0c01f6bd0654d0c5690107945e262ce720a976920b7 \ + --hash=sha256:8166ff4b1b265efaf73db397d7ad0a1a743d71281ec233885c692c24f9349bd1 + # via -r requirements.in +frozendict==2.3.8 \ + --hash=sha256:0bc4767e2f83db5b701c787e22380296977368b0c57e485ca71b2eedfa11c4a3 \ + --hash=sha256:145afd033ebfade28416093335261b8ec1af5cccc593482309e7add062ec8668 \ + --hash=sha256:23c4bb46e6b8246e1e7e49b5593c2bc09221db0d8f31f7c092be8dfb42b9e620 \ + --hash=sha256:2b2fd8ce36277919b36e3c834d2389f3cd7ac068ae730c312671dd4439a5dd65 \ + --hash=sha256:2b3435e5f1ca5ae68a5e95e64b09d6d5c645cadd6b87569a0b3019dd248c8d00 \ + --hash=sha256:313ed8d9ba6bac35d7635cd9580ee5721a0fb016f4d2d20f0efa05dbecbdb1be \ + --hash=sha256:3957d52f1906b0c85f641a1911d214255873f6408ab4e5ad657cc27a247fb145 \ + --hash=sha256:4742e76c4111bd09198d3ab66cef94be8506212311338f9182d6ef5f5cb60493 \ + --hash=sha256:47fc26468407fdeb428cfc89495b7921419e670355c21b383765482fdf6c5c14 \ + --hash=sha256:4c258aab9c8488338634f2ec670ef049dbf0ab0e7a2fa9bc2c7b5009cb614801 \ + --hash=sha256:5526559eca8f1780a4ee5146896f59afc31435313560208dd394a3a5e537d3ff \ + --hash=sha256:5e82befa7c385a668d569cebbebbdf49cee6fea4083f08e869a1b08cfb640a9f \ + --hash=sha256:638cf363d3cbca31a341503cf2219eac52a5f5140449676fae3d9644cd3c5487 \ + --hash=sha256:6ea638228692db2bf94bce40ea4b25f4077588497b516bd16576575560094bd9 \ + --hash=sha256:72cfe08ab8ae524e54848fa90b22d02c1b1ecfb3064438696bcaa4b953f18772 \ + --hash=sha256:750632cc890d8ee9484fe6d31b261159144b6efacc08e1317fe46accd1410373 \ + --hash=sha256:7a75bf87e76c4386caecdbdd02a99e53ad43a6b5c38fb3d5a634a9fc9ce41462 \ + --hash=sha256:7ee5fe2658a8ac9a57f748acaf563f6a47f80b8308cbf0a04fac0ba057d41f75 \ + --hash=sha256:80abe81d36e889ceec665e06ec764a7638000fa3e7be09786ac4d3ddc64b76db \ + --hash=sha256:8ccc94ac781710db44e142e1a11ff9b31d02c032c01c6868d51fcbef73086225 \ + --hash=sha256:8cf35ddd25513428ec152614def9696afb93ae5ec0eb54fa6aa6206eda77ac4c \ + --hash=sha256:9a506d807858fa961aaa5b48dab6154fdc6bd045bbe9310788bbff141bb42d13 \ + --hash=sha256:9ea5520e85447ff8d4681e181941e482662817ccba921b7cb3f87922056d892a \ + --hash=sha256:ba41a7ed019bd03b62d63ed3f8dea35b8243d1936f7c9ed4b5298ca45a01928e \ + --hash=sha256:c31abc8acea309b132dde441856829f6003a3d242da8b54bce4c0f2a3c8c63f0 \ + --hash=sha256:d086440328a465dea9bef2dbad7548d75d1a0a0d21f43a08c03e1ec79ac5240e \ + --hash=sha256:d188d062084fba0e4bf32719ff7380b26c050b932ff164043ce82ab90587c52b \ + --hash=sha256:d3c6ce943946c2a61501c8cf116fff0892d11dd579877eb36e2aea2c27fddfef \ + --hash=sha256:da98427de26b5a2865727947480cbb53860089c4d195baa29c539da811cea617 \ + --hash=sha256:e27c5c1d29d0eda7979253ec88abc239da1313b38f39f4b16984db3b3e482300 \ + --hash=sha256:e4c785de7f1a13f15963945f400656b18f057c2fc76c089dacf127a2bb188c03 \ + --hash=sha256:e72dbc1bcc2203cef38d205f692396f5505921a5680f66aa9a7e8bb71fd38f28 \ + --hash=sha256:ed5a6c5c7a0f57269577c2a338a6002949aea21a23b7b7d06da7e7dced8b605b \ + --hash=sha256:f0f573dc4861dd7ec9e055c8cceaf45355e894e749f621f199aab7b311ac4bdb \ + --hash=sha256:f2a4e818ac457f6354401dcb631527af25e5a20fcfc81e6b5054b45fc245caca \ + --hash=sha256:f83fed36497af9562ead5e9fb8443224ba2781786bd3b92b1087cb7d0ff20135 \ + --hash=sha256:ffc684773de7c88724788fa9787d0016fd75830412d58acbd9ed1a04762c675b + # via + # -r requirements.in + # aio-run-runner + # envoy-base-utils +frozenlist==1.4.0 \ + --hash=sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6 \ + --hash=sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01 \ + --hash=sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251 \ + --hash=sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9 \ + --hash=sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b \ + --hash=sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87 \ + --hash=sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf \ + --hash=sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f \ + --hash=sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0 \ + --hash=sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2 \ + --hash=sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b \ + --hash=sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc \ + --hash=sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c \ + --hash=sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467 \ + --hash=sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9 \ + --hash=sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1 \ + --hash=sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a \ + --hash=sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79 \ + --hash=sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167 \ + --hash=sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300 \ + --hash=sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf \ + --hash=sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea \ + --hash=sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2 \ + --hash=sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab \ + --hash=sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3 \ + --hash=sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb \ + --hash=sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087 \ + --hash=sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc \ + --hash=sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8 \ + --hash=sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62 \ + --hash=sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f \ + --hash=sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326 \ + --hash=sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c \ + --hash=sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431 \ + --hash=sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963 \ + --hash=sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7 \ + --hash=sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef \ + --hash=sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3 \ + --hash=sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956 \ + --hash=sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781 \ + --hash=sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472 \ + --hash=sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc \ + --hash=sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839 \ + --hash=sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672 \ + --hash=sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3 \ + --hash=sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503 \ + --hash=sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d \ + --hash=sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8 \ + --hash=sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b \ + --hash=sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc \ + --hash=sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f \ + --hash=sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559 \ + --hash=sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b \ + --hash=sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95 \ + --hash=sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb \ + --hash=sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963 \ + --hash=sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919 \ + --hash=sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f \ + --hash=sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3 \ + --hash=sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1 \ + --hash=sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e + # via + # -r requirements.in + # aiohttp + # aiosignal +gidgethub==5.3.0 \ + --hash=sha256:4dd92f2252d12756b13f9dd15cde322bfb0d625b6fb5d680da1567ec74b462c0 \ + --hash=sha256:9ece7d37fbceb819b80560e7ed58f936e48a65d37ec5f56db79145156b426a25 + # via + # -r requirements.in + # aio-api-github +humanfriendly==10.0 \ + --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \ + --hash=sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc + # via + # -r requirements.in + # coloredlogs idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 + # via + # -r requirements.in + # requests + # yarl imagesize==1.4.1 \ --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a -Jinja2==3.1.2 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 -MarkupSafe==2.1.2 \ - --hash=sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed \ - --hash=sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc \ - --hash=sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2 \ - --hash=sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460 \ - --hash=sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7 \ - --hash=sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0 \ - --hash=sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1 \ - --hash=sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa \ - --hash=sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03 \ - --hash=sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 \ - --hash=sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65 \ - --hash=sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013 \ - --hash=sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036 \ - --hash=sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f \ - --hash=sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4 \ - --hash=sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419 \ - --hash=sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2 \ - --hash=sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619 \ - --hash=sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a \ - --hash=sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a \ - --hash=sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd \ - --hash=sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7 \ - --hash=sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666 \ - --hash=sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65 \ - --hash=sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859 \ - --hash=sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625 \ - --hash=sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff \ - --hash=sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156 \ - --hash=sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd \ - --hash=sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba \ - --hash=sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f \ - --hash=sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1 \ - --hash=sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094 \ - --hash=sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a \ - --hash=sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513 \ - --hash=sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed \ - --hash=sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d \ - --hash=sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3 \ - --hash=sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147 \ - --hash=sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c \ - --hash=sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603 \ - --hash=sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601 \ - --hash=sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a \ - --hash=sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1 \ - --hash=sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d \ - --hash=sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3 \ - --hash=sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54 \ - --hash=sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2 \ - --hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \ - --hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58 + # via + # -r requirements.in + # sphinx +jinja2==3.1.2 \ + --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ + --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 + # via + # -r requirements.in + # envoy-base-utils + # sphinx +markupsafe==2.1.3 \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 + # via + # -r requirements.in + # jinja2 +multidict==6.0.4 \ + --hash=sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9 \ + --hash=sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8 \ + --hash=sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03 \ + --hash=sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710 \ + --hash=sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161 \ + --hash=sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664 \ + --hash=sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569 \ + --hash=sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067 \ + --hash=sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313 \ + --hash=sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706 \ + --hash=sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2 \ + --hash=sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636 \ + --hash=sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49 \ + --hash=sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93 \ + --hash=sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603 \ + --hash=sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0 \ + --hash=sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60 \ + --hash=sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4 \ + --hash=sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e \ + --hash=sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1 \ + --hash=sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60 \ + --hash=sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951 \ + --hash=sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc \ + --hash=sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe \ + --hash=sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95 \ + --hash=sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d \ + --hash=sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8 \ + --hash=sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed \ + --hash=sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2 \ + --hash=sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775 \ + --hash=sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87 \ + --hash=sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c \ + --hash=sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2 \ + --hash=sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98 \ + --hash=sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3 \ + --hash=sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe \ + --hash=sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78 \ + --hash=sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660 \ + --hash=sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176 \ + --hash=sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e \ + --hash=sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988 \ + --hash=sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c \ + --hash=sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c \ + --hash=sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0 \ + --hash=sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449 \ + --hash=sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f \ + --hash=sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde \ + --hash=sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5 \ + --hash=sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d \ + --hash=sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac \ + --hash=sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a \ + --hash=sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9 \ + --hash=sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca \ + --hash=sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11 \ + --hash=sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35 \ + --hash=sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063 \ + --hash=sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b \ + --hash=sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982 \ + --hash=sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258 \ + --hash=sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1 \ + --hash=sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52 \ + --hash=sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480 \ + --hash=sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7 \ + --hash=sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461 \ + --hash=sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d \ + --hash=sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc \ + --hash=sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779 \ + --hash=sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a \ + --hash=sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547 \ + --hash=sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0 \ + --hash=sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171 \ + --hash=sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf \ + --hash=sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d \ + --hash=sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba + # via + # -r requirements.in + # aiohttp + # yarl +orjson==3.9.4 \ + --hash=sha256:004f0d307473af210717260dab2ddceab26750ef5d2c6b1f7454c33f7bb69f0c \ + --hash=sha256:04cd7f4a4f4cd2fe43d104eb70e7435c6fcbdde7aa0cde4230e444fbc66924d3 \ + --hash=sha256:0725260a12d7102b6e66f9925a027f55567255d8455f8288b02d5eedc8925c3e \ + --hash=sha256:0a31c2cab0ba86998205c2eba550c178a8b4ee7905cadeb402eed45392edb178 \ + --hash=sha256:0b400cf89c15958cd829c8a4ade8f5dd73588e63d2fb71a00483e7a74e9f92da \ + --hash=sha256:0e7c3b7e29572ef2d845a59853475f40fdabec53b8b7d6effda4bb26119c07f5 \ + --hash=sha256:144a3b8c7cbdd301e1b8cd7dd33e3cbfe7b011df2bebd45b84bacc8cb490302d \ + --hash=sha256:149d1b7630771222f73ecb024ab5dd8e7f41502402b02015494d429bacc4d5c1 \ + --hash=sha256:161cc72dd3ff569fd67da4af3a23c0c837029085300f0cebc287586ae3b559e0 \ + --hash=sha256:1e4b20164809b21966b63e063f894927bc85391e60d0a96fa0bb552090f1319c \ + --hash=sha256:1fb36efdf2a35286fb87cfaa195fc34621389da1c7b28a8eb51a4d212d60e56d \ + --hash=sha256:220ca4125416636a3d6b53a77d50434987a83da243f9080ee4cce7ac6a34bb4a \ + --hash=sha256:224ad19dcdc21bb220d893807f2563e219319a8891ead3c54243b51a4882d767 \ + --hash=sha256:23d3b6f2706cb324661899901e6b1fcaee4f5aac7d7588306df3f43e68173840 \ + --hash=sha256:264637cad35a1755ab90a8ea290076d444deda20753e55a0eb75496a4645f7bc \ + --hash=sha256:2e83ec1ee66d83b558a6d273d8a01b86563daa60bea9bc040e2c1cb8008de61f \ + --hash=sha256:2f57ccb50e9e123709e9f2d7b1a9e09e694e49d1fa5c5585e34b8e3f01929dc3 \ + --hash=sha256:32a9e0f140c7d0d52f79553cabd1a471f6a4f187c59742239939f1139258a053 \ + --hash=sha256:336ec8471102851f0699198031924617b7a77baadea889df3ffda6000bd59f4c \ + --hash=sha256:3932b06abf49135c93816c74139c7937fa54079fce3f44db2d598859894c344a \ + --hash=sha256:3b9f8bf43a5367d5522f80e7d533c98d880868cd0b640b9088c9237306eca6e8 \ + --hash=sha256:3d947366127abef192419257eb7db7fcee0841ced2b49ccceba43b65e9ce5e3f \ + --hash=sha256:4974cc2ebb53196081fef96743c02c8b073242b20a40b65d2aa2365ba8c949df \ + --hash=sha256:4fdb59cfa00e10c82e09d1c32a9ce08a38bd29496ba20a73cd7f498e3a0a5024 \ + --hash=sha256:53b417cc9465dbb42ec9cd7be744a921a0ce583556315d172a246d6e71aa043b \ + --hash=sha256:562cf24f9f11df8099e0e78859ba6729e7caa25c2f3947cb228d9152946c854b \ + --hash=sha256:5e876ef36801b3d4d3a4b0613b6144b0b47f13f3043fd1fcdfafd783c174b538 \ + --hash=sha256:644728d803200d7774164d252a247e2fcb0d19e4ef7a4a19a1a139ae472c551b \ + --hash=sha256:6bae10f4e7a9145b120e37b6456f1d3853a953e5131fe4740a764e46420289f5 \ + --hash=sha256:73d9507a547202f0dd0672e529ce3ca45582d152369c684a9ce75677ce5ae089 \ + --hash=sha256:915da36bc93ef0c659fa50fe7939d4f208804ad252fc4fc8d55adbbb82293c48 \ + --hash=sha256:94d15ee45c2aaed334688e511aa73b4681f7c08a0810884c6b3ae5824dea1222 \ + --hash=sha256:9ab3720fba68cc1c0bad00803d2c5e2c70177da5af12c45e18cc4d14426d56d8 \ + --hash=sha256:9c32dea3b27a97ac88783c1eb61ccb531865bf478a37df3707cbc96ca8f34a04 \ + --hash=sha256:a4c9254d21fc44526a3850355b89afd0d00ed73bdf902a5ab416df14a61eac6b \ + --hash=sha256:a4f12e9ec62679c3f2717d9ec41b497a2c2af0b1361229db0dc86ef078a4c034 \ + --hash=sha256:a533e664a0e3904307d662c5d45775544dc2b38df6e39e213ff6a86ceaa3d53c \ + --hash=sha256:a7d029fc34a516f7eae29b778b30371fcb621134b2acfe4c51c785102aefc6cf \ + --hash=sha256:b39747f8e57728b9d8c26bd1d28e9a31c028717617a5938a179244b9436c0b31 \ + --hash=sha256:b5b5038187b74e2d33e5caee8a7e83ddeb6a21da86837fa2aac95c69aeb366e6 \ + --hash=sha256:b749d06a3d84ac27311cb85fb5e8f965efd1c5f27556ad8fcfd1853c323b4d54 \ + --hash=sha256:b75f0fc7a64a95027c6f0c70f17969299bdf2b6a85e342b29fc23be2788bad6f \ + --hash=sha256:ba21fe581a83555024f3cfc9182a2390a61bc50430364855022c518b8ba285a4 \ + --hash=sha256:bcda6179eb863c295eb5ea832676d33ef12c04d227b4c98267876c8322e5a96e \ + --hash=sha256:c2fb7963c17ab347428412a0689f5c89ea480f5d5f7ba3e46c6c2f14f3159ee4 \ + --hash=sha256:c416c50f63bfcf453b6e28d1df956938486191fd1a15aeb95107e810e6e219c8 \ + --hash=sha256:c4fcd1ac0b7850f85398fd9fdbc7150ac4e82d2ae6754cc6acaf49ca7c30d79a \ + --hash=sha256:c65df12f92e771361dca45765fcac3d97491799ee8ab3c6c5ecf0155a397a313 \ + --hash=sha256:d73c0fd54a52a1a1abfad69d4f1dfb7048cd0b3ef1828ddb4920ef2d3739d8fb \ + --hash=sha256:daeed2502ddf1f2b29ec8da2fe2ea82807a5c4acf869608ce6c476db8171d070 \ + --hash=sha256:e12492ce65cb10f385e70a88badc6046bc720fa7d468db27b7429d85d41beaeb \ + --hash=sha256:edcbccfe852d1d3d56cc8bfc5fa3688c866619328a73cb2394e79b29b4ab24d2 \ + --hash=sha256:ef7119ebc9b76d5e37c330596616c697d1957779c916aec30cefd28df808f796 \ + --hash=sha256:f009c1a02773bdecdd1157036918fef1da47f7193d4ad599c9edb1e1960a0491 \ + --hash=sha256:f0a4cf31bfa94cd235aa50030bef3df529e4eb2893ea6a7771c0fb087e4e53b2 \ + --hash=sha256:fb429c56ea645e084e34976c2ea0efca7661ee961f61e51405f28bc5a9d1fb24 + # via + # -r requirements.in + # envoy-base-utils packaging==23.1 \ --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f -Pygments==2.15.1 \ - --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ - --hash=sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1 -pyparsing==3.0.9 \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb + # via + # -r requirements.in + # aio-api-github + # envoy-base-utils + # envoy-docs-sphinx-runner + # sphinx +protobuf==4.23.4 \ + --hash=sha256:0a5759f5696895de8cc913f084e27fd4125e8fb0914bb729a17816a33819f474 \ + --hash=sha256:351cc90f7d10839c480aeb9b870a211e322bf05f6ab3f55fcb2f51331f80a7d2 \ + --hash=sha256:5fea3c64d41ea5ecf5697b83e41d09b9589e6f20b677ab3c48e5f242d9b7897b \ + --hash=sha256:6dd9b9940e3f17077e820b75851126615ee38643c2c5332aa7a359988820c720 \ + --hash=sha256:7b19b6266d92ca6a2a87effa88ecc4af73ebc5cfde194dc737cf8ef23a9a3b12 \ + --hash=sha256:8547bf44fe8cec3c69e3042f5c4fb3e36eb2a7a013bb0a44c018fc1e427aafbd \ + --hash=sha256:9053df6df8e5a76c84339ee4a9f5a2661ceee4a0dab019e8663c50ba324208b0 \ + --hash=sha256:c3e0939433c40796ca4cfc0fac08af50b00eb66a40bbbc5dee711998fb0bbc1e \ + --hash=sha256:ccd9430c0719dce806b93f89c91de7977304729e55377f872a92465d548329a9 \ + --hash=sha256:e1c915778d8ced71e26fcf43c0866d7499891bca14c4368448a82edc61fdbc70 \ + --hash=sha256:e9d0be5bf34b275b9f87ba7407796556abeeba635455d036c7351f7c183ef8ff \ + --hash=sha256:effeac51ab79332d44fba74660d40ae79985901ac21bca408f8dc335a81aa597 \ + --hash=sha256:fee88269a090ada09ca63551bf2f573eb2424035bcf2cb1b121895b01a46594a + # via + # -r requirements.in + # envoy-base-utils + # envoy-docs-sphinx-runner +pycparser==2.21 \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 + # via + # -r requirements.in + # cffi +pygments==2.16.1 \ + --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ + --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 + # via + # -r requirements.in + # envoy-docs-sphinx-runner + # sphinx +pyjwt[crypto]==2.8.0 \ + --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ + --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 + # via + # -r requirements.in + # gidgethub +pyparsing==3.1.1 \ + --hash=sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb \ + --hash=sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db + # via -r requirements.in pytz==2023.3 \ --hash=sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588 \ --hash=sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb + # via + # -r requirements.in + # aio-core + # envoy-base-utils +pyyaml==6.0.1 \ + --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ + --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ + --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ + --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \ + --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ + --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ + --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ + --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ + --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ + --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \ + --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ + --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ + --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ + --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ + --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ + --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ + --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ + --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \ + --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \ + --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \ + --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \ + --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \ + --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \ + --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \ + --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \ + --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ + --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ + --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ + --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ + --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ + --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \ + --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ + --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f + # via + # -r requirements.in + # aio-core + # envoy-base-utils requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 + # via + # -r requirements.in + # sphinx six==1.16.0 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via + # -r requirements.in + # sphinxcontrib-httpdomain snowballstemmer==2.2.0 \ - --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a \ - --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 -Sphinx==7.0.0 \ - --hash=sha256:283c44aa28922bb4223777b44ac0d59af50a279ac7690dfe945bb2b9575dc41b \ - --hash=sha256:3cfc1c6756ef1b132687b813ec6ea2214cb7a7e5d1dcb2772006cb895a0fa469 -sphinx-rtd-theme==1.2.0 \ - --hash=sha256:a0d8bd1a2ed52e0b338cbe19c4b2eef3c5e7a048769753dac6a9f059c7b641b8 \ - --hash=sha256:f823f7e71890abe0ac6aaa6013361ea2696fc8d3e1fa798f463e82bdb77eeff2 + --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ + --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a + # via + # -r requirements.in + # sphinx +sphinx==7.1.2 \ + --hash=sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f \ + --hash=sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe + # via + # -r requirements.in + # envoy-docs-sphinx-runner + # sphinx-copybutton + # sphinxcontrib-googleanalytics + # sphinxcontrib-httpdomain + # sphinxcontrib-jquery + # sphinxext-rediraffe +sphinx-copybutton==0.5.2 \ + --hash=sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd \ + --hash=sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e + # via + # -r requirements.in + # envoy-docs-sphinx-runner sphinxcontrib-applehelp==1.0.4 \ --hash=sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228 \ --hash=sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e + # via + # -r requirements.in + # sphinx sphinxcontrib-devhelp==1.0.2 \ --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \ --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4 + # via + # -r requirements.in + # sphinx +sphinxcontrib-googleanalytics==0.4 \ + --hash=sha256:4b19c1f0fce5df6c7da5633201b64a9e5b0cb3210a14fdb4134942ceee8c5d12 \ + --hash=sha256:a6574983f9a58e5864ec10d34dc99914c4d647108b22c9249c8f0038b0cb18b3 + # via -r requirements.in sphinxcontrib-htmlhelp==2.0.1 \ --hash=sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff \ --hash=sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903 + # via + # -r requirements.in + # sphinx sphinxcontrib-httpdomain==1.8.1 \ --hash=sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5 \ --hash=sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b -sphinxcontrib-googleanalytics==0.4 \ - --hash=sha256:4b19c1f0fce5df6c7da5633201b64a9e5b0cb3210a14fdb4134942ceee8c5d12 \ - --hash=sha256:a6574983f9a58e5864ec10d34dc99914c4d647108b22c9249c8f0038b0cb18b3 + # via + # -r requirements.in + # envoy-docs-sphinx-runner +sphinxcontrib-jquery==4.1 \ + --hash=sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a \ + --hash=sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae + # via + # -r requirements.in + # envoy-docs-sphinx-runner sphinxcontrib-jsmath==1.0.1 \ --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 + # via + # -r requirements.in + # sphinx sphinxcontrib-qthelp==1.0.3 \ - --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 \ - --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 + --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ + --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 + # via + # -r requirements.in + # sphinx sphinxcontrib-serializinghtml==1.1.5 \ --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \ --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952 -urllib3==2.0.3 \ - --hash=sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1 \ - --hash=sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825 + # via + # -r requirements.in + # envoy-docs-sphinx-runner + # sphinx +sphinxext-rediraffe==0.2.7 \ + --hash=sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d \ + --hash=sha256:9e430a52d4403847f4ffb3a8dd6dfc34a9fe43525305131f52ed899743a5fd8c + # via + # -r requirements.in + # envoy-docs-sphinx-runner +trycast==1.0.0 \ + --hash=sha256:72dd9be7553337cfba04809646dc144ddd6e3a54f4213ab8b76a14b4a1882e04 \ + --hash=sha256:93fcbb922ef3a22c587f636ed8c128266bb2615171983dbf2f7edd3ae86afb10 + # via + # -r requirements.in + # aio-core + # envoy-base-utils +uritemplate==4.1.1 \ + --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ + --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e + # via + # -r requirements.in + # gidgethub +urllib3==2.0.4 \ + --hash=sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11 \ + --hash=sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4 + # via + # -r requirements.in + # requests +uvloop==0.17.0 \ + --hash=sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d \ + --hash=sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1 \ + --hash=sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595 \ + --hash=sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b \ + --hash=sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05 \ + --hash=sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8 \ + --hash=sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20 \ + --hash=sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded \ + --hash=sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c \ + --hash=sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8 \ + --hash=sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474 \ + --hash=sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f \ + --hash=sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62 \ + --hash=sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376 \ + --hash=sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c \ + --hash=sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e \ + --hash=sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b \ + --hash=sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4 \ + --hash=sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578 \ + --hash=sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811 \ + --hash=sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d \ + --hash=sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738 \ + --hash=sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa \ + --hash=sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9 \ + --hash=sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539 \ + --hash=sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c \ + --hash=sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718 \ + --hash=sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667 \ + --hash=sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c \ + --hash=sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024 + # via + # -r requirements.in + # aio-run-runner +verboselogs==1.7 \ + --hash=sha256:d63f23bf568295b95d3530c6864a0b580cec70e7ff974177dead1e4ffbc6ff49 \ + --hash=sha256:e33ddedcdfdafcb3a174701150430b11b46ceb64c2a9a26198c76a156568e427 + # via + # -r requirements.in + # aio-run-runner +yarl==1.9.2 \ + --hash=sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571 \ + --hash=sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3 \ + --hash=sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3 \ + --hash=sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c \ + --hash=sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7 \ + --hash=sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04 \ + --hash=sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191 \ + --hash=sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea \ + --hash=sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4 \ + --hash=sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4 \ + --hash=sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095 \ + --hash=sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e \ + --hash=sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74 \ + --hash=sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef \ + --hash=sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33 \ + --hash=sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde \ + --hash=sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45 \ + --hash=sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf \ + --hash=sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b \ + --hash=sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac \ + --hash=sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0 \ + --hash=sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528 \ + --hash=sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716 \ + --hash=sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb \ + --hash=sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18 \ + --hash=sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72 \ + --hash=sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6 \ + --hash=sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582 \ + --hash=sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5 \ + --hash=sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368 \ + --hash=sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc \ + --hash=sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9 \ + --hash=sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be \ + --hash=sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a \ + --hash=sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80 \ + --hash=sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8 \ + --hash=sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6 \ + --hash=sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417 \ + --hash=sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574 \ + --hash=sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59 \ + --hash=sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608 \ + --hash=sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82 \ + --hash=sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1 \ + --hash=sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3 \ + --hash=sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d \ + --hash=sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8 \ + --hash=sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc \ + --hash=sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac \ + --hash=sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8 \ + --hash=sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955 \ + --hash=sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0 \ + --hash=sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367 \ + --hash=sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb \ + --hash=sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a \ + --hash=sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623 \ + --hash=sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2 \ + --hash=sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6 \ + --hash=sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7 \ + --hash=sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4 \ + --hash=sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051 \ + --hash=sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938 \ + --hash=sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8 \ + --hash=sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9 \ + --hash=sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3 \ + --hash=sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5 \ + --hash=sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9 \ + --hash=sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333 \ + --hash=sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185 \ + --hash=sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3 \ + --hash=sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560 \ + --hash=sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b \ + --hash=sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7 \ + --hash=sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78 \ + --hash=sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7 + # via + # -r requirements.in + # aiohttp +zstandard==0.21.0 \ + --hash=sha256:0aad6090ac164a9d237d096c8af241b8dcd015524ac6dbec1330092dba151657 \ + --hash=sha256:0bdbe350691dec3078b187b8304e6a9c4d9db3eb2d50ab5b1d748533e746d099 \ + --hash=sha256:0e1e94a9d9e35dc04bf90055e914077c80b1e0c15454cc5419e82529d3e70728 \ + --hash=sha256:1243b01fb7926a5a0417120c57d4c28b25a0200284af0525fddba812d575f605 \ + --hash=sha256:144a4fe4be2e747bf9c646deab212666e39048faa4372abb6a250dab0f347a29 \ + --hash=sha256:14e10ed461e4807471075d4b7a2af51f5234c8f1e2a0c1d37d5ca49aaaad49e8 \ + --hash=sha256:1545fb9cb93e043351d0cb2ee73fa0ab32e61298968667bb924aac166278c3fc \ + --hash=sha256:1e6e131a4df2eb6f64961cea6f979cdff22d6e0d5516feb0d09492c8fd36f3bc \ + --hash=sha256:25fbfef672ad798afab12e8fd204d122fca3bc8e2dcb0a2ba73bf0a0ac0f5f07 \ + --hash=sha256:2769730c13638e08b7a983b32cb67775650024632cd0476bf1ba0e6360f5ac7d \ + --hash=sha256:48b6233b5c4cacb7afb0ee6b4f91820afbb6c0e3ae0fa10abbc20000acdf4f11 \ + --hash=sha256:4af612c96599b17e4930fe58bffd6514e6c25509d120f4eae6031b7595912f85 \ + --hash=sha256:52b2b5e3e7670bd25835e0e0730a236f2b0df87672d99d3bf4bf87248aa659fb \ + --hash=sha256:57ac078ad7333c9db7a74804684099c4c77f98971c151cee18d17a12649bc25c \ + --hash=sha256:62957069a7c2626ae80023998757e27bd28d933b165c487ab6f83ad3337f773d \ + --hash=sha256:649a67643257e3b2cff1c0a73130609679a5673bf389564bc6d4b164d822a7ce \ + --hash=sha256:67829fdb82e7393ca68e543894cd0581a79243cc4ec74a836c305c70a5943f07 \ + --hash=sha256:7d3bc4de588b987f3934ca79140e226785d7b5e47e31756761e48644a45a6766 \ + --hash=sha256:7f2afab2c727b6a3d466faee6974a7dad0d9991241c498e7317e5ccf53dbc766 \ + --hash=sha256:8070c1cdb4587a8aa038638acda3bd97c43c59e1e31705f2766d5576b329e97c \ + --hash=sha256:8257752b97134477fb4e413529edaa04fc0457361d304c1319573de00ba796b1 \ + --hash=sha256:9980489f066a391c5572bc7dc471e903fb134e0b0001ea9b1d3eff85af0a6f1b \ + --hash=sha256:9cff89a036c639a6a9299bf19e16bfb9ac7def9a7634c52c257166db09d950e7 \ + --hash=sha256:a8d200617d5c876221304b0e3fe43307adde291b4a897e7b0617a61611dfff6a \ + --hash=sha256:a9fec02ce2b38e8b2e86079ff0b912445495e8ab0b137f9c0505f88ad0d61296 \ + --hash=sha256:b1367da0dde8ae5040ef0413fb57b5baeac39d8931c70536d5f013b11d3fc3a5 \ + --hash=sha256:b69cccd06a4a0a1d9fb3ec9a97600055cf03030ed7048d4bcb88c574f7895773 \ + --hash=sha256:b72060402524ab91e075881f6b6b3f37ab715663313030d0ce983da44960a86f \ + --hash=sha256:c053b7c4cbf71cc26808ed67ae955836232f7638444d709bfc302d3e499364fa \ + --hash=sha256:cff891e37b167bc477f35562cda1248acc115dbafbea4f3af54ec70821090965 \ + --hash=sha256:d12fa383e315b62630bd407477d750ec96a0f438447d0e6e496ab67b8b451d39 \ + --hash=sha256:d2d61675b2a73edcef5e327e38eb62bdfc89009960f0e3991eae5cc3d54718de \ + --hash=sha256:db62cbe7a965e68ad2217a056107cc43d41764c66c895be05cf9c8b19578ce9c \ + --hash=sha256:ddb086ea3b915e50f6604be93f4f64f168d3fc3cef3585bb9a375d5834392d4f \ + --hash=sha256:df28aa5c241f59a7ab524f8ad8bb75d9a23f7ed9d501b0fed6d40ec3064784e8 \ + --hash=sha256:e1e0c62a67ff425927898cf43da2cf6b852289ebcc2054514ea9bf121bec10a5 \ + --hash=sha256:e6048a287f8d2d6e8bc67f6b42a766c61923641dd4022b7fd3f7439e17ba5a4d \ + --hash=sha256:e7d560ce14fd209db6adacce8908244503a009c6c39eee0c10f138996cd66d3e \ + --hash=sha256:ea68b1ba4f9678ac3d3e370d96442a6332d431e5050223626bdce748692226ea \ + --hash=sha256:f08e3a10d01a247877e4cb61a82a319ea746c356a3786558bed2481e6c405546 \ + --hash=sha256:f1b9703fe2e6b6811886c44052647df7c37478af1b4a1a9078585806f42e5b15 \ + --hash=sha256:fe6c821eb6870f81d73bf10e5deed80edcac1e63fbc40610e61f340723fd5f7c \ + --hash=sha256:ff0852da2abe86326b20abae912d0367878dd0854b8931897d44cfeb18985472 + # via + # -r requirements.in + # envoy-base-utils diff --git a/mobile/examples/cc/fetch_client/BUILD b/mobile/examples/cc/fetch_client/BUILD index 15b0e1892882..ea38a6de8254 100644 --- a/mobile/examples/cc/fetch_client/BUILD +++ b/mobile/examples/cc/fetch_client/BUILD @@ -43,3 +43,27 @@ cc_binary( "@envoy//source/exe:process_wide_lib", ], ) + +cc_test( + name = "fetch_client_test", + srcs = ["fetch_client_test.cc"], + tags = [ + "requires-net:external", + "requires-net:ipv4", + ], + deps = [ + ":fetch_client_lib", + "//testing/base/public:gunit_main", + "//third_party/envoy/src/mobile/envoy_build_config:extension_registry", + "//third_party/envoy/src/mobile/library/cc:envoy_engine_cc_lib_no_stamp", + "//third_party/envoy/src/source/common/api:api_lib", + "//third_party/envoy/src/source/common/common:random_generator_lib", + "//third_party/envoy/src/source/common/common:thread_lib", + "//third_party/envoy/src/source/common/event:real_time_system_lib", + "//third_party/envoy/src/source/common/stats:allocator_lib", + "//third_party/envoy/src/source/common/stats:thread_local_store_lib", + "//third_party/envoy/src/source/exe:platform_header_lib", + "//third_party/envoy/src/source/exe:platform_impl_lib", + "//third_party/envoy/src/source/exe:process_wide_lib", + ], +) diff --git a/mobile/examples/cc/fetch_client/fetch_client.cc b/mobile/examples/cc/fetch_client/fetch_client.cc index b5cb65f6eb88..430c3252c9cf 100644 --- a/mobile/examples/cc/fetch_client/fetch_client.cc +++ b/mobile/examples/cc/fetch_client/fetch_client.cc @@ -101,6 +101,7 @@ void Fetch::sendRequest(const absl::string_view url_string) { void Fetch::runEngine(absl::Notification& engine_running) { Platform::EngineBuilder engine_builder; + engine_builder.addLogLevel(Envoy::Platform::LogLevel::debug); engine_builder.setOnEngineRunning([&engine_running]() { engine_running.Notify(); }); engine_ = engine_builder.build(); dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); diff --git a/mobile/examples/java/hello_world/MainActivity.java b/mobile/examples/java/hello_world/MainActivity.java index 920508a04de9..53d36ac6f573 100644 --- a/mobile/examples/java/hello_world/MainActivity.java +++ b/mobile/examples/java/hello_world/MainActivity.java @@ -37,13 +37,8 @@ public class MainActivity extends Activity { private static final String REQUEST_PATH = "/ping"; private static final String REQUEST_SCHEME_HTTP = "http"; private static final String REQUEST_SCHEME_HTTPS = "https"; - private static final Set FILTERED_HEADERS = new HashSet() { - { - add("server"); - add("filter-demo"); - add("x-envoy-upstream-service-time"); - } - }; + private static final Set FILTERED_HEADERS = + Set.of("server", "filter-demo", "x-envoy-upstream-service-time"); private Engine engine; private RecyclerView recyclerView; diff --git a/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml b/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml index 86eaf56db987..b0f9709d5f48 100644 --- a/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml +++ b/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=arm64-v8a //examples/java/hello_world:hello_envoy diff --git a/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml b/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml index e67d4e7976ea..3de57d07b455 100644 --- a/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml +++ b/mobile/examples/java/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=x86 //examples/java/hello_world:hello_envoy diff --git a/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml b/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml index 95206accf6e2..062867dacbdf 100644 --- a/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml +++ b/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=arm64-v8a //examples/kotlin/hello_world:hello_envoy_kt diff --git a/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml b/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml index 7e8657807886..240b0c5bd3eb 100644 --- a/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml +++ b/mobile/examples/kotlin/hello_world/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=x86 //examples/kotlin/hello_world:hello_envoy_kt diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 1b7f90531837..a8b409db5a78 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -45,6 +45,12 @@ namespace Platform { XdsBuilder::XdsBuilder(std::string xds_server_address, const int xds_server_port) : xds_server_address_(std::move(xds_server_address)), xds_server_port_(xds_server_port) {} +XdsBuilder& XdsBuilder::setAuthenticationToken(std::string token_header, std::string token) { + authentication_token_header_ = std::move(token_header); + authentication_token_ = std::move(token); + return *this; +} + XdsBuilder& XdsBuilder::setJwtAuthenticationToken(std::string token, const int token_lifetime_in_seconds) { jwt_token_ = std::move(token); @@ -94,7 +100,12 @@ void XdsBuilder::build(envoy::config::bootstrap::v3::Bootstrap* bootstrap) const ->mutable_root_certs() ->set_inline_string(ssl_root_certs_); } - if (!jwt_token_.empty()) { + + if (!authentication_token_header_.empty() && !authentication_token_.empty()) { + auto* auth_token_metadata = grpc_service.add_initial_metadata(); + auth_token_metadata->set_key(authentication_token_header_); + auth_token_metadata->set_value(authentication_token_); + } else if (!jwt_token_.empty()) { auto& jwt = *grpc_service.mutable_google_grpc() ->add_call_credentials() ->mutable_service_account_jwt_access(); @@ -280,6 +291,17 @@ EngineBuilder& EngineBuilder::enableHttp3(bool http3_on) { enable_http3_ = http3_on; return *this; } + +EngineBuilder& EngineBuilder::setHttp3ConnectionOptions(std::string options) { + http3_connection_options_ = std::move(options); + return *this; +} + +EngineBuilder& EngineBuilder::setHttp3ClientConnectionOptions(std::string options) { + http3_client_connection_options_ = std::move(options); + return *this; +} + #endif EngineBuilder& EngineBuilder::setForceAlwaysUsev6(bool value) { @@ -774,7 +796,14 @@ std::unique_ptr EngineBuilder::generate h3_proxy_socket; h3_proxy_socket.mutable_transport_socket()->mutable_typed_config()->PackFrom(h3_inner_socket); h3_proxy_socket.mutable_transport_socket()->set_name("envoy.transport_sockets.quic"); - alpn_options.mutable_auto_config()->mutable_http3_protocol_options(); + alpn_options.mutable_auto_config() + ->mutable_http3_protocol_options() + ->mutable_quic_protocol_options() + ->set_connection_options(http3_connection_options_); + alpn_options.mutable_auto_config() + ->mutable_http3_protocol_options() + ->mutable_quic_protocol_options() + ->set_client_connection_options(http3_client_connection_options_); alpn_options.mutable_auto_config()->mutable_alternate_protocols_cache_options()->set_name( "default_alternate_protocols_cache"); diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index 23582b770857..09e11143b42c 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -49,11 +49,27 @@ class XdsBuilder final { // requests. XdsBuilder(std::string xds_server_address, const int xds_server_port); + // Sets the authentication token in the gRPC headers used to authenticate to the xDS management + // server. + // + // For example, if using API keys to authenticate to Traffic Director on GCP (see + // https://cloud.google.com/docs/authentication/api-keys for details), invoke: + // builder.setAuthenticationToken("x-goog-api-key", api_key_token) + // + // If this method is called, then don't call setJwtAuthenticationToken. + // + // `token_header`: the header name for which the the `token` will be set as a value. + // `token`: the authentication token. + XdsBuilder& setAuthenticationToken(std::string token_header, std::string token); + // Sets JWT as the authentication method to the xDS management server, using the given token. // + // If setAuthenticationToken is called, then invocations of this method will be ignored. + // // `token`: the JWT token used to authenticate the client to the xDS management server. // `token_lifetime_in_seconds`: the lifetime of the JWT token, in seconds. If none // (or 0) is specified, then DefaultJwtTokenLifetimeSeconds is used. + // TODO(abeyad): Deprecate and remove this. XdsBuilder& setJwtAuthenticationToken(std::string token, int token_lifetime_in_seconds = DefaultJwtTokenLifetimeSeconds); @@ -107,6 +123,8 @@ class XdsBuilder final { std::string xds_server_address_; int xds_server_port_; + std::string authentication_token_header_; + std::string authentication_token_; std::string jwt_token_; int jwt_token_lifetime_in_seconds_ = DefaultJwtTokenLifetimeSeconds; std::string ssl_root_certs_; @@ -152,6 +170,8 @@ class EngineBuilder { EngineBuilder& enableSocketTagging(bool socket_tagging_on); #ifdef ENVOY_ENABLE_QUIC EngineBuilder& enableHttp3(bool http3_on); + EngineBuilder& setHttp3ConnectionOptions(std::string options); + EngineBuilder& setHttp3ClientConnectionOptions(std::string options); #endif EngineBuilder& enableInterfaceBinding(bool interface_binding_on); EngineBuilder& enableDrainPostDnsRefresh(bool drain_post_dns_refresh_on); @@ -232,6 +252,8 @@ class EngineBuilder { bool enable_drain_post_dns_refresh_ = false; bool enforce_trust_chain_verification_ = true; bool enable_http3_ = true; + std::string http3_connection_options_ = ""; + std::string http3_client_connection_options_ = ""; bool always_use_v6_ = false; int dns_min_refresh_seconds_ = 60; int max_connections_per_host_ = 7; diff --git a/mobile/library/common/jni/jni_interface.cc b/mobile/library/common/jni/jni_interface.cc index bc5c3316030f..aa56453024d8 100644 --- a/mobile/library/common/jni/jni_interface.cc +++ b/mobile/library/common/jni/jni_interface.cc @@ -1200,6 +1200,7 @@ void configureBuilder(JNIEnv* env, jstring grpc_stats_domain, jlong connect_time jlong dns_min_refresh_seconds, jobjectArray dns_preresolve_hostnames, jboolean enable_dns_cache, jlong dns_cache_save_interval_seconds, jboolean enable_drain_post_dns_refresh, jboolean enable_http3, + jstring http3_connection_options, jstring http3_client_connection_options, jboolean enable_gzip_decompression, jboolean enable_brotli_decompression, jboolean enable_socket_tagging, jboolean enable_interface_binding, jlong h2_connection_keepalive_idle_interval_milliseconds, @@ -1234,6 +1235,8 @@ void configureBuilder(JNIEnv* env, jstring grpc_stats_domain, jlong connect_time builder.enableSocketTagging(enable_socket_tagging == JNI_TRUE); #ifdef ENVOY_ENABLE_QUIC builder.enableHttp3(enable_http3 == JNI_TRUE); + builder.setHttp3ConnectionOptions(getCppString(env, http3_connection_options)); + builder.setHttp3ClientConnectionOptions(getCppString(env, http3_client_connection_options)); #endif builder.enableInterfaceBinding(enable_interface_binding == JNI_TRUE); builder.enableDrainPostDnsRefresh(enable_drain_post_dns_refresh == JNI_TRUE); @@ -1278,36 +1281,44 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr jlong dns_failure_refresh_seconds_max, jlong dns_query_timeout_seconds, jlong dns_min_refresh_seconds, jobjectArray dns_preresolve_hostnames, jboolean enable_dns_cache, jlong dns_cache_save_interval_seconds, jboolean enable_drain_post_dns_refresh, - jboolean enable_http3, jboolean enable_gzip_decompression, jboolean enable_brotli_decompression, - jboolean enable_socket_tagging, jboolean enable_interface_binding, - jlong h2_connection_keepalive_idle_interval_milliseconds, + jboolean enable_http3, jstring http3_connection_options, + jstring http3_client_connection_options, jboolean enable_gzip_decompression, + jboolean enable_brotli_decompression, jboolean enable_socket_tagging, + jboolean enable_interface_binding, jlong h2_connection_keepalive_idle_interval_milliseconds, jlong h2_connection_keepalive_timeout_seconds, jlong max_connections_per_host, jlong stats_flush_seconds, jlong stream_idle_timeout_seconds, jlong per_try_idle_timeout_seconds, jstring app_version, jstring app_id, jboolean trust_chain_verification, jobjectArray filter_chain, jobjectArray stat_sinks, jboolean enable_platform_certificates_validation, jobjectArray runtime_guards, jstring rtds_resource_name, jlong rtds_timeout_seconds, jstring xds_address, jlong xds_port, - jstring xds_jwt_token, jlong xds_jwt_token_lifetime, jstring xds_root_certs, jstring xds_sni, - jstring node_id, jstring node_region, jstring node_zone, jstring node_sub_zone, - jstring cds_resources_locator, jlong cds_timeout_seconds, jboolean enable_cds) { + jstring xds_auth_header, jstring xds_auth_token, jstring xds_jwt_token, + jlong xds_jwt_token_lifetime, jstring xds_root_certs, jstring xds_sni, jstring node_id, + jstring node_region, jstring node_zone, jstring node_sub_zone, jstring cds_resources_locator, + jlong cds_timeout_seconds, jboolean enable_cds) { Envoy::Platform::EngineBuilder builder; - configureBuilder( - env, grpc_stats_domain, connect_timeout_seconds, dns_refresh_seconds, - dns_failure_refresh_seconds_base, dns_failure_refresh_seconds_max, dns_query_timeout_seconds, - dns_min_refresh_seconds, dns_preresolve_hostnames, enable_dns_cache, - dns_cache_save_interval_seconds, enable_drain_post_dns_refresh, enable_http3, - enable_gzip_decompression, enable_brotli_decompression, enable_socket_tagging, - enable_interface_binding, h2_connection_keepalive_idle_interval_milliseconds, - h2_connection_keepalive_timeout_seconds, max_connections_per_host, stats_flush_seconds, - stream_idle_timeout_seconds, per_try_idle_timeout_seconds, app_version, app_id, - trust_chain_verification, filter_chain, stat_sinks, enable_platform_certificates_validation, - runtime_guards, node_id, node_region, node_zone, node_sub_zone, builder); + configureBuilder(env, grpc_stats_domain, connect_timeout_seconds, dns_refresh_seconds, + dns_failure_refresh_seconds_base, dns_failure_refresh_seconds_max, + dns_query_timeout_seconds, dns_min_refresh_seconds, dns_preresolve_hostnames, + enable_dns_cache, dns_cache_save_interval_seconds, enable_drain_post_dns_refresh, + enable_http3, http3_connection_options, http3_client_connection_options, + enable_gzip_decompression, enable_brotli_decompression, enable_socket_tagging, + enable_interface_binding, h2_connection_keepalive_idle_interval_milliseconds, + h2_connection_keepalive_timeout_seconds, max_connections_per_host, + stats_flush_seconds, stream_idle_timeout_seconds, per_try_idle_timeout_seconds, + app_version, app_id, trust_chain_verification, filter_chain, stat_sinks, + enable_platform_certificates_validation, runtime_guards, node_id, node_region, + node_zone, node_sub_zone, builder); #ifdef ENVOY_GOOGLE_GRPC std::string native_xds_address = getCppString(env, xds_address); if (!native_xds_address.empty()) { Envoy::Platform::XdsBuilder xds_builder(std::move(native_xds_address), xds_port); + std::string native_xds_auth_header = getCppString(env, xds_auth_header); + if (!native_xds_auth_header.empty()) { + xds_builder.setAuthenticationToken(std::move(native_xds_auth_header), + getCppString(env, xds_auth_token)); + } std::string native_jwt_token = getCppString(env, xds_jwt_token); if (!native_jwt_token.empty()) { xds_builder.setJwtAuthenticationToken(std::move(native_jwt_token), xds_jwt_token_lifetime); diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index 689238af7ee7..746e985c5d7e 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -40,6 +40,8 @@ public enum TrustChainVerification { public final Integer dnsCacheSaveIntervalSeconds; public final Boolean enableDrainPostDnsRefresh; public final Boolean enableHttp3; + public final String http3ConnectionOptions; + public final String http3ClientConnectionOptions; public final Boolean enableGzipDecompression; public final Boolean enableBrotliDecompression; public final Boolean enableSocketTagging; @@ -64,6 +66,8 @@ public enum TrustChainVerification { public final Integer rtdsTimeoutSeconds; public final String xdsAddress; public final Integer xdsPort; + public final String xdsAuthHeader; + public final String xdsAuthToken; public final String xdsJwtToken; public final Integer xdsJwtTokenLifetime; public final String xdsRootCerts; @@ -103,6 +107,9 @@ public enum TrustChainVerification { * DNS refresh. * @param enableHttp3 whether to enable experimental support for * HTTP/3 (QUIC). + * @param http3ConnectionOptions connection options to be used in HTTP/3. + * @param http3ClientConnectionOptions client connection options to be used in + * HTTP/3. * @param enableGzipDecompression whether to enable response gzip * decompression. * compression. @@ -134,6 +141,11 @@ public enum TrustChainVerification { * @param rtdsTimeoutSeconds the timeout for RTDS fetches. * @param xdsAddress the address for the xDS management server. * @param xdsPort the port for the xDS server. + * @param xdsAuthHeader the HTTP header to use for sending the + * authentication token to the xDS server. + * @param xdsAuthToken the token to send as the authentication + * header value to authenticate with the + * xDS server. * @param xdsJwtToken the JWT token to use for authenticating * with the xDS server. * @param xdsTokenLifetime the lifetime of the JWT token. @@ -156,6 +168,7 @@ public EnvoyConfiguration( int dnsFailureRefreshSecondsBase, int dnsFailureRefreshSecondsMax, int dnsQueryTimeoutSeconds, int dnsMinRefreshSeconds, List dnsPreresolveHostnames, boolean enableDNSCache, int dnsCacheSaveIntervalSeconds, boolean enableDrainPostDnsRefresh, boolean enableHttp3, + String http3ConnectionOptions, String http3ClientConnectionOptions, boolean enableGzipDecompression, boolean enableBrotliDecompression, boolean enableSocketTagging, boolean enableInterfaceBinding, int h2ConnectionKeepaliveIdleIntervalMilliseconds, int h2ConnectionKeepaliveTimeoutSeconds, @@ -168,9 +181,10 @@ public EnvoyConfiguration( Map keyValueStores, List statSinks, Map runtimeGuards, boolean enablePlatformCertificatesValidation, String rtdsResourceName, Integer rtdsTimeoutSeconds, String xdsAddress, Integer xdsPort, - String xdsJwtToken, Integer xdsJwtTokenLifetime, String xdsRootCerts, String xdsSni, - String nodeId, String nodeRegion, String nodeZone, String nodeSubZone, - String cdsResourcesLocator, Integer cdsTimeoutSeconds, boolean enableCds) { + String xdsAuthHeader, String xdsAuthToken, String xdsJwtToken, Integer xdsJwtTokenLifetime, + String xdsRootCerts, String xdsSni, String nodeId, String nodeRegion, String nodeZone, + String nodeSubZone, String cdsResourcesLocator, Integer cdsTimeoutSeconds, + boolean enableCds) { JniLibrary.load(); this.grpcStatsDomain = grpcStatsDomain; this.connectTimeoutSeconds = connectTimeoutSeconds; @@ -184,6 +198,8 @@ public EnvoyConfiguration( this.dnsCacheSaveIntervalSeconds = dnsCacheSaveIntervalSeconds; this.enableDrainPostDnsRefresh = enableDrainPostDnsRefresh; this.enableHttp3 = enableHttp3; + this.http3ConnectionOptions = http3ConnectionOptions; + this.http3ClientConnectionOptions = http3ClientConnectionOptions; this.enableGzipDecompression = enableGzipDecompression; this.enableBrotliDecompression = enableBrotliDecompression; this.enableSocketTagging = enableSocketTagging; @@ -224,6 +240,8 @@ public EnvoyConfiguration( this.rtdsTimeoutSeconds = rtdsTimeoutSeconds; this.xdsAddress = xdsAddress; this.xdsPort = xdsPort; + this.xdsAuthHeader = xdsAuthHeader; + this.xdsAuthToken = xdsAuthToken; this.xdsJwtToken = xdsJwtToken; this.xdsJwtTokenLifetime = xdsJwtTokenLifetime; this.xdsRootCerts = xdsRootCerts; @@ -252,14 +270,15 @@ public long createBootstrap() { grpcStatsDomain, connectTimeoutSeconds, dnsRefreshSeconds, dnsFailureRefreshSecondsBase, dnsFailureRefreshSecondsMax, dnsQueryTimeoutSeconds, dnsMinRefreshSeconds, dns_preresolve, enableDNSCache, dnsCacheSaveIntervalSeconds, enableDrainPostDnsRefresh, enableHttp3, - enableGzipDecompression, enableBrotliDecompression, enableSocketTagging, - enableInterfaceBinding, h2ConnectionKeepaliveIdleIntervalMilliseconds, - h2ConnectionKeepaliveTimeoutSeconds, maxConnectionsPerHost, statsFlushSeconds, - streamIdleTimeoutSeconds, perTryIdleTimeoutSeconds, appVersion, appId, - enforceTrustChainVerification, filter_chain, stats_sinks, - enablePlatformCertificatesValidation, runtime_guards, rtdsResourceName, rtdsTimeoutSeconds, - xdsAddress, xdsPort, xdsJwtToken, xdsJwtTokenLifetime, xdsRootCerts, xdsSni, nodeId, - nodeRegion, nodeZone, nodeSubZone, cdsResourcesLocator, cdsTimeoutSeconds, enableCds); + http3ConnectionOptions, http3ClientConnectionOptions, enableGzipDecompression, + enableBrotliDecompression, enableSocketTagging, enableInterfaceBinding, + h2ConnectionKeepaliveIdleIntervalMilliseconds, h2ConnectionKeepaliveTimeoutSeconds, + maxConnectionsPerHost, statsFlushSeconds, streamIdleTimeoutSeconds, + perTryIdleTimeoutSeconds, appVersion, appId, enforceTrustChainVerification, filter_chain, + stats_sinks, enablePlatformCertificatesValidation, runtime_guards, rtdsResourceName, + rtdsTimeoutSeconds, xdsAddress, xdsPort, xdsAuthHeader, xdsAuthToken, xdsJwtToken, + xdsJwtTokenLifetime, xdsRootCerts, xdsSni, nodeId, nodeRegion, nodeZone, nodeSubZone, + cdsResourcesLocator, cdsTimeoutSeconds, enableCds); } static class ConfigurationException extends RuntimeException { diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index b0eb2cc2c104..6eb87fcf472f 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -303,15 +303,16 @@ public static native long createBootstrap( long dnsFailureRefreshSecondsBase, long dnsFailureRefreshSecondsMax, long dnsQueryTimeoutSeconds, long dnsMinRefreshSeconds, byte[][] dnsPreresolveHostnames, boolean enableDNSCache, long dnsCacheSaveIntervalSeconds, boolean enableDrainPostDnsRefresh, - boolean enableHttp3, boolean enableGzipDecompression, boolean enableBrotliDecompression, + boolean enableHttp3, String http3ConnectionOptions, String http3ClientConnectionOptions, + boolean enableGzipDecompression, boolean enableBrotliDecompression, boolean enableSocketTagging, boolean enableInterfaceBinding, long h2ConnectionKeepaliveIdleIntervalMilliseconds, long h2ConnectionKeepaliveTimeoutSeconds, long maxConnectionsPerHost, long statsFlushSeconds, long streamIdleTimeoutSeconds, long perTryIdleTimeoutSeconds, String appVersion, String appId, boolean trustChainVerification, byte[][] filterChain, byte[][] statSinks, boolean enablePlatformCertificatesValidation, byte[][] runtimeGuards, String rtdsResourceName, - long rtdsTimeoutSeconds, String xdsAddress, long xdsPort, String xdsJwtToken, - long xdsJwtTokenLifetime, String xdsRootCerts, String xdsJni, String nodeId, - String nodeRegion, String nodeZone, String nodeSubZone, String cdsResourcesLocator, - long cdsTimeoutSeconds, boolean enableCds); + long rtdsTimeoutSeconds, String xdsAddress, long xdsPort, String xdsAuthenticationHeader, + String xdsAuthenticationToken, String xdsJwtToken, long xdsJwtTokenLifetime, + String xdsRootCerts, String xdsSni, String nodeId, String nodeRegion, String nodeZone, + String nodeSubZone, String cdsResourcesLocator, long cdsTimeoutSeconds, boolean enableCds); } diff --git a/mobile/library/java/org/chromium/net/impl/CronvoyEngineBuilderImpl.java b/mobile/library/java/org/chromium/net/impl/CronvoyEngineBuilderImpl.java index 41aa4ddcf644..2a2c8c9a0b8d 100644 --- a/mobile/library/java/org/chromium/net/impl/CronvoyEngineBuilderImpl.java +++ b/mobile/library/java/org/chromium/net/impl/CronvoyEngineBuilderImpl.java @@ -71,6 +71,8 @@ final static class Pkp { private String mUserAgent; private String mStoragePath; private boolean mQuicEnabled; + private String mQuicConnectionOptions = ""; + private String mQuicClientConnectionOptions = ""; private boolean mHttp2Enabled; private boolean mBrotiEnabled; private boolean mDisableCache; @@ -142,6 +144,20 @@ public CronvoyEngineBuilderImpl enableQuic(boolean value) { boolean quicEnabled() { return mQuicEnabled; } + public CronvoyEngineBuilderImpl setQuicConnectionOptions(String options) { + mQuicConnectionOptions = options; + return this; + } + + String quicConnectionOptions() { return mQuicConnectionOptions; } + + public CronvoyEngineBuilderImpl setQuicClientConnectionOptions(String options) { + mQuicClientConnectionOptions = options; + return this; + } + + String quicClientConnectionOptions() { return mQuicClientConnectionOptions; } + /** * Constructs default QUIC User Agent Id string including application name and Cronet version. * Returns empty string if QUIC is not enabled. diff --git a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java index 2593c0812fb1..8efc6efc05c4 100644 --- a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java +++ b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java @@ -125,14 +125,16 @@ private EnvoyConfiguration createEnvoyConfiguration() { mGrpcStatsDomain, mConnectTimeoutSeconds, mDnsRefreshSeconds, mDnsFailureRefreshSecondsBase, mDnsFailureRefreshSecondsMax, mDnsQueryTimeoutSeconds, mDnsMinRefreshSeconds, mDnsPreresolveHostnames, mEnableDNSCache, mDnsCacheSaveIntervalSeconds, - mEnableDrainPostDnsRefresh, quicEnabled(), mEnableGzipDecompression, brotliEnabled(), - mEnableSocketTag, mEnableInterfaceBinding, mH2ConnectionKeepaliveIdleIntervalMilliseconds, + mEnableDrainPostDnsRefresh, quicEnabled(), quicConnectionOptions(), + quicClientConnectionOptions(), mEnableGzipDecompression, brotliEnabled(), mEnableSocketTag, + mEnableInterfaceBinding, mH2ConnectionKeepaliveIdleIntervalMilliseconds, mH2ConnectionKeepaliveTimeoutSeconds, mMaxConnectionsPerHost, mStatsFlushSeconds, mStreamIdleTimeoutSeconds, mPerTryIdleTimeoutSeconds, mAppVersion, mAppId, mTrustChainVerification, nativeFilterChain, platformFilterChain, stringAccessors, keyValueStores, statSinks, runtimeGuards, mEnablePlatformCertificatesValidation, /*rtdsResourceName=*/"", /*rtdsTimeoutSeconds=*/0, /*xdsAddress=*/"", - /*xdsPort=*/0, /*xdsJwtToken=*/"", /*xdsJwtTokenLifetime=*/0, /*xdsSslRootCerts=*/"", + /*xdsPort=*/0, /*xdsAuthenticationHeader=*/"", /*xdsAuthenticationToken=*/"", + /*xdsJwtToken=*/"", /*xdsJwtTokenLifetime=*/0, /*xdsSslRootCerts=*/"", /*xdsSni=*/"", mNodeId, mNodeRegion, mNodeZone, mNodeSubZone, /*cdsResourcesLocator=*/"", /*cdsTimeoutSeconds=*/0, /*enableCds=*/false); } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt index b5ced04a1646..49dfb197308e 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -43,6 +43,8 @@ open class XdsBuilder ( private const val DEFAULT_XDS_TIMEOUT_IN_SECONDS: Int = 5 } + internal var authHeader: String? = null + internal var authToken: String? = null internal var jwtToken: String? = null internal var jwtTokenLifetimeInSeconds: Int = DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS internal var sslRootCerts: String? = null @@ -53,6 +55,24 @@ open class XdsBuilder ( internal var cdsResourcesLocator: String? = null internal var cdsTimeoutInSeconds: Int = DEFAULT_XDS_TIMEOUT_IN_SECONDS + /** + * Sets the authentication HTTP header and token value for authenticating with the xDS + * management server. + * + * @param header The HTTP authentication header. + * @param token The authentication token to be sent in the header. + * + * @return this builder. + */ + fun setAuthenticationToken( + header: String, + token: String + ): XdsBuilder { + this.authHeader = header + this.authToken = token + return this + } + /** * Sets JWT as the authentication method to the xDS management server, using the given token. * @@ -180,6 +200,8 @@ open class EngineBuilder( private var dnsCacheSaveIntervalSeconds = 1 private var enableDrainPostDnsRefresh = false internal var enableHttp3 = true + private var http3ConnectionOptions = "" + private var http3ClientConnectionOptions = "" private var enableGzipDecompression = true private var enableBrotliDecompression = false private var enableSocketTagging = false @@ -707,6 +729,8 @@ open class EngineBuilder( dnsCacheSaveIntervalSeconds, enableDrainPostDnsRefresh, enableHttp3, + http3ConnectionOptions, + http3ClientConnectionOptions, enableGzipDecompression, enableBrotliDecompression, enableSocketTagging, @@ -731,6 +755,8 @@ open class EngineBuilder( xdsBuilder?.rtdsTimeoutInSeconds ?: 0, xdsBuilder?.xdsServerAddress, xdsBuilder?.xdsServerPort ?: 0, + xdsBuilder?.authHeader, + xdsBuilder?.authToken, xdsBuilder?.jwtToken, xdsBuilder?.jwtTokenLifetimeInSeconds ?: 0, xdsBuilder?.sslRootCerts, diff --git a/mobile/library/objective-c/EnvoyConfiguration.h b/mobile/library/objective-c/EnvoyConfiguration.h index 4ecb82f0f76b..cd40c177aad3 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.h +++ b/mobile/library/objective-c/EnvoyConfiguration.h @@ -49,6 +49,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, nullable) NSString *nodeSubZone; @property (nonatomic, strong, nullable) NSString *xdsServerAddress; @property (nonatomic, assign) UInt32 xdsServerPort; +@property (nonatomic, strong, nullable) NSString *xdsAuthHeader; +@property (nonatomic, strong, nullable) NSString *xdsAuthToken; @property (nonatomic, strong, nullable) NSString *xdsJwtToken; @property (nonatomic, assign) UInt32 xdsJwtTokenLifetimeSeconds; @property (nonatomic, strong, nullable) NSString *xdsSslRootCerts; @@ -109,6 +111,8 @@ NS_ASSUME_NONNULL_BEGIN nodeSubZone:(nullable NSString *)nodeSubZone xdsServerAddress:(nullable NSString *)xdsServerAddress xdsServerPort:(UInt32)xdsServerPort + xdsAuthHeader:(nullable NSString *)xdsAuthHeader + xdsAuthToken:(nullable NSString *)xdsAuthToken xdsJwtToken:(nullable NSString *)xdsJwtToken xdsJwtTokenLifetimeSeconds:(UInt32)xdsJwtTokenLifetimeSeconds xdsSslRootCerts:(nullable NSString *)xdsSslRootCerts diff --git a/mobile/library/objective-c/EnvoyConfiguration.mm b/mobile/library/objective-c/EnvoyConfiguration.mm index b1a70b867609..60aca2302f0f 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.mm +++ b/mobile/library/objective-c/EnvoyConfiguration.mm @@ -113,6 +113,8 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain nodeSubZone:(nullable NSString *)nodeSubZone xdsServerAddress:(nullable NSString *)xdsServerAddress xdsServerPort:(UInt32)xdsServerPort + xdsAuthHeader:(nullable NSString *)xdsAuthHeader + xdsAuthToken:(nullable NSString *)xdsAuthToken xdsJwtToken:(nullable NSString *)xdsJwtToken xdsJwtTokenLifetimeSeconds:(UInt32)xdsJwtTokenLifetimeSeconds xdsSslRootCerts:(nullable NSString *)xdsSslRootCerts @@ -166,6 +168,8 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain self.nodeSubZone = nodeSubZone; self.xdsServerAddress = xdsServerAddress; self.xdsServerPort = xdsServerPort; + self.xdsAuthHeader = xdsAuthHeader; + self.xdsAuthToken = xdsAuthToken; self.xdsJwtToken = xdsJwtToken; self.xdsJwtTokenLifetimeSeconds = xdsJwtTokenLifetimeSeconds; self.xdsSslRootCerts = xdsSslRootCerts; @@ -262,6 +266,10 @@ - (instancetype)initWithGrpcStatsDomain:(nullable NSString *)grpcStatsDomain #ifdef ENVOY_GOOGLE_GRPC if (self.xdsServerAddress != nil) { Envoy::Platform::XdsBuilder xdsBuilder([self.xdsServerAddress toCXXString], self.xdsServerPort); + if (self.xdsAuthHeader != nil) { + xdsBuilder.setAuthenticationToken([self.xdsAuthHeader toCXXString], + [self.xdsAuthToken toCXXString]); + } if (self.xdsJwtToken != nil) { xdsBuilder.setJwtAuthenticationToken([self.xdsJwtToken toCXXString], self.xdsJwtTokenLifetimeSeconds); diff --git a/mobile/library/swift/EngineBuilder.swift b/mobile/library/swift/EngineBuilder.swift index 40331f8752ad..aa7631191577 100644 --- a/mobile/library/swift/EngineBuilder.swift +++ b/mobile/library/swift/EngineBuilder.swift @@ -19,6 +19,8 @@ open class XdsBuilder: NSObject { let xdsServerAddress: String let xdsServerPort: UInt32 + var authHeader: String? + var authToken: String? var jwtToken: String? var jwtTokenLifetimeInSeconds: UInt32 = XdsBuilder.defaultJwtTokenLifetimeInSeconds var sslRootCerts: String? @@ -38,6 +40,22 @@ open class XdsBuilder: NSObject { self.xdsServerPort = xdsServerPort } + /// Sets the authentication HTTP header and token value for authentication with the xDS + /// management server. + /// + /// - parameter header: The HTTP authentication header. + /// - parameter token: The authentication token to be sent in the header. + /// + /// - returns: This builder. + @discardableResult + public func setAuthenticationToken( + header: String, + token: String) -> Self { + self.authHeader = header + self.authToken = token + return self + } + /// Sets JWT as the authentication method to the xDS management server, using the given token. /// /// - parameter token: The JWT token used to authenticate the client to the xDS @@ -754,6 +772,8 @@ open class EngineBuilder: NSObject { func makeConfig() -> EnvoyConfiguration { var xdsServerAddress: String? var xdsServerPort: UInt32 = 0 + var xdsAuthHeader: String? + var xdsAuthToken: String? var xdsJwtToken: String? var xdsJwtTokenLifetimeSeconds: UInt32 = 0 var xdsSslRootCerts: String? @@ -767,6 +787,8 @@ open class EngineBuilder: NSObject { #if ENVOY_GOOGLE_GRPC xdsServerAddress = self.xdsBuilder?.xdsServerAddress xdsServerPort = self.xdsBuilder?.xdsServerPort ?? 0 + xdsAuthHeader = self.xdsBuilder?.authHeader + xdsAuthToken = self.xdsBuilder?.authToken xdsJwtToken = self.xdsBuilder?.jwtToken xdsJwtTokenLifetimeSeconds = self.xdsBuilder?.jwtTokenLifetimeInSeconds ?? 0 xdsSslRootCerts = self.xdsBuilder?.sslRootCerts @@ -818,6 +840,8 @@ open class EngineBuilder: NSObject { nodeSubZone: self.nodeSubZone, xdsServerAddress: xdsServerAddress, xdsServerPort: xdsServerPort, + xdsAuthHeader: xdsAuthHeader, + xdsAuthToken: xdsAuthToken, xdsJwtToken: xdsJwtToken, xdsJwtTokenLifetimeSeconds: xdsJwtTokenLifetimeSeconds, xdsSslRootCerts: xdsSslRootCerts, @@ -919,6 +943,10 @@ private extension EngineBuilder { if let xdsBuilder = self.xdsBuilder { var cxxXdsBuilder = Envoy.Platform.XdsBuilder(xdsBuilder.xdsServerAddress.toCXX(), Int32(xdsBuilder.xdsServerPort)) + if let xdsAuthHeader = xdsBuilder.authHeader { + cxxXdsBuilder.setAuthenticationToken(xdsAuthHeader.toCXX(), + xdsBuilder.authToken?.toCXX() ?? "".toCXX()) + } if let xdsJwtToken = xdsBuilder.jwtToken { cxxXdsBuilder.setJwtAuthenticationToken(xdsJwtToken.toCXX(), Int32(xdsBuilder.jwtTokenLifetimeInSeconds)) diff --git a/mobile/test/cc/unit/BUILD b/mobile/test/cc/unit/BUILD index d0079181bde5..c7dfb3d012a2 100644 --- a/mobile/test/cc/unit/BUILD +++ b/mobile/test/cc/unit/BUILD @@ -25,3 +25,14 @@ envoy_cc_test( "@envoy_build_config//:extension_registry", ], ) + +envoy_cc_test( + name = "fetch_client_test", + srcs = ["fetch_client_test.cc"], + repository = "@envoy", + deps = [ + "//examples/cc/fetch_client:fetch_client_lib", + "@envoy_build_config//:extension_registry", + "@envoy_build_config//:test_extensions", + ], +) diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index 7b6d21fe4520..d4ef7a438752 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -32,6 +32,10 @@ TEST(TestConfig, ConfigIsApplied) { #ifdef ENVOY_MOBILE_STATS_REPORTING .addGrpcStatsDomain("asdf.fake.website") .addStatsFlushSeconds(654) +#endif +#ifdef ENVOY_ENABLE_QUIC + .setHttp3ConnectionOptions("5RTO") + .setHttp3ClientConnectionOptions("MPQC") #endif .addConnectTimeoutSeconds(123) .addDnsRefreshSeconds(456) @@ -62,6 +66,10 @@ TEST(TestConfig, ConfigIsApplied) { #ifdef ENVOY_MOBILE_STATS_REPORTING "asdf.fake.website", "stats_flush_interval { seconds: 654 }", +#endif +#ifdef ENVOY_ENABLE_QUIC + "connection_options: \"5RTO\"", + "client_connection_options: \"MPQC\"", #endif "key: \"dns_persistent_cache\" save_interval { seconds: 101 }", "key: \"always_use_v6\" value { bool_value: true }", @@ -281,11 +289,10 @@ TEST(TestConfig, XdsConfig) { IsEmpty()); EXPECT_THAT(ads_config.grpc_services(0).google_grpc().call_credentials(), SizeIs(0)); - // With security credentials. + // With authentication credentials. xds_builder = XdsBuilder(/*xds_server_address=*/"fake-td.googleapis.com", /*xds_server_port=*/12345); - xds_builder.setJwtAuthenticationToken(/*token=*/"my_jwt_token", - /*token_lifetime_in_seconds=*/500); + xds_builder.setAuthenticationToken(/*header=*/"x-goog-api-key", /*token=*/"A1B2C3"); xds_builder.setSslRootCerts(/*root_certs=*/"my_root_cert"); xds_builder.setSni(/*sni=*/"fake-td.googleapis.com"); engine_builder.setXds(std::move(xds_builder)); @@ -302,19 +309,50 @@ TEST(TestConfig, XdsConfig) { .root_certs() .inline_string(), "my_root_cert"); + EXPECT_EQ(ads_config_with_tokens.grpc_services(0).initial_metadata(0).key(), "x-goog-api-key"); + EXPECT_EQ(ads_config_with_tokens.grpc_services(0).initial_metadata(0).value(), "A1B2C3"); EXPECT_EQ(ads_config_with_tokens.grpc_services(0) + .google_grpc() + .channel_args() + .args() + .at("grpc.default_authority") + .string_value(), + "fake-td.googleapis.com"); + + // With JWT security credentials. + xds_builder = + XdsBuilder(/*xds_server_address=*/"fake-td.googleapis.com", /*xds_server_port=*/12345); + xds_builder.setJwtAuthenticationToken(/*token=*/"my_jwt_token", + /*token_lifetime_in_seconds=*/500); + xds_builder.setSslRootCerts(/*root_certs=*/"my_root_cert"); + xds_builder.setSni(/*sni=*/"fake-td.googleapis.com"); + engine_builder.setXds(std::move(xds_builder)); + bootstrap = engine_builder.generateBootstrap(); + auto& ads_config_with_jwt_tokens = bootstrap->dynamic_resources().ads_config(); + EXPECT_EQ(ads_config_with_jwt_tokens.api_type(), envoy::config::core::v3::ApiConfigSource::GRPC); + EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0).google_grpc().target_uri(), + "fake-td.googleapis.com:12345"); + EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0).google_grpc().stat_prefix(), "ads"); + EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0) + .google_grpc() + .channel_credentials() + .ssl_credentials() + .root_certs() + .inline_string(), + "my_root_cert"); + EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0) .google_grpc() .call_credentials(0) .service_account_jwt_access() .json_key(), "my_jwt_token"); - EXPECT_EQ(ads_config_with_tokens.grpc_services(0) + EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0) .google_grpc() .call_credentials(0) .service_account_jwt_access() .token_lifetime_seconds(), 500); - EXPECT_EQ(ads_config_with_tokens.grpc_services(0) + EXPECT_EQ(ads_config_with_jwt_tokens.grpc_services(0) .google_grpc() .channel_args() .args() diff --git a/mobile/test/cc/unit/fetch_client_test.cc b/mobile/test/cc/unit/fetch_client_test.cc new file mode 100644 index 000000000000..396e6a58787c --- /dev/null +++ b/mobile/test/cc/unit/fetch_client_test.cc @@ -0,0 +1,21 @@ +#include +#include + +#include "examples/cc/fetch_client/fetch_client.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Platform { +namespace { + +// This test verifies that the fetch client is able to successfully +// build and start the Envoy engine. It will panic if it is unable +// to do so. +TEST(FetchClientTest, Foo) { + Envoy::Fetch client; + client.fetch({"https://www.google.com/"}); +} + +} // namespace +} // namespace Platform +} // namespace Envoy diff --git a/mobile/test/common/integration/rtds_integration_test.cc b/mobile/test/common/integration/rtds_integration_test.cc index 19b356cba7a6..91a3f2c89cfa 100644 --- a/mobile/test/common/integration/rtds_integration_test.cc +++ b/mobile/test/common/integration/rtds_integration_test.cc @@ -73,6 +73,24 @@ TEST_P(RtdsIntegrationTest, RtdsReload) { // Verify that the Runtime config values are from the RTDS response. EXPECT_TRUE(Runtime::runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); + + load_success_value = getCounterValue(load_success_counter); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Runtime, "", {"some_rtds_resource"}, + {"some_rtds_resource"}, {})); + some_rtds_resource = TestUtility::parseYaml(R"EOF( + name: some_rtds_resource + layer: + envoy.reloadable_features.test_feature_false: False + )EOF"); + // Send another response with Resource wrapper. + sendDiscoveryResponse( + Config::TypeUrl::get().Runtime, {some_rtds_resource}, {some_rtds_resource}, {}, "2", + {{"test", ProtobufWkt::Any()}}); + // Wait until the RTDS updates from the DiscoveryResponse have been applied. + ASSERT_TRUE(waitForCounterGe(load_success_counter, load_success_value + 1)); + + // Verify that the Runtime config values are from the RTDS response. + EXPECT_FALSE(Runtime::runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); } } // namespace diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt index cc6e23031af0..9d2b37ac1475 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt @@ -80,6 +80,8 @@ class EnvoyConfigurationTest { dnsCacheSaveIntervalSeconds: Int = 101, enableDrainPostDnsRefresh: Boolean = false, enableHttp3: Boolean = true, + http3ConnectionOptions: String = "5RTO", + http3ClientConnectionOptions: String = "MPQC", enableGzipDecompression: Boolean = true, enableBrotliDecompression: Boolean = false, enableSocketTagging: Boolean = false, @@ -102,6 +104,8 @@ class EnvoyConfigurationTest { rtdsTimeoutSeconds: Int = 0, xdsAddress: String = "", xdsPort: Int = 0, + xdsAuthHeader: String = "", + xdsAuthToken: String = "", xdsJwtToken: String = "", xdsJwtTokenLifetimeSeconds: Int = 0, xdsSslRootCerts: String = "", @@ -128,6 +132,8 @@ class EnvoyConfigurationTest { dnsCacheSaveIntervalSeconds, enableDrainPostDnsRefresh, enableHttp3, + http3ConnectionOptions, + http3ClientConnectionOptions, enableGzipDecompression, enableBrotliDecompression, enableSocketTagging, @@ -152,6 +158,8 @@ class EnvoyConfigurationTest { rtdsTimeoutSeconds, xdsAddress, xdsPort, + xdsAuthHeader, + xdsAuthToken, xdsJwtToken, xdsJwtTokenLifetimeSeconds, xdsSslRootCerts, @@ -195,6 +203,8 @@ class EnvoyConfigurationTest { // H3 assertThat(resolvedTemplate).contains("http3_protocol_options:"); assertThat(resolvedTemplate).contains("name: alternate_protocols_cache"); + assertThat(resolvedTemplate).contains("connection_options: 5RTO"); + assertThat(resolvedTemplate).contains("client_connection_options: MPQC"); // Gzip assertThat(resolvedTemplate).contains("type.googleapis.com/envoy.extensions.compression.gzip.decompressor.v3.Gzip"); diff --git a/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml b/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml index 062ef17d8bc8..0fe4d3fa8aeb 100644 --- a/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml +++ b/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=arm64-v8a //test/kotlin/apps/baseline:hello_envoy_kt diff --git a/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml b/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml index 568962403d86..3510ee45dc77 100644 --- a/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml +++ b/mobile/test/kotlin/apps/baseline/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=x86 //test/kotlin/apps/baseline:hello_envoy_kt diff --git a/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml b/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml index a6e5c1d53f0f..f47ede5a3509 100644 --- a/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml +++ b/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_arm64.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=arm64-v8a //test/kotlin/apps/experimental:hello_envoy_kt diff --git a/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml b/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml index 08d2d5e7ee74..277fae926007 100644 --- a/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml +++ b/mobile/test/kotlin/apps/experimental/tools/android-studio-run-configurations/run_configuration_example_debug_x86.xml @@ -14,7 +14,7 @@ use-work-profile-if-present="false" show-logcat-automatically="false" AM_START_OPTIONS=""> - --config=dbg-android + --config=mobile-dbg-android --fat_apk_cpu=x86 //test/kotlin/apps/experimental:hello_envoy_kt diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt index 6d9ba62a2655..1703b08690d7 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt @@ -207,6 +207,7 @@ class EngineBuilderTest { @Test fun `specifying xDS works`() { var xdsBuilder = XdsBuilder("fake_test_address", 0) + xdsBuilder.setAuthenticationToken("x-goog-api-key", "A1B2C3") xdsBuilder.setJwtAuthenticationToken("my_jwt_token") xdsBuilder.setSslRootCerts("my_root_certs") xdsBuilder.setSni("fake_test_address"); @@ -218,6 +219,8 @@ class EngineBuilderTest { val engine = engineBuilder.build() as EngineImpl assertThat(engine.envoyConfiguration.xdsAddress).isEqualTo("fake_test_address") + assertThat(engine.envoyConfiguration.xdsAuthHeader).isEqualTo("x-goog-api-key") + assertThat(engine.envoyConfiguration.xdsAuthToken).isEqualTo("A1B2C3") assertThat(engine.envoyConfiguration.xdsJwtToken).isEqualTo("my_jwt_token") assertThat(engine.envoyConfiguration.xdsRootCerts).isEqualTo("my_root_certs") assertThat(engine.envoyConfiguration.xdsSni).isEqualTo("fake_test_address") diff --git a/mobile/test/non_hermetic/BUILD b/mobile/test/non_hermetic/BUILD index 1d1d8ad9942d..37af76e1edf8 100644 --- a/mobile/test/non_hermetic/BUILD +++ b/mobile/test/non_hermetic/BUILD @@ -33,10 +33,11 @@ envoy_cc_test_binary( "@envoy//source/common/config:api_version_lib", "@envoy//source/common/grpc:google_grpc_creds_lib", "@envoy//source/common/protobuf:utility_lib_header", - "@envoy//source/extensions/clusters/eds:eds_lib", + "@envoy//source/extensions/clusters/strict_dns:strict_dns_cluster_lib", "@envoy//source/extensions/config_subscription/grpc:grpc_mux_lib", "@envoy//source/extensions/config_subscription/grpc:grpc_subscription_lib", "@envoy//source/extensions/config_subscription/grpc:new_grpc_mux_lib", + "@envoy//source/extensions/health_checkers/http:health_checker_lib", "@envoy//test/common/grpc:grpc_client_integration_lib", "@envoy//test/integration:http_integration_lib", "@envoy//test/test_common:environment_lib", diff --git a/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc b/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc index 182320d68a71..e8bd07755228 100644 --- a/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc +++ b/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc @@ -12,10 +12,11 @@ #include "source/common/grpc/google_grpc_creds_impl.h" #include "source/common/protobuf/utility.h" -#include "source/extensions/clusters/eds/eds.h" +#include "source/extensions/clusters/strict_dns/strict_dns_cluster.h" #include "source/extensions/config_subscription/grpc/grpc_mux_impl.h" #include "source/extensions/config_subscription/grpc/grpc_subscription_factory.h" #include "source/extensions/config_subscription/grpc/new_grpc_mux_impl.h" +#include "source/extensions/health_checkers/http/health_checker_impl.h" #include "test/common/grpc/grpc_client_integration.h" #include "test/common/integration/base_client_integration_test.h" @@ -36,44 +37,10 @@ using ::Envoy::Grpc::SotwOrDelta; using ::Envoy::Network::Address::IpVersion; // The One-Platform API endpoint for Traffic Director. -constexpr char TD_API_ENDPOINT[] = "trafficdirector.googleapis.com"; -// The name of the project in Google Cloud Console; copied from the project_id -// field in the generated JWT token. -constexpr char PROJECT_NAME[] = "td-testing-gfq"; +constexpr char TD_API_ENDPOINT[] = "staging-trafficdirectorconsumermesh.sandbox.googleapis.com"; // The project number of the project, found on the main page of the project in // Google Cloud Console. -constexpr char PROJECT_ID[] = "798832730858"; -// Copied from the "client_id" field in the generated JWT token. -constexpr char CLIENT_ID[] = "102524055118681734203"; -// Copied from the "private_key_id" field in the generated JWT token. -constexpr char PRIVATE_KEY_ID[] = "e07f02d49044a533cf4342d138eacecc6acdb6ed"; - -// Using a JWT token to authenticate to Traffic Director. -std::string jwtToken() { - const std::string email = - absl::Substitute("$0-compute@developer.gserviceaccount.com", PROJECT_ID); - const std::string cert_url = absl::Substitute("https://www.googleapis.com/robot/v1/metadata/x509/" - "$0-compute%40developer.gserviceaccount.com", - PROJECT_ID); - - const char* private_key = std::getenv("GCP_JWT_PRIVATE_KEY"); - RELEASE_ASSERT(private_key != nullptr, "GCP_JWT_PRIVATE_KEY environment variable not set."); - - return absl::Substitute( - R"json({ - "private_key": "$0", - "private_key_id": "$1", - "project_id": "$2", - "client_email": "$3", - "client_id": "$4", - "client_x509_cert_url": "$5", - "type": "service_account", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs" - })json", - private_key, PRIVATE_KEY_ID, PROJECT_NAME, email, CLIENT_ID, cert_url); -} +constexpr char PROJECT_ID[] = "947171374466"; // Tests that Envoy Mobile can connect to Traffic Director (an xDS management server offered by GCP) // via a test GCP project, and can pull down xDS config for the given project. @@ -92,15 +59,20 @@ class GcpTrafficDirectorIntegrationTest Config::forceRegisterNewGrpcMuxFactory(); Config::forceRegisterAdsConfigSubscriptionFactory(); // Force register the cluster factories used by the test. - Upstream::forceRegisterEdsClusterFactory(); + Upstream::forceRegisterStrictDnsClusterFactory(); + Upstream::forceRegisterHttpHealthCheckerFactory(); std::string root_certs(TestEnvironment::readFileToStringForTest( TestEnvironment::runfilesPath("test/config/integration/certs/google_root_certs.pem"))); + // API key for the `bct-staging-td-consumer-mesh` GCP test project. + const char* api_key = std::getenv("GCP_TEST_PROJECT_API_KEY"); + RELEASE_ASSERT(api_key != nullptr, "GCP_TEST_PROJECT_API_KEY environment variable not set."); + // TODO(abeyad): switch to using API key authentication instead of a JWT token. Platform::XdsBuilder xds_builder(/*xds_server_address=*/std::string(TD_API_ENDPOINT), /*xds_server_port=*/443); - xds_builder.setJwtAuthenticationToken(jwtToken(), Platform::DefaultJwtTokenLifetimeSeconds); + xds_builder.setAuthenticationToken("x-goog-api-key", std::string(api_key)); xds_builder.setSslRootCerts(std::move(root_certs)); xds_builder.addClusterDiscoveryService(); builder_.addLogLevel(Platform::LogLevel::trace) @@ -140,14 +112,12 @@ TEST_P(GcpTrafficDirectorIntegrationTest, AdsDynamicClusters) { // // There are 5 total active clusters after the Envoy engine has finished initialization. // - // (A) There are three dynamic clusters retrieved from Traffic Director: - // 1. cloud-internal-istio:cloud_mp_798832730858_1578897841695688881 - // 2. cloud-internal-istio:cloud_mp_798832730858_523871542841416155 - // 3. cloud-internal-istio:cloud_mp_798832730858_4497773746904456309 - // (B) There are two static clusters added by the EngineBuilder by default: - // 4. base - // 5. base_clear - ASSERT_TRUE(waitForGaugeGe("cluster_manager.active_clusters", 5)); + // 1. There is one strict dns cluster retrieved from Traffic Director: + // backend-svc-do-not-delete + // 2. There are two static clusters added by the EngineBuilder by default: + // base + // base_clear + ASSERT_TRUE(waitForGaugeGe("cluster_manager.active_clusters", 3)); // TODO(abeyad): Once we have a Envoy Mobile stats API, we can use it to check the // actual cluster names. diff --git a/mobile/test/swift/EngineBuilderTests.swift b/mobile/test/swift/EngineBuilderTests.swift index c534183ff20d..c2713374592b 100644 --- a/mobile/test/swift/EngineBuilderTests.swift +++ b/mobile/test/swift/EngineBuilderTests.swift @@ -393,6 +393,22 @@ final class EngineBuilderTests: XCTestCase { } func testAddingXdsSecurityConfigurationWhenRunningEnvoy() { + let xdsBuilder = XdsBuilder(xdsServerAddress: "FAKE_SWIFT_ADDRESS", xdsServerPort: 0) + .setAuthenticationToken(header: "x-goog-api-key", token: "A1B2C3") + .setSslRootCerts(rootCerts: "fake_ssl_root_certs") + .setSni(sni: "fake_sni_address") + .addRuntimeDiscoveryService(resourceName: "some_rtds_resource", timeoutInSeconds: 14325) + let bootstrapDebugDescription = EngineBuilder() + .addEngineType(MockEnvoyEngine.self) + .setXds(xdsBuilder) + .bootstrapDebugDescription() + XCTAssertTrue(bootstrapDebugDescription.contains("x-goog-api-key")) + XCTAssertTrue(bootstrapDebugDescription.contains("A1B2C3")) + XCTAssertTrue(bootstrapDebugDescription.contains("fake_ssl_root_certs")) + XCTAssertTrue(bootstrapDebugDescription.contains("fake_sni_address")) + } + + func testAddingXdsJwtSecurityConfigurationWhenRunningEnvoy() { let xdsBuilder = XdsBuilder(xdsServerAddress: "FAKE_SWIFT_ADDRESS", xdsServerPort: 0) .setJwtAuthenticationToken(token: "fake_jwt_token", tokenLifetimeInSeconds: 12345) .setSslRootCerts(rootCerts: "fake_ssl_root_certs") diff --git a/mobile/tools/what_to_run.sh b/mobile/tools/what_to_run.sh index 2ca3941818b4..939d23173b8e 100755 --- a/mobile/tools/what_to_run.sh +++ b/mobile/tools/what_to_run.sh @@ -5,7 +5,7 @@ set -euo pipefail BRANCH_NAME="$GITHUB_REF_NAME" BASE_COMMIT="$(git merge-base origin/main HEAD)" CHANGED_FILES="$(git diff "${BASE_COMMIT}" --name-only)" -CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml' +CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.bazelversion|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml' # The logic in this file is roughly: # diff --git a/source/common/common/BUILD b/source/common/common/BUILD index 2130d35d2bbf..49bc26b620bc 100644 --- a/source/common/common/BUILD +++ b/source/common/common/BUILD @@ -210,6 +210,7 @@ envoy_cc_library( external_deps = ["abseil_synchronization"], deps = [ ":base_logger_lib", + ":fmt_lib", ":lock_guard_lib", ":macros", ":non_copyable", @@ -225,6 +226,7 @@ envoy_cc_library( srcs = ["base_logger.cc"], hdrs = ["base_logger.h"], external_deps = [ + "abseil_strings", "spdlog", ], ) diff --git a/source/common/common/logger.cc b/source/common/common/logger.cc index 5a1723ed5e3e..18888b059e9d 100644 --- a/source/common/common/logger.cc +++ b/source/common/common/logger.cc @@ -7,6 +7,8 @@ #include #include +#include "envoy/common/exception.h" + #include "source/common/common/json_escape_string.h" #include "source/common/common/lock_guard.h" @@ -258,6 +260,10 @@ void Registry::setLogFormat(const std::string& log_format) { } absl::Status Registry::setJsonLogFormat(const Protobuf::Message& log_format_struct) { +#ifndef ENVOY_ENABLE_YAML + UNREFERENCED_PARAMETER(log_format_struct); + return absl::UnimplementedError("JSON/YAML support compiled out"); +#else Protobuf::util::JsonPrintOptions json_options; json_options.preserve_proto_field_names = true; json_options.always_print_primitive_fields = true; @@ -297,6 +303,7 @@ absl::Status Registry::setJsonLogFormat(const Protobuf::Message& log_format_stru setLogFormat(format_as_json); json_log_format_set_ = true; return absl::OkStatus(); +#endif } bool Registry::json_log_format_set_ = false; diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 36fb554c27ab..dedc5b135393 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -661,8 +661,16 @@ class ExtractedMessage : public spdlog::custom_flag_formatter { } while (0) #define ENVOY_CONN_LOG_EVENT(LEVEL, EVENT_NAME, FORMAT, CONNECTION, ...) \ - ENVOY_LOG_EVENT_TO_LOGGER(ENVOY_LOGGER(), LEVEL, EVENT_NAME, "[C{}] " FORMAT, (CONNECTION).id(), \ - ##__VA_ARGS__); + do { \ + if (ENVOY_LOG_COMP_LEVEL(ENVOY_LOGGER(), LEVEL)) { \ + std::map log_tags; \ + log_tags.emplace("ConnectionId", std::to_string((CONNECTION).id())); \ + const auto combined_format = ::Envoy::Logger::Utility::serializeLogTags(log_tags) + FORMAT; \ + ENVOY_LOG_TO_LOGGER(ENVOY_LOGGER(), LEVEL, fmt::runtime(combined_format), ##__VA_ARGS__); \ + ::Envoy::Logger::Registry::getSink()->logWithStableName( \ + EVENT_NAME, #LEVEL, (ENVOY_LOGGER()).name(), combined_format, ##__VA_ARGS__); \ + } \ + } while (0) #define ENVOY_LOG_FIRST_N_TO_LOGGER(LOGGER, LEVEL, N, ...) \ do { \ @@ -674,20 +682,45 @@ class ExtractedMessage : public spdlog::custom_flag_formatter { } \ } while (0) +#define ENVOY_LOG_FIRST_N_TO_LOGGER_IF(LOGGER, LEVEL, N, CONDITION, ...) \ + do { \ + if (ENVOY_LOG_COMP_LEVEL(LOGGER, LEVEL) && (CONDITION)) { \ + static auto* countdown = new std::atomic(); \ + if (countdown->fetch_add(1) < N) { \ + ENVOY_LOG_TO_LOGGER(LOGGER, LEVEL, ##__VA_ARGS__); \ + } \ + } \ + } while (0) + #define ENVOY_LOG_FIRST_N(LEVEL, N, ...) \ ENVOY_LOG_FIRST_N_TO_LOGGER(ENVOY_LOGGER(), LEVEL, N, ##__VA_ARGS__) +#define ENVOY_LOG_FIRST_N_IF(LEVEL, N, CONDITION, ...) \ + ENVOY_LOG_FIRST_N_TO_LOGGER_IF(ENVOY_LOGGER(), LEVEL, N, (CONDITION), ##__VA_ARGS__) + #define ENVOY_LOG_FIRST_N_MISC(LEVEL, N, ...) \ ENVOY_LOG_FIRST_N_TO_LOGGER(GET_MISC_LOGGER(), LEVEL, N, ##__VA_ARGS__) +#define ENVOY_LOG_FIRST_N_MISC_IF(LEVEL, N, CONDITION, ...) \ + ENVOY_LOG_FIRST_N_TO_LOGGER_IF(GET_MISC_LOGGER(), LEVEL, N, (CONDITION), ##__VA_ARGS__) + #define ENVOY_LOG_ONCE_TO_LOGGER(LOGGER, LEVEL, ...) \ ENVOY_LOG_FIRST_N_TO_LOGGER(LOGGER, LEVEL, 1, ##__VA_ARGS__) +#define ENVOY_LOG_ONCE_TO_LOGGER_IF(LOGGER, LEVEL, CONDITION, ...) \ + ENVOY_LOG_FIRST_N_TO_LOGGER_IF(LOGGER, LEVEL, 1, (CONDITION), ##__VA_ARGS__) + #define ENVOY_LOG_ONCE(LEVEL, ...) ENVOY_LOG_ONCE_TO_LOGGER(ENVOY_LOGGER(), LEVEL, ##__VA_ARGS__) +#define ENVOY_LOG_ONCE_IF(LEVEL, CONDITION, ...) \ + ENVOY_LOG_ONCE_TO_LOGGER_IF(ENVOY_LOGGER(), LEVEL, (CONDITION), ##__VA_ARGS__) + #define ENVOY_LOG_ONCE_MISC(LEVEL, ...) \ ENVOY_LOG_ONCE_TO_LOGGER(GET_MISC_LOGGER(), LEVEL, ##__VA_ARGS__) +#define ENVOY_LOG_ONCE_MISC_IF(LEVEL, CONDITION, ...) \ + ENVOY_LOG_ONCE_TO_LOGGER_IF(GET_MISC_LOGGER(), LEVEL, (CONDITION), ##__VA_ARGS__) + #define ENVOY_LOG_EVERY_NTH_TO_LOGGER(LOGGER, LEVEL, N, ...) \ do { \ if (ENVOY_LOG_COMP_LEVEL(LOGGER, LEVEL)) { \ diff --git a/source/common/common/thread_synchronizer.cc b/source/common/common/thread_synchronizer.cc index f41d0cdad064..492509ab1fb1 100644 --- a/source/common/common/thread_synchronizer.cc +++ b/source/common/common/thread_synchronizer.cc @@ -13,7 +13,7 @@ ThreadSynchronizer::getOrCreateEntry(absl::string_view event_name) { absl::MutexLock lock(&data_->mutex_); auto& existing_entry = data_->entries_[event_name]; if (existing_entry == nullptr) { - ENVOY_LOG(debug, "thread synchronzier: creating entry: {}", event_name); + ENVOY_LOG(debug, "thread synchronizer: creating entry: {}", event_name); existing_entry = std::make_unique(); } return *existing_entry; diff --git a/source/common/config/BUILD b/source/common/config/BUILD index 8f527194970c..0813e2bb694d 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -153,7 +153,10 @@ envoy_cc_library( envoy_cc_library( name = "resource_name_lib", hdrs = ["resource_name.h"], - deps = ["//source/common/common:assert_lib"], + deps = [ + "//source/common/common:assert_lib", + "//source/common/protobuf", + ], ) envoy_cc_library( diff --git a/source/common/config/null_grpc_mux_impl.h b/source/common/config/null_grpc_mux_impl.h index b4211e3a21ec..5e82f42e5447 100644 --- a/source/common/config/null_grpc_mux_impl.h +++ b/source/common/config/null_grpc_mux_impl.h @@ -27,6 +27,8 @@ class NullGrpcMuxImpl : public GrpcMux, ENVOY_BUG(false, "unexpected request for on demand update"); } + EdsResourcesCacheOptRef edsResourcesCache() override { return absl::nullopt; } + void onWriteable() override {} void onStreamEstablished() override {} void onEstablishmentFailure() override {} diff --git a/source/common/config/resource_name.h b/source/common/config/resource_name.h index 16d84e97bd6a..222302555630 100644 --- a/source/common/config/resource_name.h +++ b/source/common/config/resource_name.h @@ -3,6 +3,8 @@ #include #include +#include "source/common/protobuf/protobuf.h" + namespace Envoy { namespace Config { @@ -10,7 +12,7 @@ namespace Config { * Get resource name from api type. */ template std::string getResourceName() { - return Current().GetDescriptor()->full_name(); + return createReflectableMessage(Current())->GetDescriptor()->full_name(); } /** diff --git a/source/common/config/utility.cc b/source/common/config/utility.cc index 460a7b1dc1ac..ac770ddf66d4 100644 --- a/source/common/config/utility.cc +++ b/source/common/config/utility.cc @@ -71,8 +71,14 @@ void Utility::checkFilesystemSubscriptionBackingPath(const std::string& path, Ap } } -void Utility::checkApiConfigSourceNames( - const envoy::config::core::v3::ApiConfigSource& api_config_source) { +namespace { +/** + * Check the grpc_services and cluster_names for API config sanity. Throws on error. + * @param api_config_source the config source to validate. + * @throws EnvoyException when an API config has the wrong number of gRPC + * services or cluster names, depending on expectations set by its API type. + */ +void checkApiConfigSourceNames(const envoy::config::core::v3::ApiConfigSource& api_config_source) { const bool is_grpc = (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC || api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC); @@ -107,6 +113,7 @@ void Utility::checkApiConfigSourceNames( } } } +} // namespace void Utility::validateClusterName(const Upstream::ClusterManager::ClusterSet& primary_clusters, const std::string& cluster_name, @@ -129,7 +136,7 @@ void Utility::checkApiConfigSourceSubscriptionBackingCluster( envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC) { return; } - Utility::checkApiConfigSourceNames(api_config_source); + checkApiConfigSourceNames(api_config_source); const bool is_grpc = (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC); @@ -227,7 +234,7 @@ Grpc::AsyncClientFactoryPtr Utility::factoryForGrpcApiConfigSource( Grpc::AsyncClientManager& async_client_manager, const envoy::config::core::v3::ApiConfigSource& api_config_source, Stats::Scope& scope, bool skip_cluster_check) { - Utility::checkApiConfigSourceNames(api_config_source); + checkApiConfigSourceNames(api_config_source); if (api_config_source.api_type() != envoy::config::core::v3::ApiConfigSource::GRPC && api_config_source.api_type() != envoy::config::core::v3::ApiConfigSource::DELTA_GRPC) { @@ -244,13 +251,11 @@ Grpc::AsyncClientFactoryPtr Utility::factoryForGrpcApiConfigSource( void Utility::translateOpaqueConfig(const ProtobufWkt::Any& typed_config, ProtobufMessage::ValidationVisitor& validation_visitor, Protobuf::Message& out_proto) { - static const std::string struct_type = - ProtobufWkt::Struct::default_instance().GetDescriptor()->full_name(); + static const std::string struct_type = ProtobufWkt::Struct::default_instance().GetTypeName(); static const std::string typed_struct_type = - xds::type::v3::TypedStruct::default_instance().GetDescriptor()->full_name(); + xds::type::v3::TypedStruct::default_instance().GetTypeName(); static const std::string legacy_typed_struct_type = - udpa::type::v1::TypedStruct::default_instance().GetDescriptor()->full_name(); - + udpa::type::v1::TypedStruct::default_instance().GetTypeName(); if (!typed_config.value().empty()) { // Unpack methods will only use the fully qualified type name after the last '/'. // https://github.com/protocolbuffers/protobuf/blob/3.6.x/src/google/protobuf/any.proto#L87 @@ -260,8 +265,8 @@ void Utility::translateOpaqueConfig(const ProtobufWkt::Any& typed_config, xds::type::v3::TypedStruct typed_struct; MessageUtil::unpackTo(typed_config, typed_struct); // if out_proto is expecting Struct, return directly - if (out_proto.GetDescriptor()->full_name() == struct_type) { - out_proto.CopyFrom(typed_struct.value()); + if (out_proto.GetTypeName() == struct_type) { + out_proto.CheckTypeAndMergeFrom(typed_struct.value()); } else { // The typed struct might match out_proto, or some earlier version, let // MessageUtil::jsonConvert sort this out. @@ -275,8 +280,8 @@ void Utility::translateOpaqueConfig(const ProtobufWkt::Any& typed_config, udpa::type::v1::TypedStruct typed_struct; MessageUtil::unpackTo(typed_config, typed_struct); // if out_proto is expecting Struct, return directly - if (out_proto.GetDescriptor()->full_name() == struct_type) { - out_proto.CopyFrom(typed_struct.value()); + if (out_proto.GetTypeName() == struct_type) { + out_proto.CheckTypeAndMergeFrom(typed_struct.value()); } else { // The typed struct might match out_proto, or some earlier version, let // MessageUtil::jsonConvert sort this out. @@ -288,7 +293,7 @@ void Utility::translateOpaqueConfig(const ProtobufWkt::Any& typed_config, #endif } } // out_proto is expecting Struct, unpack directly - else if (type != struct_type || out_proto.GetDescriptor()->full_name() == struct_type) { + else if (type != struct_type || out_proto.GetTypeName() == struct_type) { MessageUtil::unpackTo(typed_config, out_proto); } else { #ifdef ENVOY_ENABLE_YAML diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 5ea480bce7dd..b111ae6fed3d 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -102,7 +102,7 @@ class Utility { /** * Extract initial_fetch_timeout as a std::chrono::milliseconds from * envoy::config::core::v3::ApiConfigSource. If request_timeout isn't set in the config source, a - * default value of 0s will be returned. + * default value of 15s will be returned. */ static std::chrono::milliseconds configSourceInitialFetchTimeout(const envoy::config::core::v3::ConfigSource& config_source); @@ -148,15 +148,6 @@ class Utility { */ static void checkFilesystemSubscriptionBackingPath(const std::string& path, Api::Api& api); - /** - * Check the grpc_services and cluster_names for API config sanity. Throws on error. - * @param api_config_source the config source to validate. - * @throws EnvoyException when an API config has the wrong number of gRPC - * services or cluster names, depending on expectations set by its API type. - */ - static void - checkApiConfigSourceNames(const envoy::config::core::v3::ApiConfigSource& api_config_source); - /** * Check the validity of a cluster backing an api config source. Throws on error. * @param primary_clusters the API config source eligible clusters. @@ -338,9 +329,9 @@ class Utility { */ static std::string getFactoryType(const ProtobufWkt::Any& typed_config) { static const std::string& typed_struct_type = - xds::type::v3::TypedStruct::default_instance().GetDescriptor()->full_name(); + xds::type::v3::TypedStruct::default_instance().GetTypeName(); static const std::string& legacy_typed_struct_type = - udpa::type::v1::TypedStruct::default_instance().GetDescriptor()->full_name(); + udpa::type::v1::TypedStruct::default_instance().GetTypeName(); // Unpack methods will only use the fully qualified type name after the last '/'. // https://github.com/protocolbuffers/protobuf/blob/3.6.x/src/google/protobuf/any.proto#L87 auto type = std::string(TypeUtil::typeUrlToDescriptorFullName(typed_config.type_url())); @@ -389,7 +380,7 @@ class Utility { RELEASE_ASSERT(config != nullptr, ""); // Check that the config type is not google.protobuf.Empty - RELEASE_ASSERT(config->GetDescriptor()->full_name() != "google.protobuf.Empty", ""); + RELEASE_ASSERT(config->GetTypeName() != "google.protobuf.Empty", ""); translateOpaqueConfig(enclosing_message.typed_config(), validation_visitor, *config); return config; @@ -413,7 +404,7 @@ class Utility { RELEASE_ASSERT(config != nullptr, ""); // Check that the config type is not google.protobuf.Empty - RELEASE_ASSERT(config->GetDescriptor()->full_name() != "google.protobuf.Empty", ""); + RELEASE_ASSERT(config->GetTypeName() != "google.protobuf.Empty", ""); translateOpaqueConfig(typed_config, validation_visitor, *config); return config; diff --git a/source/common/filesystem/BUILD b/source/common/filesystem/BUILD index 34b812833eed..e23bd7a27d3f 100644 --- a/source/common/filesystem/BUILD +++ b/source/common/filesystem/BUILD @@ -25,6 +25,7 @@ envoy_cc_win32_library( strip_include_prefix = "win32", deps = [ "//envoy/filesystem:filesystem_interface", + "//source/common/common:fmt_lib", ], ) diff --git a/source/common/filter/config_discovery_impl.cc b/source/common/filter/config_discovery_impl.cc index 3f6c40cce4b4..6bcf095d0d5c 100644 --- a/source/common/filter/config_discovery_impl.cc +++ b/source/common/filter/config_discovery_impl.cc @@ -59,7 +59,8 @@ const std::string& DynamicFilterConfigProviderImplBase::name() { return subscrip FilterConfigSubscription::FilterConfigSubscription( const envoy::config::core::v3::ConfigSource& config_source, const std::string& filter_config_name, - Server::Configuration::ServerFactoryContext& factory_context, const std::string& stat_prefix, + Server::Configuration::ServerFactoryContext& factory_context, + Upstream::ClusterManager& cluster_manager, const std::string& stat_prefix, FilterConfigProviderManagerImplBase& filter_config_provider_manager, const std::string& subscription_id) : Config::SubscriptionBase( @@ -74,10 +75,8 @@ FilterConfigSubscription::FilterConfigSubscription( filter_config_provider_manager_(filter_config_provider_manager), subscription_id_(subscription_id) { const auto resource_name = getResourceName(); - subscription_ = - factory_context.clusterManager().subscriptionFactory().subscriptionFromConfigSource( - config_source, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_, - {}); + subscription_ = cluster_manager.subscriptionFactory().subscriptionFromConfigSource( + config_source, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_, {}); } void FilterConfigSubscription::start() { @@ -181,7 +180,8 @@ void FilterConfigSubscription::incrementConflictCounter() { stats_.config_confli std::shared_ptr FilterConfigProviderManagerImplBase::getSubscription( const envoy::config::core::v3::ConfigSource& config_source, const std::string& name, - Server::Configuration::ServerFactoryContext& server_context, const std::string& stat_prefix) { + Server::Configuration::ServerFactoryContext& server_context, + Upstream::ClusterManager& cluster_manager, const std::string& stat_prefix) { // There are ECDS filters configured. Setup ECDS config dump call backs. setupEcdsConfigDumpCallbacks(server_context.admin()); @@ -193,7 +193,7 @@ std::shared_ptr FilterConfigProviderManagerImplBase::g auto it = subscriptions_.find(subscription_id); if (it == subscriptions_.end()) { auto subscription = std::make_shared( - config_source, name, server_context, stat_prefix, *this, subscription_id); + config_source, name, server_context, cluster_manager, stat_prefix, *this, subscription_id); subscriptions_.insert({subscription_id, std::weak_ptr(subscription)}); return subscription; } else { diff --git a/source/common/filter/config_discovery_impl.h b/source/common/filter/config_discovery_impl.h index 232285f57e93..56b7ac5fa67f 100644 --- a/source/common/filter/config_discovery_impl.h +++ b/source/common/filter/config_discovery_impl.h @@ -224,10 +224,10 @@ class HttpDynamicFilterConfigProviderImpl }; template -class NetworkDynamicFilterConfigProviderImpl +class NetworkDynamicFilterConfigProviderImplBase : public DynamicFilterConfigProviderImpl { public: - NetworkDynamicFilterConfigProviderImpl( + NetworkDynamicFilterConfigProviderImplBase( FilterConfigSubscriptionSharedPtr& subscription, const absl::flat_hash_set& require_type_urls, Server::Configuration::ServerFactoryContext& server_context, FactoryCtx& factory_context, @@ -239,14 +239,6 @@ class NetworkDynamicFilterConfigProviderImpl last_filter_in_filter_chain, filter_chain_type, stat_prefix, listener_filter_matcher), server_context_(server_context), factory_context_(factory_context) {} - void validateMessage(const std::string& config_name, const Protobuf::Message& message, - const std::string& factory_name) const override { - auto* factory = - Registry::FactoryRegistry::getFactory(factory_name); - const bool is_terminal_filter = factory->isTerminalFilterByProto(message, server_context_); - Config::Utility::validateTerminalFilters(config_name, factory_name, filter_chain_type_, - is_terminal_filter, last_filter_in_filter_chain_); - } private: Network::FilterFactoryCb @@ -256,10 +248,45 @@ class NetworkDynamicFilterConfigProviderImpl return factory->createFilterFactoryFromProto(message, factory_context_); } +protected: Server::Configuration::ServerFactoryContext& server_context_; FactoryCtx& factory_context_; }; +template +class DownstreamNetworkDynamicFilterConfigProviderImpl + : public NetworkDynamicFilterConfigProviderImplBase { +public: + using NetworkDynamicFilterConfigProviderImplBase< + FactoryCtx, NeutralNetworkFilterConfigFactory>::NetworkDynamicFilterConfigProviderImplBase; + + void validateMessage(const std::string& config_name, const Protobuf::Message& message, + const std::string& factory_name) const override { + auto* factory = + Registry::FactoryRegistry::getFactory(factory_name); + const bool is_terminal_filter = + factory->isTerminalFilterByProto(message, this->server_context_); + Config::Utility::validateTerminalFilters(config_name, factory_name, this->filter_chain_type_, + is_terminal_filter, + this->last_filter_in_filter_chain_); + } +}; + +template +class UpstreamNetworkDynamicFilterConfigProviderImpl + : public NetworkDynamicFilterConfigProviderImplBase { +public: + using NetworkDynamicFilterConfigProviderImplBase< + FactoryCtx, NeutralNetworkFilterConfigFactory>::NetworkDynamicFilterConfigProviderImplBase; + + void validateMessage(const std::string&, const Protobuf::Message&, + const std::string&) const override { + // Upstream network filters don't use the concept of terminal filters. + } +}; + // Implementation of a listener dynamic filter config provider. template class ListenerDynamicFilterConfigProviderImpl : public DynamicFilterConfigProviderImpl { @@ -344,6 +371,7 @@ class FilterConfigSubscription FilterConfigSubscription(const envoy::config::core::v3::ConfigSource& config_source, const std::string& filter_config_name, Server::Configuration::ServerFactoryContext& factory_context, + Upstream::ClusterManager& cluster_manager, const std::string& stat_prefix, FilterConfigProviderManagerImplBase& filter_config_provider_manager, const std::string& subscription_id); @@ -438,9 +466,11 @@ class FilterConfigProviderManagerImplBase : Logger::Loggable Server::Configuration::ServerFactoryContext& factory_context) const PURE; protected: - std::shared_ptr getSubscription( - const envoy::config::core::v3::ConfigSource& config_source, const std::string& name, - Server::Configuration::ServerFactoryContext& server_context, const std::string& stat_prefix); + std::shared_ptr + getSubscription(const envoy::config::core::v3::ConfigSource& config_source, + const std::string& name, + Server::Configuration::ServerFactoryContext& server_context, + Upstream::ClusterManager& cluster_manager, const std::string& stat_prefix); void applyLastOrDefaultConfig(std::shared_ptr& subscription, DynamicFilterConfigProviderImplBase& provider, const std::string& filter_config_name); @@ -474,7 +504,7 @@ class FilterConfigProviderManagerImplBase : Logger::Loggable envoy::config::core::v3::TypedExtensionConfig filter_config; filter_config.set_name(ecds_filter->name()); if (ecds_filter->lastConfig()) { - filter_config.mutable_typed_config()->PackFrom(*ecds_filter->lastConfig()); + MessageUtil::packFrom(*filter_config.mutable_typed_config(), *ecds_filter->lastConfig()); } auto& filter_config_dump = *config_dump->mutable_ecds_filters()->Add(); filter_config_dump.mutable_ecds_filter()->PackFrom(filter_config); @@ -502,7 +532,8 @@ class FilterConfigProviderManagerImpl : public FilterConfigProviderManagerImplBa const envoy::config::core::v3::ExtensionConfigSource& config_source, const std::string& filter_config_name, Server::Configuration::ServerFactoryContext& server_context, FactoryCtx& factory_context, - bool last_filter_in_filter_chain, const std::string& filter_chain_type, + Upstream::ClusterManager& cluster_manager, bool last_filter_in_filter_chain, + const std::string& filter_chain_type, const Network::ListenerFilterMatcherSharedPtr& listener_filter_matcher) override { std::string subscription_stat_prefix; absl::string_view provider_stat_prefix; @@ -511,7 +542,7 @@ class FilterConfigProviderManagerImpl : public FilterConfigProviderManagerImplBa provider_stat_prefix = subscription_stat_prefix; auto subscription = getSubscription(config_source.config_source(), filter_config_name, - server_context, subscription_stat_prefix); + server_context, cluster_manager, subscription_stat_prefix); // For warming, wait until the subscription receives the first response to indicate readiness. // Otherwise, mark ready immediately and start the subscription on initialization. A default // config is expected in the latter case. @@ -648,7 +679,7 @@ class NetworkFilterConfigProviderManagerImpl : public FilterConfigProviderManagerImpl< Server::Configuration::NamedNetworkFilterConfigFactory, Network::FilterFactoryCb, Server::Configuration::FactoryContext, - NetworkDynamicFilterConfigProviderImpl< + DownstreamNetworkDynamicFilterConfigProviderImpl< Server::Configuration::FactoryContext, Server::Configuration::NamedNetworkFilterConfigFactory>> { public: @@ -675,7 +706,7 @@ class UpstreamNetworkFilterConfigProviderManagerImpl : public FilterConfigProviderManagerImpl< Server::Configuration::NamedUpstreamNetworkFilterConfigFactory, Network::FilterFactoryCb, Server::Configuration::CommonFactoryContext, - NetworkDynamicFilterConfigProviderImpl< + UpstreamNetworkDynamicFilterConfigProviderImpl< Server::Configuration::CommonFactoryContext, Server::Configuration::NamedUpstreamNetworkFilterConfigFactory>> { public: @@ -688,11 +719,9 @@ class UpstreamNetworkFilterConfigProviderManagerImpl Server::Configuration::ServerFactoryContext& factory_context) const override { return default_factory->isTerminalFilterByProto(message, factory_context); } - void validateFilters(const std::string& filter_config_name, const std::string& filter_type, - const std::string& filter_chain_type, bool is_terminal_filter, - bool last_filter_in_filter_chain) const override { - Config::Utility::validateTerminalFilters(filter_config_name, filter_type, filter_chain_type, - is_terminal_filter, last_filter_in_filter_chain); + void validateFilters(const std::string&, const std::string&, const std::string&, bool, + bool) const override { + // Upstream network filters don't use the concept of terminal filters. } const std::string getConfigDumpType() const override { return "ecds_filter_upstream_network"; } }; diff --git a/source/common/formatter/BUILD b/source/common/formatter/BUILD index 202ad9d030cd..eeefb5637d0c 100644 --- a/source/common/formatter/BUILD +++ b/source/common/formatter/BUILD @@ -10,8 +10,16 @@ envoy_package() envoy_cc_library( name = "substitution_formatter_lib", - srcs = ["substitution_formatter.cc"], - hdrs = ["substitution_formatter.h"], + srcs = [ + "http_specific_formatter.cc", + "stream_info_formatter.cc", + "substitution_formatter.cc", + ], + hdrs = [ + "http_specific_formatter.h", + "stream_info_formatter.h", + "substitution_formatter.h", + ], external_deps = ["abseil_str_format"], deps = [ "//envoy/api:api_interface", diff --git a/source/common/formatter/http_specific_formatter.cc b/source/common/formatter/http_specific_formatter.cc new file mode 100644 index 000000000000..42724dd659ee --- /dev/null +++ b/source/common/formatter/http_specific_formatter.cc @@ -0,0 +1,410 @@ +#include "source/common/formatter/http_specific_formatter.h" + +#include "source/common/common/assert.h" +#include "source/common/common/empty_string.h" +#include "source/common/common/fmt.h" +#include "source/common/common/thread.h" +#include "source/common/common/utility.h" +#include "source/common/config/metadata.h" +#include "source/common/grpc/common.h" +#include "source/common/grpc/status.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/message_validator_impl.h" +#include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_features.h" +#include "source/common/stream_info/utility.h" + +namespace Envoy { +namespace Formatter { + +absl::optional LocalReplyBodyFormatter::format(const Http::RequestHeaderMap&, + const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo&, + absl::string_view local_reply_body, + AccessLog::AccessLogType) const { + return std::string(local_reply_body); +} + +ProtobufWkt::Value LocalReplyBodyFormatter::formatValue(const Http::RequestHeaderMap&, + const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo&, + absl::string_view local_reply_body, + AccessLog::AccessLogType) const { + return ValueUtil::stringValue(std::string(local_reply_body)); +} + +absl::optional +AccessLogTypeFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType access_log_type) const { + return AccessLogType_Name(access_log_type); +} + +ProtobufWkt::Value +AccessLogTypeFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, + AccessLog::AccessLogType access_log_type) const { + return ValueUtil::stringValue(AccessLogType_Name(access_log_type)); +} + +HeaderFormatter::HeaderFormatter(const std::string& main_header, + const std::string& alternative_header, + absl::optional max_length) + : main_header_(main_header), alternative_header_(alternative_header), max_length_(max_length) {} + +const Http::HeaderEntry* HeaderFormatter::findHeader(const Http::HeaderMap& headers) const { + const auto header = headers.get(main_header_); + + if (header.empty() && !alternative_header_.get().empty()) { + const auto alternate_header = headers.get(alternative_header_); + // TODO(https://github.com/envoyproxy/envoy/issues/13454): Potentially log all header values. + return alternate_header.empty() ? nullptr : alternate_header[0]; + } + + return header.empty() ? nullptr : header[0]; +} + +absl::optional HeaderFormatter::format(const Http::HeaderMap& headers) const { + const Http::HeaderEntry* header = findHeader(headers); + if (!header) { + return absl::nullopt; + } + + std::string val = std::string(header->value().getStringView()); + SubstitutionFormatUtils::truncate(val, max_length_); + return val; +} + +ProtobufWkt::Value HeaderFormatter::formatValue(const Http::HeaderMap& headers) const { + const Http::HeaderEntry* header = findHeader(headers); + if (!header) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + std::string val = std::string(header->value().getStringView()); + SubstitutionFormatUtils::truncate(val, max_length_); + return ValueUtil::stringValue(val); +} + +ResponseHeaderFormatter::ResponseHeaderFormatter(const std::string& main_header, + const std::string& alternative_header, + absl::optional max_length) + : HeaderFormatter(main_header, alternative_header, max_length) {} + +absl::optional +ResponseHeaderFormatter::format(const Http::RequestHeaderMap&, + const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const { + return HeaderFormatter::format(response_headers); +} + +ProtobufWkt::Value +ResponseHeaderFormatter::formatValue(const Http::RequestHeaderMap&, + const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const { + return HeaderFormatter::formatValue(response_headers); +} + +RequestHeaderFormatter::RequestHeaderFormatter(const std::string& main_header, + const std::string& alternative_header, + absl::optional max_length) + : HeaderFormatter(main_header, alternative_header, max_length) {} + +absl::optional +RequestHeaderFormatter::format(const Http::RequestHeaderMap& request_headers, + const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo&, absl::string_view, + AccessLog::AccessLogType) const { + return HeaderFormatter::format(request_headers); +} + +ProtobufWkt::Value +RequestHeaderFormatter::formatValue(const Http::RequestHeaderMap& request_headers, + const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo&, absl::string_view, + AccessLog::AccessLogType) const { + return HeaderFormatter::formatValue(request_headers); +} + +ResponseTrailerFormatter::ResponseTrailerFormatter(const std::string& main_header, + const std::string& alternative_header, + absl::optional max_length) + : HeaderFormatter(main_header, alternative_header, max_length) {} + +absl::optional +ResponseTrailerFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap& response_trailers, + const StreamInfo::StreamInfo&, absl::string_view, + AccessLog::AccessLogType) const { + return HeaderFormatter::format(response_trailers); +} + +ProtobufWkt::Value +ResponseTrailerFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap& response_trailers, + const StreamInfo::StreamInfo&, absl::string_view, + AccessLog::AccessLogType) const { + return HeaderFormatter::formatValue(response_trailers); +} + +HeadersByteSizeFormatter::HeadersByteSizeFormatter(const HeaderType header_type) + : header_type_(header_type) {} + +uint64_t HeadersByteSizeFormatter::extractHeadersByteSize( + const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers) const { + switch (header_type_) { + case HeaderType::RequestHeaders: + return request_headers.byteSize(); + case HeaderType::ResponseHeaders: + return response_headers.byteSize(); + case HeaderType::ResponseTrailers: + return response_trailers.byteSize(); + } + PANIC_DUE_TO_CORRUPT_ENUM; +} + +absl::optional HeadersByteSizeFormatter::format( + const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const { + return absl::StrCat(extractHeadersByteSize(request_headers, response_headers, response_trailers)); +} + +ProtobufWkt::Value HeadersByteSizeFormatter::formatValue( + const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const { + return ValueUtil::numberValue( + extractHeadersByteSize(request_headers, response_headers, response_trailers)); +} + +GrpcStatusFormatter::Format GrpcStatusFormatter::parseFormat(absl::string_view format) { + if (format.empty() || format == "CAMEL_STRING") { + return GrpcStatusFormatter::CamelString; + } + + if (format == "SNAKE_STRING") { + return GrpcStatusFormatter::SnakeString; + } + if (format == "NUMBER") { + return GrpcStatusFormatter::Number; + } + + throw EnvoyException("GrpcStatusFormatter only supports CAMEL_STRING, SNAKE_STRING or NUMBER."); +} + +GrpcStatusFormatter::GrpcStatusFormatter(const std::string& main_header, + const std::string& alternative_header, + absl::optional max_length, Format format) + : HeaderFormatter(main_header, alternative_header, max_length), format_(format) {} + +absl::optional GrpcStatusFormatter::format( + const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo& info, + absl::string_view, AccessLog::AccessLogType) const { + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.validate_grpc_header_before_log_grpc_status")) { + if (!Grpc::Common::isGrpcRequestHeaders(request_headers)) { + return absl::nullopt; + } + } + const auto grpc_status = + Grpc::Common::getGrpcStatus(response_trailers, response_headers, info, true); + if (!grpc_status.has_value()) { + return absl::nullopt; + } + switch (format_) { + case CamelString: { + const auto grpc_status_message = Grpc::Utility::grpcStatusToString(grpc_status.value()); + if (grpc_status_message == EMPTY_STRING || grpc_status_message == "InvalidCode") { + return std::to_string(grpc_status.value()); + } + return grpc_status_message; + } + case SnakeString: { + const auto grpc_status_message = + absl::StatusCodeToString(static_cast(grpc_status.value())); + if (grpc_status_message == EMPTY_STRING) { + return std::to_string(grpc_status.value()); + } + return grpc_status_message; + } + case Number: { + return std::to_string(grpc_status.value()); + } + } + PANIC_DUE_TO_CORRUPT_ENUM; +} + +ProtobufWkt::Value GrpcStatusFormatter::formatValue( + const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo& info, + absl::string_view, AccessLog::AccessLogType) const { + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.validate_grpc_header_before_log_grpc_status")) { + if (!Grpc::Common::isGrpcRequestHeaders(request_headers)) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + } + const auto grpc_status = + Grpc::Common::getGrpcStatus(response_trailers, response_headers, info, true); + if (!grpc_status.has_value()) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + switch (format_) { + case CamelString: { + const auto grpc_status_message = Grpc::Utility::grpcStatusToString(grpc_status.value()); + if (grpc_status_message == EMPTY_STRING || grpc_status_message == "InvalidCode") { + return ValueUtil::stringValue(std::to_string(grpc_status.value())); + } + return ValueUtil::stringValue(grpc_status_message); + } + case SnakeString: { + const auto grpc_status_message = + absl::StatusCodeToString(static_cast(grpc_status.value())); + if (grpc_status_message == EMPTY_STRING) { + return ValueUtil::stringValue(std::to_string(grpc_status.value())); + } + return ValueUtil::stringValue(grpc_status_message); + } + case Number: { + return ValueUtil::numberValue(grpc_status.value()); + } + } + PANIC_DUE_TO_CORRUPT_ENUM; +} + +StreamInfoRequestHeaderFormatter::StreamInfoRequestHeaderFormatter( + const std::string& main_header, const std::string& alternative_header, + absl::optional max_length) + : HeaderFormatter(main_header, alternative_header, max_length) {} + +absl::optional StreamInfoRequestHeaderFormatter::format( + const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { + return HeaderFormatter::format(*stream_info.getRequestHeaders()); +} + +ProtobufWkt::Value StreamInfoRequestHeaderFormatter::formatValue( + const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { + return HeaderFormatter::formatValue(*stream_info.getRequestHeaders()); +} + +const HttpBuiltInCommandParser::FormatterProviderLookupTbl& +HttpBuiltInCommandParser::getKnownFormatters() { + CONSTRUCT_ON_FIRST_USE( + FormatterProviderLookupTbl, + {{"REQ", + {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, + [](const std::string& format, absl::optional& max_length) { + std::string main_header, alternative_header; + + SubstitutionFormatParser::parseSubcommandHeaders(format, main_header, + alternative_header); + + return std::make_unique(main_header, alternative_header, + max_length); + }}}, + {"RESP", + {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, + [](const std::string& format, absl::optional& max_length) { + std::string main_header, alternative_header; + + SubstitutionFormatParser::parseSubcommandHeaders(format, main_header, + alternative_header); + + return std::make_unique(main_header, alternative_header, + max_length); + }}}, + {"TRAILER", + {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, + [](const std::string& format, absl::optional& max_length) { + std::string main_header, alternative_header; + + SubstitutionFormatParser::parseSubcommandHeaders(format, main_header, + alternative_header); + + return std::make_unique(main_header, alternative_header, + max_length); + }}}, + {"LOCAL_REPLY_BODY", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional&) { + return std::make_unique(); + }}}, + {"ACCESS_LOG_TYPE", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional&) { + return std::make_unique(); + }}}, + {"GRPC_STATUS", + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](const std::string& format, const absl::optional&) { + return std::make_unique("grpc-status", "", absl::optional(), + GrpcStatusFormatter::parseFormat(format)); + }}}, + {"GRPC_STATUS_NUMBER", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, const absl::optional&) { + return std::make_unique("grpc-status", "", absl::optional(), + GrpcStatusFormatter::Number); + }}}, + {"REQUEST_HEADERS_BYTES", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional&) { + return std::make_unique( + HeadersByteSizeFormatter::HeaderType::RequestHeaders); + }}}, + {"RESPONSE_HEADERS_BYTES", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional&) { + return std::make_unique( + HeadersByteSizeFormatter::HeaderType::ResponseHeaders); + }}}, + {"RESPONSE_TRAILERS_BYTES", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional&) { + return std::make_unique( + HeadersByteSizeFormatter::HeaderType::ResponseTrailers); + }}}, + {"STREAM_INFO_REQ", + {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, + [](const std::string& format, absl::optional& max_length) { + std::string main_header, alternative_header; + SubstitutionFormatParser::parseSubcommandHeaders(format, main_header, + alternative_header); + + return std::make_unique(main_header, alternative_header, + max_length); + }}}}); +} + +FormatterProviderPtr HttpBuiltInCommandParser::parse(const std::string& command, + const std::string& subcommand, + absl::optional& max_length) const { + const FormatterProviderLookupTbl& providers = getKnownFormatters(); + + auto it = providers.find(command); + + if (it == providers.end()) { + return nullptr; + } + + // Check flags for the command. + CommandSyntaxChecker::verifySyntax((*it).second.first, command, subcommand, max_length); + + // Create a pointer to the formatter by calling a function + // associated with formatter's name. + return (*it).second.second(subcommand, max_length); +} + +} // namespace Formatter +} // namespace Envoy diff --git a/source/common/formatter/http_specific_formatter.h b/source/common/formatter/http_specific_formatter.h new file mode 100644 index 000000000000..f71daaa79875 --- /dev/null +++ b/source/common/formatter/http_specific_formatter.h @@ -0,0 +1,227 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "envoy/formatter/substitution_formatter.h" +#include "envoy/stream_info/stream_info.h" + +#include "source/common/common/utility.h" +#include "source/common/formatter/stream_info_formatter.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Formatter { + +/** + * FormatterProvider for local_reply_body. It returns the string from `local_reply_body` argument. + */ +class LocalReplyBodyFormatter : public FormatterProvider { +public: + LocalReplyBodyFormatter() = default; + + // Formatter::format + absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view local_reply_body, + AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view local_reply_body, + AccessLog::AccessLogType) const override; +}; + +/** + * FormatterProvider for access log type. It returns the string from `access_log_type` argument. + */ +class AccessLogTypeFormatter : public FormatterProvider { +public: + AccessLogTypeFormatter() = default; + + // Formatter::format + absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view local_reply_body, + AccessLog::AccessLogType access_log_type) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view local_reply_body, + AccessLog::AccessLogType access_log_type) const override; +}; + +class HeaderFormatter { +public: + HeaderFormatter(const std::string& main_header, const std::string& alternative_header, + absl::optional max_length); + +protected: + absl::optional format(const Http::HeaderMap& headers) const; + ProtobufWkt::Value formatValue(const Http::HeaderMap& headers) const; + +private: + const Http::HeaderEntry* findHeader(const Http::HeaderMap& headers) const; + + Http::LowerCaseString main_header_; + Http::LowerCaseString alternative_header_; + absl::optional max_length_; +}; + +/** + * FormatterProvider for headers byte size. + */ +class HeadersByteSizeFormatter : public FormatterProvider { +public: + // TODO(taoxuy): Add RequestTrailers here. + enum class HeaderType { RequestHeaders, ResponseHeaders, ResponseTrailers }; + + HeadersByteSizeFormatter(const HeaderType header_type); + + absl::optional format(const Http::RequestHeaderMap& request_headers, + const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers, + const StreamInfo::StreamInfo&, absl::string_view, + AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap& request_headers, + const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers, + const StreamInfo::StreamInfo&, absl::string_view, + AccessLog::AccessLogType) const override; + +private: + uint64_t extractHeadersByteSize(const Http::RequestHeaderMap& request_headers, + const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers) const; + HeaderType header_type_; +}; + +/** + * FormatterProvider for request headers. + */ +class RequestHeaderFormatter : public FormatterProvider, HeaderFormatter { +public: + RequestHeaderFormatter(const std::string& main_header, const std::string& alternative_header, + absl::optional max_length); + + // FormatterProvider + absl::optional format(const Http::RequestHeaderMap& request_headers, + const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; +}; + +/** + * FormatterProvider for response headers. + */ +class ResponseHeaderFormatter : public FormatterProvider, HeaderFormatter { +public: + ResponseHeaderFormatter(const std::string& main_header, const std::string& alternative_header, + absl::optional max_length); + + // FormatterProvider + absl::optional format(const Http::RequestHeaderMap&, + const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; +}; + +/** + * FormatterProvider for response trailers. + */ +class ResponseTrailerFormatter : public FormatterProvider, HeaderFormatter { +public: + ResponseTrailerFormatter(const std::string& main_header, const std::string& alternative_header, + absl::optional max_length); + + // FormatterProvider + absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap& response_trailers, + const StreamInfo::StreamInfo&, absl::string_view, + AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; +}; + +class GrpcStatusFormatter : public FormatterProvider, HeaderFormatter { +public: + enum Format { + CamelString, + SnakeString, + Number, + }; + + GrpcStatusFormatter(const std::string& main_header, const std::string& alternative_header, + absl::optional max_length, Format format); + + // FormatterProvider + absl::optional format(const Http::RequestHeaderMap&, + const Http::ResponseHeaderMap& response_headers, + const Http::ResponseTrailerMap& response_trailers, + const StreamInfo::StreamInfo&, absl::string_view, + AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + + static Format parseFormat(absl::string_view format); + +private: + const Format format_; +}; + +/** + * FormatterProvider for request headers from StreamInfo (rather than the request_headers param). + * Purely for testing. + */ +class StreamInfoRequestHeaderFormatter : public FormatterProvider, HeaderFormatter { +public: + StreamInfoRequestHeaderFormatter(const std::string& main_header, + const std::string& alternative_header, + absl::optional max_length); + + // FormatterProvider + absl::optional format(const Http::RequestHeaderMap& request_headers, + const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; +}; + +class HttpBuiltInCommandParser : public CommandParser { +public: + HttpBuiltInCommandParser() = default; + + // CommandParser + FormatterProviderPtr parse(const std::string& command, const std::string& subcommand, + absl::optional& max_length) const override; + + static const CommandParser& builtInCommandParser() { + CONSTRUCT_ON_FIRST_USE(HttpBuiltInCommandParser); + } + +private: + using FormatterProviderCreateFunc = + std::function&)>; + + using FormatterProviderLookupTbl = + absl::flat_hash_map>; + static const FormatterProviderLookupTbl& getKnownFormatters(); +}; + +} // namespace Formatter +} // namespace Envoy diff --git a/source/common/formatter/stream_info_formatter.cc b/source/common/formatter/stream_info_formatter.cc new file mode 100644 index 000000000000..cb4b179a9f8e --- /dev/null +++ b/source/common/formatter/stream_info_formatter.cc @@ -0,0 +1,1431 @@ +#include "source/common/formatter/stream_info_formatter.h" + +#include + +#include "source/common/config/metadata.h" +#include "source/common/http/utility.h" +#include "source/common/runtime/runtime_features.h" +#include "source/common/stream_info/utility.h" + +#include "absl/strings/str_format.h" +#include "absl/strings/str_replace.h" + +namespace Envoy { +namespace Formatter { + +namespace { + +static const std::string DefaultUnspecifiedValueString = "-"; + +const std::regex& getSystemTimeFormatNewlinePattern() { + CONSTRUCT_ON_FIRST_USE(std::regex, "%[-_0^#]*[1-9]*(E|O)?n"); +} + +} // namespace + +MetadataFormatter::MetadataFormatter(const std::string& filter_namespace, + const std::vector& path, + absl::optional max_length, + MetadataFormatter::GetMetadataFunction get_func) + : filter_namespace_(filter_namespace), path_(path), max_length_(max_length), + get_func_(get_func) {} + +absl::optional +MetadataFormatter::formatMetadata(const envoy::config::core::v3::Metadata& metadata) const { + ProtobufWkt::Value value = formatMetadataValue(metadata); + if (value.kind_case() == ProtobufWkt::Value::kNullValue) { + return absl::nullopt; + } + + std::string str; + if (value.kind_case() == ProtobufWkt::Value::kStringValue) { + str = value.string_value(); + } else { +#ifdef ENVOY_ENABLE_YAML + absl::StatusOr json_or_error = + MessageUtil::getJsonStringFromMessage(value, false, true); + if (json_or_error.ok()) { + str = json_or_error.value(); + } else { + str = json_or_error.status().message(); + } +#else + IS_ENVOY_BUG("Json support compiled out"); +#endif + } + SubstitutionFormatUtils::truncate(str, max_length_); + return str; +} + +ProtobufWkt::Value +MetadataFormatter::formatMetadataValue(const envoy::config::core::v3::Metadata& metadata) const { + if (path_.empty()) { + const auto filter_it = metadata.filter_metadata().find(filter_namespace_); + if (filter_it == metadata.filter_metadata().end()) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + ProtobufWkt::Value output; + output.mutable_struct_value()->CopyFrom(filter_it->second); + return output; + } + + const ProtobufWkt::Value& val = + Config::Metadata::metadataValue(&metadata, filter_namespace_, path_); + if (val.kind_case() == ProtobufWkt::Value::KindCase::KIND_NOT_SET) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + return val; +} + +absl::optional +MetadataFormatter::format(const StreamInfo::StreamInfo& stream_info) const { + auto metadata = get_func_(stream_info); + return (metadata != nullptr) ? formatMetadata(*metadata) : absl::nullopt; +} + +ProtobufWkt::Value MetadataFormatter::formatValue(const StreamInfo::StreamInfo& stream_info) const { + auto metadata = get_func_(stream_info); + return formatMetadataValue((metadata != nullptr) ? *metadata + : envoy::config::core::v3::Metadata()); +} + +// TODO(glicht): Consider adding support for route/listener/cluster metadata as suggested by +// @htuch. See: https://github.com/envoyproxy/envoy/issues/3006 +DynamicMetadataFormatter::DynamicMetadataFormatter(const std::string& filter_namespace, + const std::vector& path, + absl::optional max_length) + : MetadataFormatter(filter_namespace, path, max_length, + [](const StreamInfo::StreamInfo& stream_info) { + return &stream_info.dynamicMetadata(); + }) {} + +ClusterMetadataFormatter::ClusterMetadataFormatter(const std::string& filter_namespace, + const std::vector& path, + absl::optional max_length) + : MetadataFormatter(filter_namespace, path, max_length, + [](const StreamInfo::StreamInfo& stream_info) + -> const envoy::config::core::v3::Metadata* { + auto cluster_info = stream_info.upstreamClusterInfo(); + if (!cluster_info.has_value() || cluster_info.value() == nullptr) { + return nullptr; + } + return &cluster_info.value()->metadata(); + }) {} + +UpstreamHostMetadataFormatter::UpstreamHostMetadataFormatter(const std::string& filter_namespace, + const std::vector& path, + absl::optional max_length) + : MetadataFormatter(filter_namespace, path, max_length, + [](const StreamInfo::StreamInfo& stream_info) + -> const envoy::config::core::v3::Metadata* { + if (!stream_info.upstreamInfo().has_value()) { + return nullptr; + } + Upstream::HostDescriptionConstSharedPtr host = + stream_info.upstreamInfo()->upstreamHost(); + if (host == nullptr) { + return nullptr; + } + return host->metadata().get(); + }) {} + +std::unique_ptr +FilterStateFormatter::create(const std::string& format, const absl::optional& max_length, + bool is_upstream) { + std::string key, serialize_type; + static constexpr absl::string_view PLAIN_SERIALIZATION{"PLAIN"}; + static constexpr absl::string_view TYPED_SERIALIZATION{"TYPED"}; + + SubstitutionFormatParser::parseSubcommand(format, ':', key, serialize_type); + if (key.empty()) { + throw EnvoyException("Invalid filter state configuration, key cannot be empty."); + } + + if (serialize_type.empty()) { + serialize_type = std::string(TYPED_SERIALIZATION); + } + if (serialize_type != PLAIN_SERIALIZATION && serialize_type != TYPED_SERIALIZATION) { + throw EnvoyException("Invalid filter state serialize type, only " + "support PLAIN/TYPED."); + } + + const bool serialize_as_string = serialize_type == PLAIN_SERIALIZATION; + + return std::make_unique(key, max_length, serialize_as_string, is_upstream); +} + +FilterStateFormatter::FilterStateFormatter(const std::string& key, + absl::optional max_length, + bool serialize_as_string, bool is_upstream) + : key_(key), max_length_(max_length), serialize_as_string_(serialize_as_string), + is_upstream_(is_upstream) {} + +const Envoy::StreamInfo::FilterState::Object* +FilterStateFormatter::filterState(const StreamInfo::StreamInfo& stream_info) const { + const StreamInfo::FilterState* filter_state = nullptr; + if (is_upstream_) { + const OptRef upstream_info = stream_info.upstreamInfo(); + if (upstream_info) { + filter_state = upstream_info->upstreamFilterState().get(); + } + } else { + filter_state = &stream_info.filterState(); + } + + if (filter_state) { + return filter_state->getDataReadOnly(key_); + } + + return nullptr; +} + +absl::optional +FilterStateFormatter::format(const StreamInfo::StreamInfo& stream_info) const { + const Envoy::StreamInfo::FilterState::Object* state = filterState(stream_info); + if (!state) { + return absl::nullopt; + } + + if (serialize_as_string_) { + absl::optional plain_value = state->serializeAsString(); + if (plain_value.has_value()) { + SubstitutionFormatUtils::truncate(plain_value.value(), max_length_); + return plain_value.value(); + } + return absl::nullopt; + } + + ProtobufTypes::MessagePtr proto = state->serializeAsProto(); + if (proto == nullptr) { + return absl::nullopt; + } + +#ifdef ENVOY_ENABLE_FULL_PROTOS + std::string value; + const auto status = Protobuf::util::MessageToJsonString(*proto, &value); + if (!status.ok()) { + // If the message contains an unknown Any (from WASM or Lua), MessageToJsonString will fail. + // TODO(lizan): add support of unknown Any. + return absl::nullopt; + } + + SubstitutionFormatUtils::truncate(value, max_length_); + return value; +#else + PANIC("FilterStateFormatter::format requires full proto support"); + return absl::nullopt; +#endif +} + +ProtobufWkt::Value +FilterStateFormatter::formatValue(const StreamInfo::StreamInfo& stream_info) const { + const Envoy::StreamInfo::FilterState::Object* state = filterState(stream_info); + if (!state) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + if (serialize_as_string_) { + absl::optional plain_value = state->serializeAsString(); + if (plain_value.has_value()) { + SubstitutionFormatUtils::truncate(plain_value.value(), max_length_); + return ValueUtil::stringValue(plain_value.value()); + } + return SubstitutionFormatUtils::unspecifiedValue(); + } + + ProtobufTypes::MessagePtr proto = state->serializeAsProto(); + if (!proto) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + +#ifdef ENVOY_ENABLE_YAML + ProtobufWkt::Value val; + if (MessageUtil::jsonConvertValue(*proto, val)) { + return val; + } +#endif + return SubstitutionFormatUtils::unspecifiedValue(); +} + +// A SystemTime formatter that extracts the startTime from StreamInfo. Must be provided +// an access log command that starts with `START_TIME`. +StartTimeFormatter::StartTimeFormatter(const std::string& format) + : SystemTimeFormatter( + format, std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + return stream_info.startTime(); + })) {} + +DownstreamPeerCertVStartFormatter::DownstreamPeerCertVStartFormatter(const std::string& format) + : SystemTimeFormatter( + format, std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + const auto connection_info = + stream_info.downstreamAddressProvider().sslConnection(); + return connection_info != nullptr + ? connection_info->validFromPeerCertificate() + : absl::optional(); + })) {} +DownstreamPeerCertVEndFormatter::DownstreamPeerCertVEndFormatter(const std::string& format) + : SystemTimeFormatter( + format, std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + const auto connection_info = + stream_info.downstreamAddressProvider().sslConnection(); + return connection_info != nullptr + ? connection_info->expirationPeerCertificate() + : absl::optional(); + })) {} +UpstreamPeerCertVStartFormatter::UpstreamPeerCertVStartFormatter(const std::string& format) + : SystemTimeFormatter( + format, std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + return stream_info.upstreamInfo() && + stream_info.upstreamInfo()->upstreamSslConnection() != + nullptr + ? stream_info.upstreamInfo() + ->upstreamSslConnection() + ->validFromPeerCertificate() + : absl::optional(); + })) {} +UpstreamPeerCertVEndFormatter::UpstreamPeerCertVEndFormatter(const std::string& format) + : SystemTimeFormatter( + format, std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + return stream_info.upstreamInfo() && + stream_info.upstreamInfo()->upstreamSslConnection() != + nullptr + ? stream_info.upstreamInfo() + ->upstreamSslConnection() + ->expirationPeerCertificate() + : absl::optional(); + })) {} + +SystemTimeFormatter::SystemTimeFormatter(const std::string& format, TimeFieldExtractorPtr f) + : date_formatter_(format), time_field_extractor_(std::move(f)) { + // Validate the input specifier here. The formatted string may be destined for a header, and + // should not contain invalid characters {NUL, LR, CF}. + if (std::regex_search(format, getSystemTimeFormatNewlinePattern())) { + throw EnvoyException("Invalid header configuration. Format string contains newline."); + } +} + +absl::optional +SystemTimeFormatter::format(const StreamInfo::StreamInfo& stream_info) const { + const auto time_field = (*time_field_extractor_)(stream_info); + if (!time_field.has_value()) { + return absl::nullopt; + } + if (date_formatter_.formatString().empty()) { + return AccessLogDateTimeFormatter::fromTime(time_field.value()); + } + return date_formatter_.fromTime(time_field.value()); +} + +ProtobufWkt::Value +SystemTimeFormatter::formatValue(const StreamInfo::StreamInfo& stream_info) const { + return ValueUtil::optionalStringValue(format(stream_info)); +} + +EnvironmentFormatter::EnvironmentFormatter(const std::string& key, + absl::optional max_length) { + ASSERT(!key.empty()); + + const char* env_value = std::getenv(key.c_str()); + if (env_value != nullptr) { + std::string env_string = env_value; + SubstitutionFormatUtils::truncate(env_string, max_length); + str_.set_string_value(env_string); + return; + } + str_.set_string_value(DefaultUnspecifiedValueString); +} + +absl::optional EnvironmentFormatter::format(const StreamInfo::StreamInfo&) const { + return str_.string_value(); +} +ProtobufWkt::Value EnvironmentFormatter::formatValue(const StreamInfo::StreamInfo&) const { + return str_; +} + +// StreamInfo std::string formatter provider. +class StreamInfoStringFormatterProvider : public StreamInfoFormatterProvider { +public: + using FieldExtractor = std::function(const StreamInfo::StreamInfo&)>; + + StreamInfoStringFormatterProvider(FieldExtractor f) : field_extractor_(f) {} + + // StreamInfoFormatterProvider + absl::optional format(const StreamInfo::StreamInfo& stream_info) const override { + return field_extractor_(stream_info); + } + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo& stream_info) const override { + return ValueUtil::optionalStringValue(field_extractor_(stream_info)); + } + +private: + FieldExtractor field_extractor_; +}; + +// StreamInfo std::chrono_nanoseconds field extractor. +class StreamInfoDurationFormatterProvider : public StreamInfoFormatterProvider { +public: + using FieldExtractor = + std::function(const StreamInfo::StreamInfo&)>; + + StreamInfoDurationFormatterProvider(FieldExtractor f) : field_extractor_(f) {} + + // StreamInfoFormatterProvider + absl::optional format(const StreamInfo::StreamInfo& stream_info) const override { + const auto millis = extractMillis(stream_info); + if (!millis) { + return absl::nullopt; + } + + return fmt::format_int(millis.value()).str(); + } + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo& stream_info) const override { + const auto millis = extractMillis(stream_info); + if (!millis) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + return ValueUtil::numberValue(millis.value()); + } + +private: + absl::optional extractMillis(const StreamInfo::StreamInfo& stream_info) const { + const auto time = field_extractor_(stream_info); + if (time) { + return std::chrono::duration_cast(time.value()).count(); + } + return absl::nullopt; + } + + FieldExtractor field_extractor_; +}; + +// StreamInfo uint64_t field extractor. +class StreamInfoUInt64FormatterProvider : public StreamInfoFormatterProvider { +public: + using FieldExtractor = std::function; + + StreamInfoUInt64FormatterProvider(FieldExtractor f) : field_extractor_(f) {} + + // StreamInfoFormatterProvider + absl::optional format(const StreamInfo::StreamInfo& stream_info) const override { + return fmt::format_int(field_extractor_(stream_info)).str(); + } + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo& stream_info) const override { + return ValueUtil::numberValue(field_extractor_(stream_info)); + } + +private: + FieldExtractor field_extractor_; +}; + +// StreamInfo Envoy::Network::Address::InstanceConstSharedPtr field extractor. +class StreamInfoAddressFormatterProvider : public StreamInfoFormatterProvider { +public: + using FieldExtractor = + std::function; + + static std::unique_ptr withPort(FieldExtractor f) { + return std::make_unique( + f, StreamInfoAddressFieldExtractionType::WithPort); + } + + static std::unique_ptr withoutPort(FieldExtractor f) { + return std::make_unique( + f, StreamInfoAddressFieldExtractionType::WithoutPort); + } + + static std::unique_ptr justPort(FieldExtractor f) { + return std::make_unique( + f, StreamInfoAddressFieldExtractionType::JustPort); + } + + StreamInfoAddressFormatterProvider(FieldExtractor f, + StreamInfoAddressFieldExtractionType extraction_type) + : field_extractor_(f), extraction_type_(extraction_type) {} + + // StreamInfoFormatterProvider + absl::optional format(const StreamInfo::StreamInfo& stream_info) const override { + Network::Address::InstanceConstSharedPtr address = field_extractor_(stream_info); + if (!address) { + return absl::nullopt; + } + + return toString(*address); + } + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo& stream_info) const override { + Network::Address::InstanceConstSharedPtr address = field_extractor_(stream_info); + if (!address) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.format_ports_as_numbers")) { + if (extraction_type_ == StreamInfoAddressFieldExtractionType::JustPort) { + const auto port = StreamInfo::Utility::extractDownstreamAddressJustPort(*address); + if (port) { + return ValueUtil::numberValue(*port); + } + return SubstitutionFormatUtils::unspecifiedValue(); + } + } + return ValueUtil::stringValue(toString(*address)); + } + +private: + std::string toString(const Network::Address::Instance& address) const { + switch (extraction_type_) { + case StreamInfoAddressFieldExtractionType::WithoutPort: + return StreamInfo::Utility::formatDownstreamAddressNoPort(address); + case StreamInfoAddressFieldExtractionType::JustPort: + return StreamInfo::Utility::formatDownstreamAddressJustPort(address); + case StreamInfoAddressFieldExtractionType::WithPort: + default: + return address.asString(); + } + } + + FieldExtractor field_extractor_; + const StreamInfoAddressFieldExtractionType extraction_type_; +}; + +// Ssl::ConnectionInfo std::string field extractor. +class StreamInfoSslConnectionInfoFormatterProvider : public StreamInfoFormatterProvider { +public: + using FieldExtractor = + std::function(const Ssl::ConnectionInfo& connection_info)>; + + StreamInfoSslConnectionInfoFormatterProvider(FieldExtractor f) : field_extractor_(f) {} + + absl::optional format(const StreamInfo::StreamInfo& stream_info) const override { + if (stream_info.downstreamAddressProvider().sslConnection() == nullptr) { + return absl::nullopt; + } + + const auto value = field_extractor_(*stream_info.downstreamAddressProvider().sslConnection()); + if (value && value->empty()) { + return absl::nullopt; + } + + return value; + } + + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo& stream_info) const override { + if (stream_info.downstreamAddressProvider().sslConnection() == nullptr) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + const auto value = field_extractor_(*stream_info.downstreamAddressProvider().sslConnection()); + if (value && value->empty()) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + return ValueUtil::optionalStringValue(value); + } + +private: + FieldExtractor field_extractor_; +}; + +class StreamInfoUpstreamSslConnectionInfoFormatterProvider : public StreamInfoFormatterProvider { +public: + using FieldExtractor = + std::function(const Ssl::ConnectionInfo& connection_info)>; + + StreamInfoUpstreamSslConnectionInfoFormatterProvider(FieldExtractor f) : field_extractor_(f) {} + + absl::optional format(const StreamInfo::StreamInfo& stream_info) const override { + if (!stream_info.upstreamInfo() || + stream_info.upstreamInfo()->upstreamSslConnection() == nullptr) { + return absl::nullopt; + } + + const auto value = field_extractor_(*(stream_info.upstreamInfo()->upstreamSslConnection())); + if (value && value->empty()) { + return absl::nullopt; + } + + return value; + } + + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo& stream_info) const override { + if (!stream_info.upstreamInfo() || + stream_info.upstreamInfo()->upstreamSslConnection() == nullptr) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + const auto value = field_extractor_(*(stream_info.upstreamInfo()->upstreamSslConnection())); + if (value && value->empty()) { + return SubstitutionFormatUtils::unspecifiedValue(); + } + + return ValueUtil::optionalStringValue(value); + } + +private: + FieldExtractor field_extractor_; +}; + +const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProviders() { + CONSTRUCT_ON_FIRST_USE( + StreamInfoFormatterProviderLookupTable, + { + {"REQUEST_DURATION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + StreamInfo::TimingUtility timing(stream_info); + return timing.lastDownstreamRxByteReceived(); + }); + }}}, + {"REQUEST_TX_DURATION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + StreamInfo::TimingUtility timing(stream_info); + return timing.lastUpstreamTxByteSent(); + }); + }}}, + {"RESPONSE_DURATION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + StreamInfo::TimingUtility timing(stream_info); + return timing.firstUpstreamRxByteReceived(); + }); + }}}, + {"RESPONSE_TX_DURATION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + StreamInfo::TimingUtility timing(stream_info); + auto downstream = timing.lastDownstreamTxByteSent(); + auto upstream = timing.firstUpstreamRxByteReceived(); + + absl::optional result; + if (downstream && upstream) { + result = downstream.value() - upstream.value(); + } + + return result; + }); + }}}, + {"DOWNSTREAM_HANDSHAKE_DURATION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + StreamInfo::TimingUtility timing(stream_info); + return timing.downstreamHandshakeComplete(); + }); + }}}, + {"ROUNDTRIP_DURATION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + StreamInfo::TimingUtility timing(stream_info); + return timing.lastDownstreamAckReceived(); + }); + }}}, + {"BYTES_RECEIVED", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.bytesReceived(); + }); + }}}, + {"BYTES_RETRANSMITTED", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.bytesRetransmitted(); + }); + }}}, + {"PACKETS_RETRANSMITTED", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.packetsRetransmitted(); + }); + }}}, + {"UPSTREAM_WIRE_BYTES_RECEIVED", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + const auto& bytes_meter = stream_info.getUpstreamBytesMeter(); + return bytes_meter ? bytes_meter->wireBytesReceived() : 0; + }); + }}}, + {"UPSTREAM_HEADER_BYTES_RECEIVED", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + const auto& bytes_meter = stream_info.getUpstreamBytesMeter(); + return bytes_meter ? bytes_meter->headerBytesReceived() : 0; + }); + }}}, + {"DOWNSTREAM_WIRE_BYTES_RECEIVED", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + const auto& bytes_meter = stream_info.getDownstreamBytesMeter(); + return bytes_meter ? bytes_meter->wireBytesReceived() : 0; + }); + }}}, + {"DOWNSTREAM_HEADER_BYTES_RECEIVED", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + const auto& bytes_meter = stream_info.getDownstreamBytesMeter(); + return bytes_meter ? bytes_meter->headerBytesReceived() : 0; + }); + }}}, + {"PROTOCOL", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return SubstitutionFormatUtils::protocolToString(stream_info.protocol()); + }); + }}}, + {"UPSTREAM_PROTOCOL", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.upstreamInfo() + ? SubstitutionFormatUtils::protocolToString( + stream_info.upstreamInfo()->upstreamProtocol()) + : absl::nullopt; + }); + }}}, + {"RESPONSE_CODE", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.responseCode().value_or(0); + }); + }}}, + {"RESPONSE_CODE_DETAILS", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.responseCodeDetails(); + }); + }}}, + {"CONNECTION_TERMINATION_DETAILS", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.connectionTerminationDetails(); + }); + }}}, + {"BYTES_SENT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.bytesSent(); + }); + }}}, + {"UPSTREAM_WIRE_BYTES_SENT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + const auto& bytes_meter = stream_info.getUpstreamBytesMeter(); + return bytes_meter ? bytes_meter->wireBytesSent() : 0; + }); + }}}, + {"UPSTREAM_HEADER_BYTES_SENT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + const auto& bytes_meter = stream_info.getUpstreamBytesMeter(); + return bytes_meter ? bytes_meter->headerBytesSent() : 0; + }); + }}}, + {"DOWNSTREAM_WIRE_BYTES_SENT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + const auto& bytes_meter = stream_info.getDownstreamBytesMeter(); + return bytes_meter ? bytes_meter->wireBytesSent() : 0; + }); + }}}, + {"DOWNSTREAM_HEADER_BYTES_SENT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + const auto& bytes_meter = stream_info.getDownstreamBytesMeter(); + return bytes_meter ? bytes_meter->headerBytesSent() : 0; + }); + }}}, + {"DURATION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.currentDuration(); + }); + }}}, + {"RESPONSE_FLAGS", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return StreamInfo::ResponseFlagUtils::toShortString(stream_info); + }); + }}}, + {"RESPONSE_FLAGS_LONG", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return StreamInfo::ResponseFlagUtils::toString(stream_info); + }); + }}}, + {"UPSTREAM_HOST", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withPort( + [](const StreamInfo::StreamInfo& stream_info) + -> std::shared_ptr { + if (stream_info.upstreamInfo() && stream_info.upstreamInfo()->upstreamHost()) { + return stream_info.upstreamInfo()->upstreamHost()->address(); + } + return nullptr; + }); + }}}, + {"UPSTREAM_CLUSTER", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + std::string upstream_cluster_name; + if (stream_info.upstreamClusterInfo().has_value() && + stream_info.upstreamClusterInfo().value() != nullptr) { + upstream_cluster_name = + stream_info.upstreamClusterInfo().value()->observabilityName(); + } + + return upstream_cluster_name.empty() + ? absl::nullopt + : absl::make_optional(upstream_cluster_name); + }); + }}}, + {"UPSTREAM_LOCAL_ADDRESS", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withPort( + [](const StreamInfo::StreamInfo& stream_info) + -> std::shared_ptr { + if (stream_info.upstreamInfo().has_value()) { + return stream_info.upstreamInfo().value().get().upstreamLocalAddress(); + } + return nullptr; + }); + }}}, + {"UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withoutPort( + [](const StreamInfo::StreamInfo& stream_info) + -> std::shared_ptr { + if (stream_info.upstreamInfo().has_value()) { + return stream_info.upstreamInfo().value().get().upstreamLocalAddress(); + } + return nullptr; + }); + }}}, + {"UPSTREAM_LOCAL_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::justPort( + [](const StreamInfo::StreamInfo& stream_info) + -> std::shared_ptr { + if (stream_info.upstreamInfo().has_value()) { + return stream_info.upstreamInfo().value().get().upstreamLocalAddress(); + } + return nullptr; + }); + }}}, + {"UPSTREAM_REMOTE_ADDRESS", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withPort( + [](const StreamInfo::StreamInfo& stream_info) + -> std::shared_ptr { + if (stream_info.upstreamInfo() && stream_info.upstreamInfo()->upstreamHost()) { + return stream_info.upstreamInfo()->upstreamHost()->address(); + } + return nullptr; + }); + }}}, + {"UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withoutPort( + [](const StreamInfo::StreamInfo& stream_info) + -> std::shared_ptr { + if (stream_info.upstreamInfo() && stream_info.upstreamInfo()->upstreamHost()) { + return stream_info.upstreamInfo()->upstreamHost()->address(); + } + return nullptr; + }); + }}}, + {"UPSTREAM_REMOTE_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::justPort( + [](const StreamInfo::StreamInfo& stream_info) + -> std::shared_ptr { + if (stream_info.upstreamInfo() && stream_info.upstreamInfo()->upstreamHost()) { + return stream_info.upstreamInfo()->upstreamHost()->address(); + } + return nullptr; + }); + }}}, + {"UPSTREAM_REQUEST_ATTEMPT_COUNT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.attemptCount().value_or(0); + }); + }}}, + {"UPSTREAM_TLS_CIPHER", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.ciphersuiteString(); + }); + }}}, + {"UPSTREAM_TLS_VERSION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.tlsVersion(); + }); + }}}, + {"UPSTREAM_TLS_SESSION_ID", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.sessionId(); + }); + }}}, + {"UPSTREAM_PEER_ISSUER", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.issuerPeerCertificate(); + }); + }}}, + {"UPSTREAM_PEER_CERT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.urlEncodedPemEncodedPeerCertificate(); + }); + }}}, + {"UPSTREAM_PEER_SUBJECT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.subjectPeerCertificate(); + }); + }}}, + {"DOWNSTREAM_LOCAL_ADDRESS", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withPort( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().localAddress(); + }); + }}}, + {"DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withoutPort( + [](const Envoy::StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().localAddress(); + }); + }}}, + {"DOWNSTREAM_LOCAL_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::justPort( + [](const Envoy::StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().localAddress(); + }); + }}}, + {"DOWNSTREAM_REMOTE_ADDRESS", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withPort( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().remoteAddress(); + }); + }}}, + {"DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withoutPort( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().remoteAddress(); + }); + }}}, + {"DOWNSTREAM_REMOTE_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::justPort( + [](const Envoy::StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().remoteAddress(); + }); + }}}, + {"DOWNSTREAM_DIRECT_REMOTE_ADDRESS", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withPort( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().directRemoteAddress(); + }); + }}}, + {"DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::withoutPort( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().directRemoteAddress(); + }); + }}}, + {"DOWNSTREAM_DIRECT_REMOTE_PORT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return StreamInfoAddressFormatterProvider::justPort( + [](const Envoy::StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().directRemoteAddress(); + }); + }}}, + {"CONNECTION_ID", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + return stream_info.downstreamAddressProvider().connectionID().value_or(0); + }); + }}}, + {"REQUESTED_SERVER_NAME", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + absl::optional result; + if (!stream_info.downstreamAddressProvider().requestedServerName().empty()) { + result = std::string( + stream_info.downstreamAddressProvider().requestedServerName()); + } + return result; + }); + }}}, + {"ROUTE_NAME", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + absl::optional result; + std::string route_name = stream_info.getRouteName(); + if (!route_name.empty()) { + result = route_name; + } + return result; + }); + }}}, + {"DOWNSTREAM_PEER_URI_SAN", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return absl::StrJoin(connection_info.uriSanPeerCertificate(), ","); + }); + }}}, + {"DOWNSTREAM_PEER_DNS_SAN", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return absl::StrJoin(connection_info.dnsSansPeerCertificate(), ","); + }); + }}}, + {"DOWNSTREAM_PEER_IP_SAN", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return absl::StrJoin(connection_info.ipSansPeerCertificate(), ","); + }); + }}}, + {"DOWNSTREAM_LOCAL_URI_SAN", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return absl::StrJoin(connection_info.uriSanLocalCertificate(), ","); + }); + }}}, + {"DOWNSTREAM_LOCAL_DNS_SAN", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return absl::StrJoin(connection_info.dnsSansLocalCertificate(), ","); + }); + }}}, + {"DOWNSTREAM_LOCAL_IP_SAN", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return absl::StrJoin(connection_info.ipSansLocalCertificate(), ","); + }); + }}}, + {"DOWNSTREAM_PEER_SUBJECT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.subjectPeerCertificate(); + }); + }}}, + {"DOWNSTREAM_LOCAL_SUBJECT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.subjectLocalCertificate(); + }); + }}}, + {"DOWNSTREAM_TLS_SESSION_ID", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.sessionId(); + }); + }}}, + {"DOWNSTREAM_TLS_CIPHER", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.ciphersuiteString(); + }); + }}}, + {"DOWNSTREAM_TLS_VERSION", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.tlsVersion(); + }); + }}}, + {"DOWNSTREAM_PEER_FINGERPRINT_256", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.sha256PeerCertificateDigest(); + }); + }}}, + {"DOWNSTREAM_PEER_FINGERPRINT_1", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.sha1PeerCertificateDigest(); + }); + }}}, + {"DOWNSTREAM_PEER_SERIAL", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.serialNumberPeerCertificate(); + }); + }}}, + {"DOWNSTREAM_PEER_ISSUER", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.issuerPeerCertificate(); + }); + }}}, + {"DOWNSTREAM_PEER_CERT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const Ssl::ConnectionInfo& connection_info) { + return connection_info.urlEncodedPemEncodedPeerCertificate(); + }); + }}}, + {"DOWNSTREAM_TRANSPORT_FAILURE_REASON", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + absl::optional result; + if (!stream_info.downstreamTransportFailureReason().empty()) { + result = absl::StrReplaceAll(stream_info.downstreamTransportFailureReason(), + {{" ", "_"}}); + } + return result; + }); + }}}, + {"UPSTREAM_TRANSPORT_FAILURE_REASON", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + absl::optional result; + if (stream_info.upstreamInfo().has_value() && + !stream_info.upstreamInfo() + .value() + .get() + .upstreamTransportFailureReason() + .empty()) { + result = + stream_info.upstreamInfo().value().get().upstreamTransportFailureReason(); + } + if (result) { + std::replace(result->begin(), result->end(), ' ', '_'); + } + return result; + }); + }}}, + {"HOSTNAME", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + absl::optional hostname = SubstitutionFormatUtils::getHostname(); + return std::make_unique( + [hostname](const StreamInfo::StreamInfo&) { return hostname; }); + }}}, + {"FILTER_CHAIN_NAME", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + if (!stream_info.filterChainName().empty()) { + return stream_info.filterChainName(); + } + return absl::nullopt; + }); + }}}, + {"VIRTUAL_CLUSTER_NAME", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + return stream_info.virtualClusterName(); + }); + }}}, + {"TLS_JA3_FINGERPRINT", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) { + absl::optional result; + if (!stream_info.downstreamAddressProvider().ja3Hash().empty()) { + result = std::string(stream_info.downstreamAddressProvider().ja3Hash()); + } + return result; + }); + }}}, + {"STREAM_ID", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + auto provider = stream_info.getStreamIdProvider(); + if (!provider.has_value()) { + return {}; + } + auto id = provider->toStringView(); + if (!id.has_value()) { + return {}; + } + return absl::make_optional(id.value()); + }); + }}}, + {"START_TIME", + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](const std::string& format, absl::optional) { + return std::make_unique( + format, + std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + return stream_info.startTime(); + })); + }}}, + {"DYNAMIC_METADATA", + {CommandSyntaxChecker::PARAMS_REQUIRED, + [](const std::string& format, absl::optional max_length) { + std::string filter_namespace; + std::vector path; + + SubstitutionFormatParser::parseSubcommand(format, ':', filter_namespace, path); + return std::make_unique(filter_namespace, path, max_length); + }}}, + + {"CLUSTER_METADATA", + {CommandSyntaxChecker::PARAMS_REQUIRED, + [](const std::string& format, absl::optional max_length) { + std::string filter_namespace; + std::vector path; + + SubstitutionFormatParser::parseSubcommand(format, ':', filter_namespace, path); + return std::make_unique(filter_namespace, path, max_length); + }}}, + {"UPSTREAM_METADATA", + {CommandSyntaxChecker::PARAMS_REQUIRED, + [](const std::string& format, absl::optional max_length) { + std::string filter_namespace; + std::vector path; + + SubstitutionFormatParser::parseSubcommand(format, ':', filter_namespace, path); + return std::make_unique(filter_namespace, path, + max_length); + }}}, + {"FILTER_STATE", + {CommandSyntaxChecker::PARAMS_OPTIONAL | CommandSyntaxChecker::LENGTH_ALLOWED, + [](const std::string& format, absl::optional max_length) { + return FilterStateFormatter::create(format, max_length, false); + }}}, + {"UPSTREAM_FILTER_STATE", + {CommandSyntaxChecker::PARAMS_OPTIONAL | CommandSyntaxChecker::LENGTH_ALLOWED, + [](const std::string& format, absl::optional max_length) { + return FilterStateFormatter::create(format, max_length, true); + }}}, + {"DOWNSTREAM_PEER_CERT_V_START", + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](const std::string& format, absl::optional) { + return std::make_unique(format); + }}}, + {"DOWNSTREAM_PEER_CERT_V_END", + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](const std::string& format, absl::optional) { + return std::make_unique(format); + }}}, + {"UPSTREAM_PEER_CERT_V_START", + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](const std::string& format, absl::optional) { + return std::make_unique(format); + }}}, + {"UPSTREAM_PEER_CERT_V_END", + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](const std::string& format, absl::optional) { + return std::make_unique(format); + }}}, + {"ENVIRONMENT", + {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, + [](const std::string& key, absl::optional max_length) { + return std::make_unique(key, max_length); + }}}, + }); +} + +PlainStringFormatter::PlainStringFormatter(const std::string& str) { str_.set_string_value(str); } + +absl::optional +PlainStringFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const { + return str_.string_value(); +} + +ProtobufWkt::Value +PlainStringFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const { + return str_; +} + +PlainNumberFormatter::PlainNumberFormatter(double num) { num_.set_number_value(num); } + +absl::optional +PlainNumberFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const { + std::string str = absl::StrFormat("%g", num_.number_value()); + return str; +} + +ProtobufWkt::Value +PlainNumberFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const { + return num_; +} + +StreamInfoFormatter::StreamInfoFormatter(const std::string& command, const std::string& subcommand, + absl::optional length) { + const auto& formatters = getKnownStreamInfoFormatterProviders(); + + auto it = formatters.find(command); + + if (it == formatters.end()) { + throw EnvoyException(fmt::format("Not supported field in StreamInfo: {}", command)); + } + + // Check flags for the command. + CommandSyntaxChecker::verifySyntax((*it).second.first, command, subcommand, length); + + // Create a pointer to the formatter by calling a function + // associated with formatter's name. + formatter_ = (*it).second.second(subcommand, length); +} + +absl::optional StreamInfoFormatter::format( + const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { + return formatter_->format(stream_info); +} + +ProtobufWkt::Value StreamInfoFormatter::formatValue( + const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, + const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { + return formatter_->formatValue(stream_info); +} + +} // namespace Formatter +} // namespace Envoy diff --git a/source/common/formatter/stream_info_formatter.h b/source/common/formatter/stream_info_formatter.h new file mode 100644 index 000000000000..5ae0d3fe7ab3 --- /dev/null +++ b/source/common/formatter/stream_info_formatter.h @@ -0,0 +1,263 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "envoy/formatter/substitution_formatter.h" +#include "envoy/stream_info/stream_info.h" + +#include "source/common/common/utility.h" +#include "source/common/formatter/substitution_formatter.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Formatter { + +class StreamInfoFormatterProvider { +public: + virtual ~StreamInfoFormatterProvider() = default; + + virtual absl::optional format(const StreamInfo::StreamInfo&) const PURE; + virtual ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo&) const PURE; +}; + +using StreamInfoFormatterProviderPtr = std::unique_ptr; +using StreamInfoFormatterProviderCreateFunc = + std::function)>; + +enum class StreamInfoAddressFieldExtractionType { WithPort, WithoutPort, JustPort }; + +/** + * Base formatter for formatting Metadata objects + */ +class MetadataFormatter : public StreamInfoFormatterProvider { +public: + using GetMetadataFunction = + std::function; + MetadataFormatter(const std::string& filter_namespace, const std::vector& path, + absl::optional max_length, GetMetadataFunction get); + + // StreamInfoFormatterProvider + absl::optional format(const StreamInfo::StreamInfo& stream_info) const override; + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo& stream_info) const override; + +protected: + absl::optional + formatMetadata(const envoy::config::core::v3::Metadata& metadata) const; + ProtobufWkt::Value formatMetadataValue(const envoy::config::core::v3::Metadata& metadata) const; + +private: + std::string filter_namespace_; + std::vector path_; + absl::optional max_length_; + GetMetadataFunction get_func_; +}; + +/** + * FormatterProvider for DynamicMetadata from StreamInfo. + */ +class DynamicMetadataFormatter : public MetadataFormatter { +public: + DynamicMetadataFormatter(const std::string& filter_namespace, + const std::vector& path, absl::optional max_length); +}; + +/** + * FormatterProvider for ClusterMetadata from StreamInfo. + */ +class ClusterMetadataFormatter : public MetadataFormatter { +public: + ClusterMetadataFormatter(const std::string& filter_namespace, + const std::vector& path, absl::optional max_length); +}; + +/** + * FormatterProvider for UpstreamHostMetadata from StreamInfo. + */ +class UpstreamHostMetadataFormatter : public MetadataFormatter { +public: + UpstreamHostMetadataFormatter(const std::string& filter_namespace, + const std::vector& path, + absl::optional max_length); +}; + +/** + * StreamInfoFormatterProvider for FilterState from StreamInfo. + */ +class FilterStateFormatter : public StreamInfoFormatterProvider { +public: + static std::unique_ptr + create(const std::string& format, const absl::optional& max_length, bool is_upstream); + + FilterStateFormatter(const std::string& key, absl::optional max_length, + bool serialize_as_string, bool is_upstream = false); + + // StreamInfoFormatterProvider + absl::optional format(const StreamInfo::StreamInfo&) const override; + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo&) const override; + +private: + const Envoy::StreamInfo::FilterState::Object* + filterState(const StreamInfo::StreamInfo& stream_info) const; + + std::string key_; + absl::optional max_length_; + + bool serialize_as_string_; + const bool is_upstream_; +}; + +/** + * Base StreamInfoFormatterProvider for system times from StreamInfo. + */ +class SystemTimeFormatter : public StreamInfoFormatterProvider { +public: + using TimeFieldExtractor = + std::function(const StreamInfo::StreamInfo& stream_info)>; + using TimeFieldExtractorPtr = std::unique_ptr; + + SystemTimeFormatter(const std::string& format, TimeFieldExtractorPtr f); + + // StreamInfoFormatterProvider + absl::optional format(const StreamInfo::StreamInfo&) const override; + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo&) const override; + +private: + const Envoy::DateFormatter date_formatter_; + const TimeFieldExtractorPtr time_field_extractor_; +}; + +/** + * SystemTimeFormatter (FormatterProvider) for request start time from StreamInfo. + */ +class StartTimeFormatter : public SystemTimeFormatter { +public: + StartTimeFormatter(const std::string& format); +}; + +/** + * SystemTimeFormatter (FormatterProvider) for downstream cert start time from the StreamInfo's + * ConnectionInfo. + */ +class DownstreamPeerCertVStartFormatter : public SystemTimeFormatter { +public: + DownstreamPeerCertVStartFormatter(const std::string& format); +}; + +/** + * SystemTimeFormatter (FormatterProvider) for downstream cert end time from the StreamInfo's + * ConnectionInfo. + */ +class DownstreamPeerCertVEndFormatter : public SystemTimeFormatter { +public: + DownstreamPeerCertVEndFormatter(const std::string& format); +}; + +/** + * SystemTimeFormatter (FormatterProvider) for upstream cert start time from the StreamInfo's + * upstreamInfo. + */ +class UpstreamPeerCertVStartFormatter : public SystemTimeFormatter { +public: + UpstreamPeerCertVStartFormatter(const std::string& format); +}; + +/** + * SystemTimeFormatter (FormatterProvider) for upstream cert end time from the StreamInfo's + * upstreamInfo. + */ +class UpstreamPeerCertVEndFormatter : public SystemTimeFormatter { +public: + UpstreamPeerCertVEndFormatter(const std::string& format); +}; + +/** + * FormatterProvider for environment. If no valid environment value then + */ +class EnvironmentFormatter : public StreamInfoFormatterProvider { +public: + EnvironmentFormatter(const std::string& key, absl::optional max_length); + + // StreamInfoFormatterProvider + absl::optional format(const StreamInfo::StreamInfo&) const override; + ProtobufWkt::Value formatValue(const StreamInfo::StreamInfo&) const override; + +private: + ProtobufWkt::Value str_; +}; + +using StreamInfoFormatterProviderLookupTable = + absl::flat_hash_map>; +const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProviders(); + +/** + * FormatterProvider for string literals. It ignores headers and stream info and returns string by + * which it was initialized. + */ +class PlainStringFormatter : public FormatterProvider { +public: + PlainStringFormatter(const std::string& str); + + // FormatterProvider + absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + +private: + ProtobufWkt::Value str_; +}; + +/** + * FormatterProvider for numbers. + */ +class PlainNumberFormatter : public FormatterProvider { +public: + PlainNumberFormatter(double num); + + // FormatterProvider + absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + +private: + ProtobufWkt::Value num_; +}; + +/** + * FormatterProvider based on StreamInfo fields. + */ +class StreamInfoFormatter : public FormatterProvider { +public: + StreamInfoFormatter(const std::string&, const std::string& = "", + absl::optional = absl::nullopt); + + StreamInfoFormatter(StreamInfoFormatterProviderPtr formatter) + : formatter_(std::move(formatter)) {} + + // FormatterProvider + absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, + const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, + absl::string_view, AccessLog::AccessLogType) const override; + +private: + StreamInfoFormatterProviderPtr formatter_; +}; + +} // namespace Formatter +} // namespace Envoy diff --git a/source/common/formatter/substitution_formatter.cc b/source/common/formatter/substitution_formatter.cc index dd35a096fcb0..90d1018c6d29 100644 --- a/source/common/formatter/substitution_formatter.cc +++ b/source/common/formatter/substitution_formatter.cc @@ -13,52 +13,29 @@ #include "envoy/config/core/v3/base.pb.h" #include "envoy/upstream/upstream.h" +#include "source/common//formatter/http_specific_formatter.h" #include "source/common/api/os_sys_calls_impl.h" #include "source/common/common/assert.h" #include "source/common/common/empty_string.h" #include "source/common/common/fmt.h" #include "source/common/common/thread.h" #include "source/common/common/utility.h" -#include "source/common/config/metadata.h" -#include "source/common/grpc/common.h" -#include "source/common/grpc/status.h" +#include "source/common/formatter/http_specific_formatter.h" +#include "source/common/formatter/stream_info_formatter.h" #include "source/common/http/utility.h" #include "source/common/protobuf/message_validator_impl.h" #include "source/common/protobuf/utility.h" -#include "source/common/runtime/runtime_features.h" -#include "source/common/stream_info/utility.h" #include "absl/strings/str_format.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" #include "fmt/format.h" -using Envoy::Config::Metadata; - namespace Envoy { namespace Formatter { static const std::string DefaultUnspecifiedValueString = "-"; -namespace { - -const ProtobufWkt::Value& unspecifiedValue() { return ValueUtil::nullValue(); } - -void truncate(std::string& str, absl::optional max_length) { - if (!max_length) { - return; - } - - str = str.substr(0, max_length.value()); -} - -// Matches newline pattern in a system time format string (e.g. start time) -const std::regex& getSystemTimeFormatNewlinePattern() { - CONSTRUCT_ON_FIRST_USE(std::regex, "%[-_0^#]*[1-9]*(E|O)?n"); -} - -} // namespace - const std::string SubstitutionFormatUtils::DEFAULT_FORMAT = "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" " "%RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% " @@ -66,6 +43,22 @@ const std::string SubstitutionFormatUtils::DEFAULT_FORMAT = "\"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" " "\"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\"\n"; +void CommandSyntaxChecker::verifySyntax(CommandSyntaxFlags flags, const std::string& command, + const std::string& subcommand, + const absl::optional& length) { + if ((flags == COMMAND_ONLY) && ((subcommand.length() != 0) || length.has_value())) { + throw EnvoyException(fmt::format("{} does not take any parameters or length", command)); + } + + if ((flags & PARAMS_REQUIRED).any() && (subcommand.length() == 0)) { + throw EnvoyException(fmt::format("{} requires parameters", command)); + } + + if ((flags & LENGTH_ALLOWED).none() && length.has_value()) { + throw EnvoyException(fmt::format("{} does not allow length to be specified.", command)); + } +} + FormatterPtr SubstitutionFormatUtils::defaultSubstitutionFormatter() { return std::make_unique(DEFAULT_FORMAT, false); } @@ -113,6 +106,20 @@ const std::string SubstitutionFormatUtils::getHostnameOrDefault() { return DefaultUnspecifiedValueString; } +const ProtobufWkt::Value& SubstitutionFormatUtils::unspecifiedValue() { + return ValueUtil::nullValue(); +} + +void SubstitutionFormatUtils::truncate(std::string& str, absl::optional max_length) { + if (!max_length) { + return; + } + + if (str.length() > max_length.value()) { + str.resize(max_length.value()); + } +} + FormatterImpl::FormatterImpl(const std::string& format, bool omit_empty_values) : empty_value_string_(omit_empty_values ? EMPTY_STRING : DefaultUnspecifiedValueString) { providers_ = SubstitutionFormatParser::parse(format); @@ -357,187 +364,6 @@ std::vector SubstitutionFormatParser::parse(const std::str return SubstitutionFormatParser::parse(format, {}); } -const SubstitutionFormatParser::FormatterProviderLookupTbl& -SubstitutionFormatParser::getKnownFormatters() { - CONSTRUCT_ON_FIRST_USE( - FormatterProviderLookupTbl, - {{"REQ", - {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, - [](const std::string& format, absl::optional& max_length) { - std::string main_header, alternative_header; - - parseSubcommandHeaders(format, main_header, alternative_header); - - return std::make_unique(main_header, alternative_header, - max_length); - }}}, - {"RESP", - {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, - [](const std::string& format, absl::optional& max_length) { - std::string main_header, alternative_header; - - parseSubcommandHeaders(format, main_header, alternative_header); - - return std::make_unique(main_header, alternative_header, - max_length); - }}}, - {"TRAILER", - {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, - [](const std::string& format, absl::optional& max_length) { - std::string main_header, alternative_header; - - parseSubcommandHeaders(format, main_header, alternative_header); - - return std::make_unique(main_header, alternative_header, - max_length); - }}}, - {"LOCAL_REPLY_BODY", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, absl::optional&) { - return std::make_unique(); - }}}, - {"ACCESS_LOG_TYPE", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, absl::optional&) { - return std::make_unique(); - }}}, - {"GRPC_STATUS", - {CommandSyntaxChecker::PARAMS_OPTIONAL, - [](const std::string& format, const absl::optional&) { - return std::make_unique("grpc-status", "", absl::optional(), - GrpcStatusFormatter::parseFormat(format)); - }}}, - {"GRPC_STATUS_NUMBER", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique("grpc-status", "", absl::optional(), - GrpcStatusFormatter::Number); - }}}, - {"REQUEST_HEADERS_BYTES", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, absl::optional&) { - return std::make_unique( - HeadersByteSizeFormatter::HeaderType::RequestHeaders); - }}}, - {"RESPONSE_HEADERS_BYTES", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, absl::optional&) { - return std::make_unique( - HeadersByteSizeFormatter::HeaderType::ResponseHeaders); - }}}, - {"RESPONSE_TRAILERS_BYTES", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, absl::optional&) { - return std::make_unique( - HeadersByteSizeFormatter::HeaderType::ResponseTrailers); - }}}, - {"START_TIME", - {CommandSyntaxChecker::PARAMS_OPTIONAL, - [](const std::string& format, const absl::optional&) { - return std::make_unique( - format, - std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { - return stream_info.startTime(); - })); - }}}, - - {"DYNAMIC_METADATA", - {CommandSyntaxChecker::PARAMS_REQUIRED, - [](const std::string& format, const absl::optional& max_length) { - std::string filter_namespace; - std::vector path; - - SubstitutionFormatParser::parseSubcommand(format, ':', filter_namespace, path); - return std::make_unique(filter_namespace, path, max_length); - }}}, - - {"CLUSTER_METADATA", - {CommandSyntaxChecker::PARAMS_REQUIRED, - [](const std::string& format, const absl::optional& max_length) { - std::string filter_namespace; - std::vector path; - - SubstitutionFormatParser::parseSubcommand(format, ':', filter_namespace, path); - return std::make_unique(filter_namespace, path, max_length); - }}}, - {"UPSTREAM_METADATA", - {CommandSyntaxChecker::PARAMS_REQUIRED, - [](const std::string& format, const absl::optional& max_length) { - std::string filter_namespace; - std::vector path; - - SubstitutionFormatParser::parseSubcommand(format, ':', filter_namespace, path); - return std::make_unique(filter_namespace, path, - max_length); - }}}, - {"FILTER_STATE", - {CommandSyntaxChecker::PARAMS_OPTIONAL | CommandSyntaxChecker::LENGTH_ALLOWED, - [](const std::string& format, const absl::optional& max_length) { - return FilterStateFormatter::create(format, max_length, false); - }}}, - {"UPSTREAM_FILTER_STATE", - {CommandSyntaxChecker::PARAMS_OPTIONAL | CommandSyntaxChecker::LENGTH_ALLOWED, - [](const std::string& format, const absl::optional& max_length) { - return FilterStateFormatter::create(format, max_length, true); - }}}, - {"DOWNSTREAM_PEER_CERT_V_START", - {CommandSyntaxChecker::PARAMS_OPTIONAL, - [](const std::string& format, const absl::optional&) { - return std::make_unique(format); - }}}, - {"DOWNSTREAM_PEER_CERT_V_END", - {CommandSyntaxChecker::PARAMS_OPTIONAL, - [](const std::string& format, const absl::optional&) { - return std::make_unique(format); - }}}, - {"UPSTREAM_PEER_CERT_V_START", - {CommandSyntaxChecker::PARAMS_OPTIONAL, - [](const std::string& format, const absl::optional&) { - return std::make_unique(format); - }}}, - {"UPSTREAM_PEER_CERT_V_END", - {CommandSyntaxChecker::PARAMS_OPTIONAL, - [](const std::string& format, const absl::optional&) { - return std::make_unique(format); - }}}, - {"ENVIRONMENT", - {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, - [](const std::string& key, absl::optional& max_length) { - return std::make_unique(key, max_length); - }}}, - {"STREAM_INFO_REQ", - {CommandSyntaxChecker::PARAMS_REQUIRED | CommandSyntaxChecker::LENGTH_ALLOWED, - [](const std::string& format, absl::optional& max_length) { - std::string main_header, alternative_header; - - SubstitutionFormatParser::parseSubcommandHeaders(format, main_header, - alternative_header); - - return std::make_unique(main_header, alternative_header, - max_length); - }}}}); -} - -FormatterProviderPtr SubstitutionFormatParser::parseBuiltinCommand(const std::string& command, - const std::string& subcommand, - absl::optional& length) { - const FormatterProviderLookupTbl& providers = getKnownFormatters(); - - auto it = providers.find(command); - - if (it == providers.end()) { - return nullptr; - } - - // Check flags for the command. - CommandSyntaxChecker::verifySyntax((*it).second.first, command, subcommand, length); - - // Create a pointer to the formatter by calling a function - // associated with formatter's name. - return (*it).second.second(subcommand, length); -} - // TODO(derekargueta): #2967 - Rewrite SubstitutionFormatter with parser library & formal grammar std::vector SubstitutionFormatParser::parse(const std::string& format, @@ -638,7 +464,8 @@ SubstitutionFormatParser::parse(const std::string& format, const size_t command_end_position = pos + m.str(0).length() - 1; - auto formatter = parseBuiltinCommand(command, subcommand, max_length); + auto formatter = + HttpBuiltInCommandParser::builtInCommandParser().parse(command, subcommand, max_length); if (formatter) { formatters.push_back(std::move(formatter)); } else { @@ -673,1676 +500,5 @@ SubstitutionFormatParser::parse(const std::string& format, return formatters; } -// StreamInfo std::string field extractor. -class StreamInfoStringFieldExtractor : public StreamInfoFormatter::FieldExtractor { -public: - using FieldExtractor = std::function(const StreamInfo::StreamInfo&)>; - - StreamInfoStringFieldExtractor(FieldExtractor f) : field_extractor_(f) {} - - // StreamInfoFormatter::FieldExtractor - absl::optional extract(const StreamInfo::StreamInfo& stream_info) const override { - return field_extractor_(stream_info); - } - ProtobufWkt::Value extractValue(const StreamInfo::StreamInfo& stream_info) const override { - return ValueUtil::optionalStringValue(field_extractor_(stream_info)); - } - -private: - FieldExtractor field_extractor_; -}; - -// StreamInfo std::chrono_nanoseconds field extractor. -class StreamInfoDurationFieldExtractor : public StreamInfoFormatter::FieldExtractor { -public: - using FieldExtractor = - std::function(const StreamInfo::StreamInfo&)>; - - StreamInfoDurationFieldExtractor(FieldExtractor f) : field_extractor_(f) {} - - // StreamInfoFormatter::FieldExtractor - absl::optional extract(const StreamInfo::StreamInfo& stream_info) const override { - const auto millis = extractMillis(stream_info); - if (!millis) { - return absl::nullopt; - } - - return fmt::format_int(millis.value()).str(); - } - ProtobufWkt::Value extractValue(const StreamInfo::StreamInfo& stream_info) const override { - const auto millis = extractMillis(stream_info); - if (!millis) { - return unspecifiedValue(); - } - - return ValueUtil::numberValue(millis.value()); - } - -private: - absl::optional extractMillis(const StreamInfo::StreamInfo& stream_info) const { - const auto time = field_extractor_(stream_info); - if (time) { - return std::chrono::duration_cast(time.value()).count(); - } - return absl::nullopt; - } - - FieldExtractor field_extractor_; -}; - -// StreamInfo uint64_t field extractor. -class StreamInfoUInt64FieldExtractor : public StreamInfoFormatter::FieldExtractor { -public: - using FieldExtractor = std::function; - - StreamInfoUInt64FieldExtractor(FieldExtractor f) : field_extractor_(f) {} - - // StreamInfoFormatter::FieldExtractor - absl::optional extract(const StreamInfo::StreamInfo& stream_info) const override { - return fmt::format_int(field_extractor_(stream_info)).str(); - } - ProtobufWkt::Value extractValue(const StreamInfo::StreamInfo& stream_info) const override { - return ValueUtil::numberValue(field_extractor_(stream_info)); - } - -private: - FieldExtractor field_extractor_; -}; - -// StreamInfo Envoy::Network::Address::InstanceConstSharedPtr field extractor. -class StreamInfoAddressFieldExtractor : public StreamInfoFormatter::FieldExtractor { -public: - using FieldExtractor = - std::function; - - static std::unique_ptr withPort(FieldExtractor f) { - return std::make_unique( - f, StreamInfoFormatter::StreamInfoAddressFieldExtractionType::WithPort); - } - - static std::unique_ptr withoutPort(FieldExtractor f) { - return std::make_unique( - f, StreamInfoFormatter::StreamInfoAddressFieldExtractionType::WithoutPort); - } - - static std::unique_ptr justPort(FieldExtractor f) { - return std::make_unique( - f, StreamInfoFormatter::StreamInfoAddressFieldExtractionType::JustPort); - } - - StreamInfoAddressFieldExtractor( - FieldExtractor f, StreamInfoFormatter::StreamInfoAddressFieldExtractionType extraction_type) - : field_extractor_(f), extraction_type_(extraction_type) {} - - // StreamInfoFormatter::FieldExtractor - absl::optional extract(const StreamInfo::StreamInfo& stream_info) const override { - Network::Address::InstanceConstSharedPtr address = field_extractor_(stream_info); - if (!address) { - return absl::nullopt; - } - - return toString(*address); - } - ProtobufWkt::Value extractValue(const StreamInfo::StreamInfo& stream_info) const override { - Network::Address::InstanceConstSharedPtr address = field_extractor_(stream_info); - if (!address) { - return unspecifiedValue(); - } - - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.format_ports_as_numbers")) { - if (extraction_type_ == StreamInfoFormatter::StreamInfoAddressFieldExtractionType::JustPort) { - const auto port = StreamInfo::Utility::extractDownstreamAddressJustPort(*address); - if (port) { - return ValueUtil::numberValue(*port); - } - return unspecifiedValue(); - } - } - return ValueUtil::stringValue(toString(*address)); - } - -private: - std::string toString(const Network::Address::Instance& address) const { - switch (extraction_type_) { - case StreamInfoFormatter::StreamInfoAddressFieldExtractionType::WithoutPort: - return StreamInfo::Utility::formatDownstreamAddressNoPort(address); - case StreamInfoFormatter::StreamInfoAddressFieldExtractionType::JustPort: - return StreamInfo::Utility::formatDownstreamAddressJustPort(address); - case StreamInfoFormatter::StreamInfoAddressFieldExtractionType::WithPort: - default: - return address.asString(); - } - } - - FieldExtractor field_extractor_; - const StreamInfoFormatter::StreamInfoAddressFieldExtractionType extraction_type_; -}; - -// Ssl::ConnectionInfo std::string field extractor. -class StreamInfoSslConnectionInfoFieldExtractor : public StreamInfoFormatter::FieldExtractor { -public: - using FieldExtractor = - std::function(const Ssl::ConnectionInfo& connection_info)>; - - StreamInfoSslConnectionInfoFieldExtractor(FieldExtractor f) : field_extractor_(f) {} - - absl::optional extract(const StreamInfo::StreamInfo& stream_info) const override { - if (stream_info.downstreamAddressProvider().sslConnection() == nullptr) { - return absl::nullopt; - } - - const auto value = field_extractor_(*stream_info.downstreamAddressProvider().sslConnection()); - if (value && value->empty()) { - return absl::nullopt; - } - - return value; - } - - ProtobufWkt::Value extractValue(const StreamInfo::StreamInfo& stream_info) const override { - if (stream_info.downstreamAddressProvider().sslConnection() == nullptr) { - return unspecifiedValue(); - } - - const auto value = field_extractor_(*stream_info.downstreamAddressProvider().sslConnection()); - if (value && value->empty()) { - return unspecifiedValue(); - } - - return ValueUtil::optionalStringValue(value); - } - -private: - FieldExtractor field_extractor_; -}; - -class StreamInfoUpstreamSslConnectionInfoFieldExtractor - : public StreamInfoFormatter::FieldExtractor { -public: - using FieldExtractor = - std::function(const Ssl::ConnectionInfo& connection_info)>; - - StreamInfoUpstreamSslConnectionInfoFieldExtractor(FieldExtractor f) : field_extractor_(f) {} - - absl::optional extract(const StreamInfo::StreamInfo& stream_info) const override { - if (!stream_info.upstreamInfo() || - stream_info.upstreamInfo()->upstreamSslConnection() == nullptr) { - return absl::nullopt; - } - - const auto value = field_extractor_(*(stream_info.upstreamInfo()->upstreamSslConnection())); - if (value && value->empty()) { - return absl::nullopt; - } - - return value; - } - - ProtobufWkt::Value extractValue(const StreamInfo::StreamInfo& stream_info) const override { - if (!stream_info.upstreamInfo() || - stream_info.upstreamInfo()->upstreamSslConnection() == nullptr) { - return unspecifiedValue(); - } - - const auto value = field_extractor_(*(stream_info.upstreamInfo()->upstreamSslConnection())); - if (value && value->empty()) { - return unspecifiedValue(); - } - - return ValueUtil::optionalStringValue(value); - } - -private: - FieldExtractor field_extractor_; -}; - -const StreamInfoFormatter::FieldExtractorLookupTbl& StreamInfoFormatter::getKnownFieldExtractors() { - CONSTRUCT_ON_FIRST_USE(FieldExtractorLookupTbl, - {{"REQUEST_DURATION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - StreamInfo::TimingUtility timing(stream_info); - return timing.lastDownstreamRxByteReceived(); - }); - }}}, - {"REQUEST_TX_DURATION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - StreamInfo::TimingUtility timing(stream_info); - return timing.lastUpstreamTxByteSent(); - }); - }}}, - {"RESPONSE_DURATION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - StreamInfo::TimingUtility timing(stream_info); - return timing.firstUpstreamRxByteReceived(); - }); - }}}, - {"RESPONSE_TX_DURATION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - StreamInfo::TimingUtility timing(stream_info); - auto downstream = timing.lastDownstreamTxByteSent(); - auto upstream = timing.firstUpstreamRxByteReceived(); - - absl::optional result; - if (downstream && upstream) { - result = downstream.value() - upstream.value(); - } - - return result; - }); - }}}, - {"DOWNSTREAM_HANDSHAKE_DURATION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - StreamInfo::TimingUtility timing(stream_info); - return timing.downstreamHandshakeComplete(); - }); - }}}, - {"ROUNDTRIP_DURATION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - StreamInfo::TimingUtility timing(stream_info); - return timing.lastDownstreamAckReceived(); - }); - }}}, - {"BYTES_RECEIVED", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.bytesReceived(); - }); - }}}, - {"BYTES_RETRANSMITTED", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.bytesRetransmitted(); - }); - }}}, - {"PACKETS_RETRANSMITTED", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.packetsRetransmitted(); - }); - }}}, - {"UPSTREAM_WIRE_BYTES_RECEIVED", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - auto bytes_meter = stream_info.getUpstreamBytesMeter(); - return bytes_meter ? bytes_meter->wireBytesReceived() : 0; - }); - }}}, - {"UPSTREAM_HEADER_BYTES_RECEIVED", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - auto bytes_meter = stream_info.getUpstreamBytesMeter(); - return bytes_meter ? bytes_meter->headerBytesReceived() : 0; - }); - }}}, - {"DOWNSTREAM_WIRE_BYTES_RECEIVED", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - auto bytes_meter = stream_info.getDownstreamBytesMeter(); - return bytes_meter ? bytes_meter->wireBytesReceived() : 0; - }); - }}}, - {"DOWNSTREAM_HEADER_BYTES_RECEIVED", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - auto bytes_meter = stream_info.getDownstreamBytesMeter(); - return bytes_meter ? bytes_meter->headerBytesReceived() : 0; - }); - }}}, - {"PROTOCOL", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return SubstitutionFormatUtils::protocolToString( - stream_info.protocol()); - }); - }}}, - {"UPSTREAM_PROTOCOL", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.upstreamInfo() - ? SubstitutionFormatUtils::protocolToString( - stream_info.upstreamInfo()->upstreamProtocol()) - : absl::nullopt; - }); - }}}, - {"RESPONSE_CODE", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.responseCode().value_or(0); - }); - }}}, - {"RESPONSE_CODE_DETAILS", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.responseCodeDetails(); - }); - }}}, - {"CONNECTION_TERMINATION_DETAILS", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.connectionTerminationDetails(); - }); - }}}, - {"BYTES_SENT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.bytesSent(); - }); - }}}, - {"UPSTREAM_WIRE_BYTES_SENT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - const auto& bytes_meter = stream_info.getUpstreamBytesMeter(); - return bytes_meter ? bytes_meter->wireBytesSent() : 0; - }); - }}}, - {"UPSTREAM_HEADER_BYTES_SENT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - const auto& bytes_meter = stream_info.getUpstreamBytesMeter(); - return bytes_meter ? bytes_meter->headerBytesSent() : 0; - }); - }}}, - {"DOWNSTREAM_WIRE_BYTES_SENT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - const auto& bytes_meter = stream_info.getDownstreamBytesMeter(); - return bytes_meter ? bytes_meter->wireBytesSent() : 0; - }); - }}}, - {"DOWNSTREAM_HEADER_BYTES_SENT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - const auto& bytes_meter = stream_info.getDownstreamBytesMeter(); - return bytes_meter ? bytes_meter->headerBytesSent() : 0; - }); - }}}, - {"DURATION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.currentDuration(); - }); - }}}, - {"RESPONSE_FLAGS", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return StreamInfo::ResponseFlagUtils::toShortString( - stream_info); - }); - }}}, - {"UPSTREAM_HOST", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withPort( - [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo() && - stream_info.upstreamInfo()->upstreamHost()) { - return stream_info.upstreamInfo()->upstreamHost()->address(); - } - return nullptr; - }); - }}}, - {"UPSTREAM_CLUSTER", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - std::string upstream_cluster_name; - if (stream_info.upstreamClusterInfo().has_value() && - stream_info.upstreamClusterInfo().value() != nullptr) { - upstream_cluster_name = stream_info.upstreamClusterInfo() - .value() - ->observabilityName(); - } - - return upstream_cluster_name.empty() - ? absl::nullopt - : absl::make_optional( - upstream_cluster_name); - }); - }}}, - {"UPSTREAM_LOCAL_ADDRESS", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withPort( - [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo().has_value()) { - return stream_info.upstreamInfo() - .value() - .get() - .upstreamLocalAddress(); - } - return nullptr; - }); - }}}, - {"UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withoutPort( - [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo().has_value()) { - return stream_info.upstreamInfo() - .value() - .get() - .upstreamLocalAddress(); - } - return nullptr; - }); - }}}, - {"UPSTREAM_LOCAL_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::justPort( - [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo().has_value()) { - return stream_info.upstreamInfo() - .value() - .get() - .upstreamLocalAddress(); - } - return nullptr; - }); - }}}, - {"UPSTREAM_REMOTE_ADDRESS", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withPort( - [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo() && - stream_info.upstreamInfo()->upstreamHost()) { - return stream_info.upstreamInfo()->upstreamHost()->address(); - } - return nullptr; - }); - }}}, - {"UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withoutPort( - [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo() && - stream_info.upstreamInfo()->upstreamHost()) { - return stream_info.upstreamInfo()->upstreamHost()->address(); - } - return nullptr; - }); - }}}, - {"UPSTREAM_REMOTE_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::justPort( - [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo() && - stream_info.upstreamInfo()->upstreamHost()) { - return stream_info.upstreamInfo()->upstreamHost()->address(); - } - return nullptr; - }); - }}}, - {"UPSTREAM_REQUEST_ATTEMPT_COUNT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.attemptCount().value_or(0); - }); - }}}, - {"UPSTREAM_TLS_CIPHER", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique< - StreamInfoUpstreamSslConnectionInfoFieldExtractor>( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.ciphersuiteString(); - }); - }}}, - {"UPSTREAM_TLS_VERSION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique< - StreamInfoUpstreamSslConnectionInfoFieldExtractor>( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.tlsVersion(); - }); - }}}, - {"UPSTREAM_TLS_SESSION_ID", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique< - StreamInfoUpstreamSslConnectionInfoFieldExtractor>( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.sessionId(); - }); - }}}, - {"UPSTREAM_PEER_ISSUER", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique< - StreamInfoUpstreamSslConnectionInfoFieldExtractor>( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.issuerPeerCertificate(); - }); - }}}, - {"UPSTREAM_PEER_CERT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique< - StreamInfoUpstreamSslConnectionInfoFieldExtractor>( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.urlEncodedPemEncodedPeerCertificate(); - }); - }}}, - {"UPSTREAM_PEER_SUBJECT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique< - StreamInfoUpstreamSslConnectionInfoFieldExtractor>( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.subjectPeerCertificate(); - }); - }}}, - {"DOWNSTREAM_LOCAL_ADDRESS", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withPort( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider().localAddress(); - }); - }}}, - {"DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withoutPort( - [](const Envoy::StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider().localAddress(); - }); - }}}, - {"DOWNSTREAM_LOCAL_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::justPort( - [](const Envoy::StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider().localAddress(); - }); - }}}, - {"DOWNSTREAM_REMOTE_ADDRESS", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withPort( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider().remoteAddress(); - }); - }}}, - {"DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withoutPort( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider().remoteAddress(); - }); - }}}, - {"DOWNSTREAM_REMOTE_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::justPort( - [](const Envoy::StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider().remoteAddress(); - }); - }}}, - {"DOWNSTREAM_DIRECT_REMOTE_ADDRESS", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withPort( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider() - .directRemoteAddress(); - }); - }}}, - {"DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::withoutPort( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider() - .directRemoteAddress(); - }); - }}}, - {"DOWNSTREAM_DIRECT_REMOTE_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return StreamInfoAddressFieldExtractor::justPort( - [](const Envoy::StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider() - .directRemoteAddress(); - }); - }}}, - {"CONNECTION_ID", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - return stream_info.downstreamAddressProvider() - .connectionID() - .value_or(0); - }); - }}}, - {"REQUESTED_SERVER_NAME", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - absl::optional result; - if (!stream_info.downstreamAddressProvider() - .requestedServerName() - .empty()) { - result = std::string(stream_info.downstreamAddressProvider() - .requestedServerName()); - } - return result; - }); - }}}, - {"ROUTE_NAME", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - absl::optional result; - std::string route_name = stream_info.getRouteName(); - if (!route_name.empty()) { - result = route_name; - } - return result; - }); - }}}, - {"DOWNSTREAM_PEER_URI_SAN", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return absl::StrJoin(connection_info.uriSanPeerCertificate(), - ","); - }); - }}}, - {"DOWNSTREAM_PEER_DNS_SAN", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return absl::StrJoin(connection_info.dnsSansPeerCertificate(), - ","); - }); - }}}, - {"DOWNSTREAM_PEER_IP_SAN", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return absl::StrJoin(connection_info.ipSansPeerCertificate(), - ","); - }); - }}}, - {"DOWNSTREAM_LOCAL_URI_SAN", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return absl::StrJoin(connection_info.uriSanLocalCertificate(), - ","); - }); - }}}, - {"DOWNSTREAM_LOCAL_DNS_SAN", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return absl::StrJoin(connection_info.dnsSansLocalCertificate(), - ","); - }); - }}}, - {"DOWNSTREAM_LOCAL_IP_SAN", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return absl::StrJoin(connection_info.ipSansLocalCertificate(), - ","); - }); - }}}, - {"DOWNSTREAM_PEER_SUBJECT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.subjectPeerCertificate(); - }); - }}}, - {"DOWNSTREAM_LOCAL_SUBJECT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.subjectLocalCertificate(); - }); - }}}, - {"DOWNSTREAM_TLS_SESSION_ID", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.sessionId(); - }); - }}}, - {"DOWNSTREAM_TLS_CIPHER", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.ciphersuiteString(); - }); - }}}, - {"DOWNSTREAM_TLS_VERSION", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.tlsVersion(); - }); - }}}, - {"DOWNSTREAM_PEER_FINGERPRINT_256", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.sha256PeerCertificateDigest(); - }); - }}}, - {"DOWNSTREAM_PEER_FINGERPRINT_1", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.sha1PeerCertificateDigest(); - }); - }}}, - {"DOWNSTREAM_PEER_SERIAL", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.serialNumberPeerCertificate(); - }); - }}}, - {"DOWNSTREAM_PEER_ISSUER", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.issuerPeerCertificate(); - }); - }}}, - {"DOWNSTREAM_PEER_CERT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const Ssl::ConnectionInfo& connection_info) { - return connection_info.urlEncodedPemEncodedPeerCertificate(); - }); - }}}, - {"DOWNSTREAM_TRANSPORT_FAILURE_REASON", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - absl::optional result; - if (!stream_info.downstreamTransportFailureReason().empty()) { - result = absl::StrReplaceAll( - stream_info.downstreamTransportFailureReason(), - {{" ", "_"}}); - } - return result; - }); - }}}, - {"UPSTREAM_TRANSPORT_FAILURE_REASON", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - absl::optional result; - if (stream_info.upstreamInfo().has_value() && - !stream_info.upstreamInfo() - .value() - .get() - .upstreamTransportFailureReason() - .empty()) { - result = stream_info.upstreamInfo() - .value() - .get() - .upstreamTransportFailureReason(); - } - if (result) { - std::replace(result->begin(), result->end(), ' ', '_'); - } - return result; - }); - }}}, - {"HOSTNAME", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - absl::optional hostname = - SubstitutionFormatUtils::getHostname(); - return std::make_unique( - [hostname](const StreamInfo::StreamInfo&) { return hostname; }); - }}}, - {"FILTER_CHAIN_NAME", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) - -> absl::optional { - if (!stream_info.filterChainName().empty()) { - return stream_info.filterChainName(); - } - return absl::nullopt; - }); - }}}, - {"VIRTUAL_CLUSTER_NAME", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) - -> absl::optional { - return stream_info.virtualClusterName(); - }); - }}}, - {"TLS_JA3_FINGERPRINT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) { - absl::optional result; - if (!stream_info.downstreamAddressProvider() - .ja3Hash() - .empty()) { - result = std::string( - stream_info.downstreamAddressProvider().ja3Hash()); - } - return result; - }); - }}}, - {"STREAM_ID", - {CommandSyntaxChecker::COMMAND_ONLY, - [](const std::string&, const absl::optional&) { - return std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) - -> absl::optional { - auto provider = stream_info.getStreamIdProvider(); - if (!provider.has_value()) { - return {}; - } - auto id = provider->toStringView(); - if (!id.has_value()) { - return {}; - } - return absl::make_optional(id.value()); - }); - }}}}); -} - -StreamInfoFormatter::StreamInfoFormatter(const std::string& command, const std::string& subcommand, - const absl::optional& length) { - const FieldExtractorLookupTbl& extractors = getKnownFieldExtractors(); - - auto it = extractors.find(command); - - if (it == extractors.end()) { - throw EnvoyException(fmt::format("Not supported field in StreamInfo: {}", command)); - } - - // Check flags for the command. - CommandSyntaxChecker::verifySyntax((*it).second.first, command, subcommand, length); - - // Create a pointer to the formatter by calling a function - // associated with formatter's name. - field_extractor_ = (*it).second.second(subcommand, length); -} - -absl::optional StreamInfoFormatter::format( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - return field_extractor_->extract(stream_info); -} - -ProtobufWkt::Value StreamInfoFormatter::formatValue( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - return field_extractor_->extractValue(stream_info); -} - -PlainStringFormatter::PlainStringFormatter(const std::string& str) { str_.set_string_value(str); } - -absl::optional -PlainStringFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return str_.string_value(); -} - -ProtobufWkt::Value -PlainStringFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return str_; -} - -PlainNumberFormatter::PlainNumberFormatter(double num) { num_.set_number_value(num); } - -absl::optional -PlainNumberFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - std::string str = absl::StrFormat("%g", num_.number_value()); - return str; -} - -ProtobufWkt::Value -PlainNumberFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return num_; -} - -absl::optional LocalReplyBodyFormatter::format(const Http::RequestHeaderMap&, - const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo&, - absl::string_view local_reply_body, - AccessLog::AccessLogType) const { - return std::string(local_reply_body); -} - -ProtobufWkt::Value LocalReplyBodyFormatter::formatValue(const Http::RequestHeaderMap&, - const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo&, - absl::string_view local_reply_body, - AccessLog::AccessLogType) const { - return ValueUtil::stringValue(std::string(local_reply_body)); -} - -absl::optional -AccessLogTypeFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType access_log_type) const { - return AccessLogType_Name(access_log_type); -} - -ProtobufWkt::Value -AccessLogTypeFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, - AccessLog::AccessLogType access_log_type) const { - return ValueUtil::stringValue(AccessLogType_Name(access_log_type)); -} - -HeaderFormatter::HeaderFormatter(const std::string& main_header, - const std::string& alternative_header, - absl::optional max_length) - : main_header_(main_header), alternative_header_(alternative_header), max_length_(max_length) {} - -const Http::HeaderEntry* HeaderFormatter::findHeader(const Http::HeaderMap& headers) const { - const auto header = headers.get(main_header_); - - if (header.empty() && !alternative_header_.get().empty()) { - const auto alternate_header = headers.get(alternative_header_); - // TODO(https://github.com/envoyproxy/envoy/issues/13454): Potentially log all header values. - return alternate_header.empty() ? nullptr : alternate_header[0]; - } - - return header.empty() ? nullptr : header[0]; -} - -absl::optional HeaderFormatter::format(const Http::HeaderMap& headers) const { - const Http::HeaderEntry* header = findHeader(headers); - if (!header) { - return absl::nullopt; - } - - std::string val = std::string(header->value().getStringView()); - truncate(val, max_length_); - return val; -} - -ProtobufWkt::Value HeaderFormatter::formatValue(const Http::HeaderMap& headers) const { - const Http::HeaderEntry* header = findHeader(headers); - if (!header) { - return unspecifiedValue(); - } - - std::string val = std::string(header->value().getStringView()); - truncate(val, max_length_); - return ValueUtil::stringValue(val); -} - -ResponseHeaderFormatter::ResponseHeaderFormatter(const std::string& main_header, - const std::string& alternative_header, - absl::optional max_length) - : HeaderFormatter(main_header, alternative_header, max_length) {} - -absl::optional -ResponseHeaderFormatter::format(const Http::RequestHeaderMap&, - const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return HeaderFormatter::format(response_headers); -} - -ProtobufWkt::Value -ResponseHeaderFormatter::formatValue(const Http::RequestHeaderMap&, - const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return HeaderFormatter::formatValue(response_headers); -} - -RequestHeaderFormatter::RequestHeaderFormatter(const std::string& main_header, - const std::string& alternative_header, - absl::optional max_length) - : HeaderFormatter(main_header, alternative_header, max_length) {} - -absl::optional -RequestHeaderFormatter::format(const Http::RequestHeaderMap& request_headers, - const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo&, absl::string_view, - AccessLog::AccessLogType) const { - return HeaderFormatter::format(request_headers); -} - -ProtobufWkt::Value -RequestHeaderFormatter::formatValue(const Http::RequestHeaderMap& request_headers, - const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo&, absl::string_view, - AccessLog::AccessLogType) const { - return HeaderFormatter::formatValue(request_headers); -} - -ResponseTrailerFormatter::ResponseTrailerFormatter(const std::string& main_header, - const std::string& alternative_header, - absl::optional max_length) - : HeaderFormatter(main_header, alternative_header, max_length) {} - -absl::optional -ResponseTrailerFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap& response_trailers, - const StreamInfo::StreamInfo&, absl::string_view, - AccessLog::AccessLogType) const { - return HeaderFormatter::format(response_trailers); -} - -ProtobufWkt::Value -ResponseTrailerFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap& response_trailers, - const StreamInfo::StreamInfo&, absl::string_view, - AccessLog::AccessLogType) const { - return HeaderFormatter::formatValue(response_trailers); -} - -HeadersByteSizeFormatter::HeadersByteSizeFormatter(const HeaderType header_type) - : header_type_(header_type) {} - -uint64_t HeadersByteSizeFormatter::extractHeadersByteSize( - const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers) const { - switch (header_type_) { - case HeaderType::RequestHeaders: - return request_headers.byteSize(); - case HeaderType::ResponseHeaders: - return response_headers.byteSize(); - case HeaderType::ResponseTrailers: - return response_trailers.byteSize(); - } - PANIC_DUE_TO_CORRUPT_ENUM; -} - -absl::optional HeadersByteSizeFormatter::format( - const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return absl::StrCat(extractHeadersByteSize(request_headers, response_headers, response_trailers)); -} - -ProtobufWkt::Value HeadersByteSizeFormatter::formatValue( - const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return ValueUtil::numberValue( - extractHeadersByteSize(request_headers, response_headers, response_trailers)); -} - -GrpcStatusFormatter::Format GrpcStatusFormatter::parseFormat(absl::string_view format) { - if (format.empty() || format == "CAMEL_STRING") { - return GrpcStatusFormatter::CamelString; - } - - if (format == "SNAKE_STRING") { - return GrpcStatusFormatter::SnakeString; - } - if (format == "NUMBER") { - return GrpcStatusFormatter::Number; - } - - throw EnvoyException("GrpcStatusFormatter only supports CAMEL_STRING, SNAKE_STRING or NUMBER."); -} - -GrpcStatusFormatter::GrpcStatusFormatter(const std::string& main_header, - const std::string& alternative_header, - absl::optional max_length, Format format) - : HeaderFormatter(main_header, alternative_header, max_length), format_(format) {} - -absl::optional GrpcStatusFormatter::format( - const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo& info, - absl::string_view, AccessLog::AccessLogType) const { - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.validate_grpc_header_before_log_grpc_status")) { - if (!Grpc::Common::isGrpcRequestHeaders(request_headers)) { - return absl::nullopt; - } - } - const auto grpc_status = - Grpc::Common::getGrpcStatus(response_trailers, response_headers, info, true); - if (!grpc_status.has_value()) { - return absl::nullopt; - } - switch (format_) { - case CamelString: { - const auto grpc_status_message = Grpc::Utility::grpcStatusToString(grpc_status.value()); - if (grpc_status_message == EMPTY_STRING || grpc_status_message == "InvalidCode") { - return std::to_string(grpc_status.value()); - } - return grpc_status_message; - } - case SnakeString: { - const auto grpc_status_message = - absl::StatusCodeToString(static_cast(grpc_status.value())); - if (grpc_status_message == EMPTY_STRING) { - return std::to_string(grpc_status.value()); - } - return grpc_status_message; - } - case Number: { - return std::to_string(grpc_status.value()); - } - } - PANIC_DUE_TO_CORRUPT_ENUM; -} - -ProtobufWkt::Value GrpcStatusFormatter::formatValue( - const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo& info, - absl::string_view, AccessLog::AccessLogType) const { - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.validate_grpc_header_before_log_grpc_status")) { - if (!Grpc::Common::isGrpcRequestHeaders(request_headers)) { - return unspecifiedValue(); - } - } - const auto grpc_status = - Grpc::Common::getGrpcStatus(response_trailers, response_headers, info, true); - if (!grpc_status.has_value()) { - return unspecifiedValue(); - } - - switch (format_) { - case CamelString: { - const auto grpc_status_message = Grpc::Utility::grpcStatusToString(grpc_status.value()); - if (grpc_status_message == EMPTY_STRING || grpc_status_message == "InvalidCode") { - return ValueUtil::stringValue(std::to_string(grpc_status.value())); - } - return ValueUtil::stringValue(grpc_status_message); - } - case SnakeString: { - const auto grpc_status_message = - absl::StatusCodeToString(static_cast(grpc_status.value())); - if (grpc_status_message == EMPTY_STRING) { - return ValueUtil::stringValue(std::to_string(grpc_status.value())); - } - return ValueUtil::stringValue(grpc_status_message); - } - case Number: { - return ValueUtil::numberValue(grpc_status.value()); - } - } - PANIC_DUE_TO_CORRUPT_ENUM; -} -MetadataFormatter::MetadataFormatter(const std::string& filter_namespace, - const std::vector& path, - absl::optional max_length, - MetadataFormatter::GetMetadataFunction get_func) - : filter_namespace_(filter_namespace), path_(path), max_length_(max_length), - get_func_(get_func) {} - -absl::optional -MetadataFormatter::formatMetadata(const envoy::config::core::v3::Metadata& metadata) const { - ProtobufWkt::Value value = formatMetadataValue(metadata); - if (value.kind_case() == ProtobufWkt::Value::kNullValue) { - return absl::nullopt; - } - - std::string str; - if (value.kind_case() == ProtobufWkt::Value::kStringValue) { - str = value.string_value(); - } else { -#ifdef ENVOY_ENABLE_YAML - absl::StatusOr json_or_error = - MessageUtil::getJsonStringFromMessage(value, false, true); - if (json_or_error.ok()) { - str = json_or_error.value(); - } else { - str = json_or_error.status().message(); - } -#else - IS_ENVOY_BUG("Json support compiled out"); -#endif - } - truncate(str, max_length_); - return str; -} - -ProtobufWkt::Value -MetadataFormatter::formatMetadataValue(const envoy::config::core::v3::Metadata& metadata) const { - if (path_.empty()) { - const auto filter_it = metadata.filter_metadata().find(filter_namespace_); - if (filter_it == metadata.filter_metadata().end()) { - return unspecifiedValue(); - } - ProtobufWkt::Value output; - output.mutable_struct_value()->CopyFrom(filter_it->second); - return output; - } - - const ProtobufWkt::Value& val = Metadata::metadataValue(&metadata, filter_namespace_, path_); - if (val.kind_case() == ProtobufWkt::Value::KindCase::KIND_NOT_SET) { - return unspecifiedValue(); - } - - return val; -} - -absl::optional MetadataFormatter::format( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - auto metadata = get_func_(stream_info); - return (metadata != nullptr) ? formatMetadata(*metadata) : absl::nullopt; -} - -ProtobufWkt::Value MetadataFormatter::formatValue( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - auto metadata = get_func_(stream_info); - return formatMetadataValue((metadata != nullptr) ? *metadata - : envoy::config::core::v3::Metadata()); -} - -// TODO(glicht): Consider adding support for route/listener/cluster metadata as suggested by -// @htuch. See: https://github.com/envoyproxy/envoy/issues/3006 -DynamicMetadataFormatter::DynamicMetadataFormatter(const std::string& filter_namespace, - const std::vector& path, - absl::optional max_length) - : MetadataFormatter(filter_namespace, path, max_length, - [](const StreamInfo::StreamInfo& stream_info) { - return &stream_info.dynamicMetadata(); - }) {} - -ClusterMetadataFormatter::ClusterMetadataFormatter(const std::string& filter_namespace, - const std::vector& path, - absl::optional max_length) - : MetadataFormatter(filter_namespace, path, max_length, - [](const StreamInfo::StreamInfo& stream_info) - -> const envoy::config::core::v3::Metadata* { - auto cluster_info = stream_info.upstreamClusterInfo(); - if (!cluster_info.has_value() || cluster_info.value() == nullptr) { - return nullptr; - } - return &cluster_info.value()->metadata(); - }) {} - -UpstreamHostMetadataFormatter::UpstreamHostMetadataFormatter(const std::string& filter_namespace, - const std::vector& path, - absl::optional max_length) - : MetadataFormatter(filter_namespace, path, max_length, - [](const StreamInfo::StreamInfo& stream_info) - -> const envoy::config::core::v3::Metadata* { - if (!stream_info.upstreamInfo().has_value()) { - return nullptr; - } - Upstream::HostDescriptionConstSharedPtr host = - stream_info.upstreamInfo()->upstreamHost(); - if (host == nullptr) { - return nullptr; - } - return host->metadata().get(); - }) {} - -std::unique_ptr -FilterStateFormatter::create(const std::string& format, const absl::optional& max_length, - bool is_upstream) { - std::string key, serialize_type; - static constexpr absl::string_view PLAIN_SERIALIZATION{"PLAIN"}; - static constexpr absl::string_view TYPED_SERIALIZATION{"TYPED"}; - - SubstitutionFormatParser::parseSubcommand(format, ':', key, serialize_type); - if (key.empty()) { - throw EnvoyException("Invalid filter state configuration, key cannot be empty."); - } - - if (serialize_type.empty()) { - serialize_type = std::string(TYPED_SERIALIZATION); - } - if (serialize_type != PLAIN_SERIALIZATION && serialize_type != TYPED_SERIALIZATION) { - throw EnvoyException("Invalid filter state serialize type, only " - "support PLAIN/TYPED."); - } - - const bool serialize_as_string = serialize_type == PLAIN_SERIALIZATION; - - return std::make_unique(key, max_length, serialize_as_string, is_upstream); -} - -FilterStateFormatter::FilterStateFormatter(const std::string& key, - absl::optional max_length, - bool serialize_as_string, bool is_upstream) - : key_(key), max_length_(max_length), serialize_as_string_(serialize_as_string), - is_upstream_(is_upstream) {} - -const Envoy::StreamInfo::FilterState::Object* -FilterStateFormatter::filterState(const StreamInfo::StreamInfo& stream_info) const { - const StreamInfo::FilterState* filter_state = nullptr; - if (is_upstream_) { - const OptRef upstream_info = stream_info.upstreamInfo(); - if (upstream_info) { - filter_state = upstream_info->upstreamFilterState().get(); - } - } else { - filter_state = &stream_info.filterState(); - } - - if (filter_state) { - return filter_state->getDataReadOnly(key_); - } - - return nullptr; -} - -absl::optional FilterStateFormatter::format( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - const Envoy::StreamInfo::FilterState::Object* state = filterState(stream_info); - if (!state) { - return absl::nullopt; - } - - if (serialize_as_string_) { - absl::optional plain_value = state->serializeAsString(); - if (plain_value.has_value()) { - truncate(plain_value.value(), max_length_); - return plain_value.value(); - } - return absl::nullopt; - } - - ProtobufTypes::MessagePtr proto = state->serializeAsProto(); - if (proto == nullptr) { - return absl::nullopt; - } - - std::string value; - const auto status = Protobuf::util::MessageToJsonString(*proto, &value); - if (!status.ok()) { - // If the message contains an unknown Any (from WASM or Lua), MessageToJsonString will fail. - // TODO(lizan): add support of unknown Any. - return absl::nullopt; - } - - truncate(value, max_length_); - return value; -} - -ProtobufWkt::Value FilterStateFormatter::formatValue( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - const Envoy::StreamInfo::FilterState::Object* state = filterState(stream_info); - if (!state) { - return unspecifiedValue(); - } - - if (serialize_as_string_) { - absl::optional plain_value = state->serializeAsString(); - if (plain_value.has_value()) { - truncate(plain_value.value(), max_length_); - return ValueUtil::stringValue(plain_value.value()); - } - return unspecifiedValue(); - } - - ProtobufTypes::MessagePtr proto = state->serializeAsProto(); - if (!proto) { - return unspecifiedValue(); - } - -#ifdef ENVOY_ENABLE_YAML - ProtobufWkt::Value val; - if (MessageUtil::jsonConvertValue(*proto, val)) { - return val; - } -#endif - return unspecifiedValue(); -} - -// A SystemTime formatter that extracts the startTime from StreamInfo. Must be provided -// an access log command that starts with `START_TIME`. -StartTimeFormatter::StartTimeFormatter(const std::string& format) - : SystemTimeFormatter( - format, std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { - return stream_info.startTime(); - })) {} - -DownstreamPeerCertVStartFormatter::DownstreamPeerCertVStartFormatter(const std::string& format) - : SystemTimeFormatter( - format, std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { - const auto connection_info = - stream_info.downstreamAddressProvider().sslConnection(); - return connection_info != nullptr - ? connection_info->validFromPeerCertificate() - : absl::optional(); - })) {} -DownstreamPeerCertVEndFormatter::DownstreamPeerCertVEndFormatter(const std::string& format) - : SystemTimeFormatter( - format, std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { - const auto connection_info = - stream_info.downstreamAddressProvider().sslConnection(); - return connection_info != nullptr - ? connection_info->expirationPeerCertificate() - : absl::optional(); - })) {} -UpstreamPeerCertVStartFormatter::UpstreamPeerCertVStartFormatter(const std::string& format) - : SystemTimeFormatter( - format, std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { - return stream_info.upstreamInfo() && - stream_info.upstreamInfo()->upstreamSslConnection() != - nullptr - ? stream_info.upstreamInfo() - ->upstreamSslConnection() - ->validFromPeerCertificate() - : absl::optional(); - })) {} -UpstreamPeerCertVEndFormatter::UpstreamPeerCertVEndFormatter(const std::string& format) - : SystemTimeFormatter( - format, std::make_unique( - [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { - return stream_info.upstreamInfo() && - stream_info.upstreamInfo()->upstreamSslConnection() != - nullptr - ? stream_info.upstreamInfo() - ->upstreamSslConnection() - ->expirationPeerCertificate() - : absl::optional(); - })) {} - -SystemTimeFormatter::SystemTimeFormatter(const std::string& format, TimeFieldExtractorPtr f) - : date_formatter_(format), time_field_extractor_(std::move(f)) { - // Validate the input specifier here. The formatted string may be destined for a header, and - // should not contain invalid characters {NUL, LR, CF}. - if (std::regex_search(format, getSystemTimeFormatNewlinePattern())) { - throw EnvoyException("Invalid header configuration. Format string contains newline."); - } -} - -absl::optional SystemTimeFormatter::format( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - const auto time_field = (*time_field_extractor_)(stream_info); - if (!time_field.has_value()) { - return absl::nullopt; - } - if (date_formatter_.formatString().empty()) { - return AccessLogDateTimeFormatter::fromTime(time_field.value()); - } - return date_formatter_.fromTime(time_field.value()); -} - -ProtobufWkt::Value SystemTimeFormatter::formatValue( - const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers, const StreamInfo::StreamInfo& stream_info, - absl::string_view local_reply_body, AccessLog::AccessLogType access_log_type) const { - return ValueUtil::optionalStringValue(format(request_headers, response_headers, response_trailers, - stream_info, local_reply_body, access_log_type)); -} - -void CommandSyntaxChecker::verifySyntax(CommandSyntaxFlags flags, const std::string& command, - const std::string& subcommand, - const absl::optional& length) { - if ((flags == COMMAND_ONLY) && ((subcommand.length() != 0) || length.has_value())) { - throw EnvoyException(fmt::format("{} does not take any parameters or length", command)); - } - - if ((flags & PARAMS_REQUIRED).any() && (subcommand.length() == 0)) { - throw EnvoyException(fmt::format("{} requires parameters", command)); - } - - if ((flags & LENGTH_ALLOWED).none() && length.has_value()) { - throw EnvoyException(fmt::format("{} does not allow length to be specified.", command)); - } -} - -EnvironmentFormatter::EnvironmentFormatter(const std::string& key, - absl::optional max_length) { - ASSERT(!key.empty()); - - const char* env_value = std::getenv(key.c_str()); - if (env_value != nullptr) { - std::string env_string = env_value; - truncate(env_string, max_length); - str_.set_string_value(env_string); - return; - } - str_.set_string_value(DefaultUnspecifiedValueString); -} - -absl::optional -EnvironmentFormatter::format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return str_.string_value(); -} -ProtobufWkt::Value -EnvironmentFormatter::formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const { - return str_; -} - -StreamInfoRequestHeaderFormatter::StreamInfoRequestHeaderFormatter( - const std::string& main_header, const std::string& alternative_header, - absl::optional max_length) - : HeaderFormatter(main_header, alternative_header, max_length) {} - -absl::optional StreamInfoRequestHeaderFormatter::format( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - return HeaderFormatter::format(*stream_info.getRequestHeaders()); -} - -ProtobufWkt::Value StreamInfoRequestHeaderFormatter::formatValue( - const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, AccessLog::AccessLogType) const { - return HeaderFormatter::formatValue(*stream_info.getRequestHeaders()); -} - } // namespace Formatter } // namespace Envoy diff --git a/source/common/formatter/substitution_formatter.h b/source/common/formatter/substitution_formatter.h index a2a9f95b3c1d..d55f81e731e7 100644 --- a/source/common/formatter/substitution_formatter.h +++ b/source/common/formatter/substitution_formatter.h @@ -90,28 +90,6 @@ class SubstitutionFormatParser { }(params), ...); } - - /** - * Return a FormatterProviderPtr if a built-in command is found. This method - * handles mapping the command name to an appropriate formatter. - * - * @param command - formatter's name. - * @param subcommand - optional formatter specific data. - * @param length - optional max length of output produced by the formatter. - * @return FormattterProviderPtr substitution provider for the command or nullptr - */ - static FormatterProviderPtr parseBuiltinCommand(const std::string& command, - const std::string& subcommand, - absl::optional& length); - -private: - using FormatterProviderCreateFunc = - std::function&)>; - - using FormatterProviderLookupTbl = - absl::flat_hash_map>; - static const FormatterProviderLookupTbl& getKnownFormatters(); }; /** @@ -129,9 +107,18 @@ class SubstitutionFormatUtils { static const absl::optional getHostname(); static const std::string getHostnameOrDefault(); -private: - SubstitutionFormatUtils(); + /** + * Unspecified value for protobuf. + */ + static const ProtobufWkt::Value& unspecifiedValue(); + + /** + * Truncate a string to a maximum length. Do nothing if max_length is not set or + * max_length is greater than the length of the string. + */ + static void truncate(std::string& str, absl::optional max_length); +private: static const std::string DEFAULT_FORMAT; }; @@ -263,446 +250,5 @@ class JsonFormatterImpl : public Formatter { const StructFormatter struct_formatter_; }; -/** - * FormatterProvider for string literals. It ignores headers and stream info and returns string by - * which it was initialized. - */ -class PlainStringFormatter : public FormatterProvider { -public: - PlainStringFormatter(const std::string& str); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - -private: - ProtobufWkt::Value str_; -}; - -/** - * FormatterProvider for numbers. - */ -class PlainNumberFormatter : public FormatterProvider { -public: - PlainNumberFormatter(double num); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - -private: - ProtobufWkt::Value num_; -}; - -/** - * FormatterProvider for local_reply_body. It returns the string from `local_reply_body` argument. - */ -class LocalReplyBodyFormatter : public FormatterProvider { -public: - LocalReplyBodyFormatter() = default; - - // Formatter::format - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view local_reply_body, - AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view local_reply_body, - AccessLog::AccessLogType) const override; -}; - -/** - * FormatterProvider for access log type. It returns the string from `access_log_type` argument. - */ -class AccessLogTypeFormatter : public FormatterProvider { -public: - AccessLogTypeFormatter() = default; - - // Formatter::format - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view local_reply_body, - AccessLog::AccessLogType access_log_type) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view local_reply_body, - AccessLog::AccessLogType access_log_type) const override; -}; - -class HeaderFormatter { -public: - HeaderFormatter(const std::string& main_header, const std::string& alternative_header, - absl::optional max_length); - -protected: - absl::optional format(const Http::HeaderMap& headers) const; - ProtobufWkt::Value formatValue(const Http::HeaderMap& headers) const; - -private: - const Http::HeaderEntry* findHeader(const Http::HeaderMap& headers) const; - - Http::LowerCaseString main_header_; - Http::LowerCaseString alternative_header_; - absl::optional max_length_; -}; - -/** - * FormatterProvider for headers byte size. - */ -class HeadersByteSizeFormatter : public FormatterProvider { -public: - // TODO(taoxuy): Add RequestTrailers here. - enum class HeaderType { RequestHeaders, ResponseHeaders, ResponseTrailers }; - - HeadersByteSizeFormatter(const HeaderType header_type); - - absl::optional format(const Http::RequestHeaderMap& request_headers, - const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers, - const StreamInfo::StreamInfo&, absl::string_view, - AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap& request_headers, - const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers, - const StreamInfo::StreamInfo&, absl::string_view, - AccessLog::AccessLogType) const override; - -private: - uint64_t extractHeadersByteSize(const Http::RequestHeaderMap& request_headers, - const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers) const; - HeaderType header_type_; -}; - -/** - * FormatterProvider for request headers. - */ -class RequestHeaderFormatter : public FormatterProvider, HeaderFormatter { -public: - RequestHeaderFormatter(const std::string& main_header, const std::string& alternative_header, - absl::optional max_length); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap& request_headers, - const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; -}; - -/** - * FormatterProvider for response headers. - */ -class ResponseHeaderFormatter : public FormatterProvider, HeaderFormatter { -public: - ResponseHeaderFormatter(const std::string& main_header, const std::string& alternative_header, - absl::optional max_length); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, - const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; -}; - -/** - * FormatterProvider for response trailers. - */ -class ResponseTrailerFormatter : public FormatterProvider, HeaderFormatter { -public: - ResponseTrailerFormatter(const std::string& main_header, const std::string& alternative_header, - absl::optional max_length); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap& response_trailers, - const StreamInfo::StreamInfo&, absl::string_view, - AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; -}; - -class GrpcStatusFormatter : public FormatterProvider, HeaderFormatter { -public: - enum Format { - CamelString, - SnakeString, - Number, - }; - - GrpcStatusFormatter(const std::string& main_header, const std::string& alternative_header, - absl::optional max_length, Format format); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, - const Http::ResponseHeaderMap& response_headers, - const Http::ResponseTrailerMap& response_trailers, - const StreamInfo::StreamInfo&, absl::string_view, - AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - - static Format parseFormat(absl::string_view format); - -private: - const Format format_; -}; - -/** - * FormatterProvider based on StreamInfo fields. - */ -class StreamInfoFormatter : public FormatterProvider { -public: - StreamInfoFormatter(const std::string&, const std::string& = "", - const absl::optional& = absl::nullopt); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - - class FieldExtractor { - public: - virtual ~FieldExtractor() = default; - - virtual absl::optional extract(const StreamInfo::StreamInfo&) const PURE; - virtual ProtobufWkt::Value extractValue(const StreamInfo::StreamInfo&) const PURE; - }; - using FieldExtractorPtr = std::unique_ptr; - using FieldExtractorCreateFunc = - std::function&)>; - - enum class StreamInfoAddressFieldExtractionType { WithPort, WithoutPort, JustPort }; - StreamInfoFormatter(FieldExtractorPtr field_extractor) { - field_extractor_ = std::move(field_extractor); - } - -private: - FieldExtractorPtr field_extractor_; - - using FieldExtractorLookupTbl = - absl::flat_hash_map>; - static const FieldExtractorLookupTbl& getKnownFieldExtractors(); -}; - -/** - * Base formatter for formatting Metadata objects - */ - -class MetadataFormatter : public FormatterProvider { -public: - using GetMetadataFunction = - std::function; - MetadataFormatter(const std::string& filter_namespace, const std::vector& path, - absl::optional max_length, GetMetadataFunction get); - - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, - AccessLog::AccessLogType) const override; - - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, - const StreamInfo::StreamInfo& stream_info, absl::string_view, - AccessLog::AccessLogType) const override; - -protected: - absl::optional - formatMetadata(const envoy::config::core::v3::Metadata& metadata) const; - ProtobufWkt::Value formatMetadataValue(const envoy::config::core::v3::Metadata& metadata) const; - -private: - std::string filter_namespace_; - std::vector path_; - absl::optional max_length_; - GetMetadataFunction get_func_; -}; - -/** - * FormatterProvider for DynamicMetadata from StreamInfo. - */ -class DynamicMetadataFormatter : public MetadataFormatter { -public: - DynamicMetadataFormatter(const std::string& filter_namespace, - const std::vector& path, absl::optional max_length); -}; - -/** - * FormatterProvider for ClusterMetadata from StreamInfo. - */ -class ClusterMetadataFormatter : public MetadataFormatter { -public: - ClusterMetadataFormatter(const std::string& filter_namespace, - const std::vector& path, absl::optional max_length); -}; - -/** - * FormatterProvider for UpstreamHostMetadata from StreamInfo. - */ -class UpstreamHostMetadataFormatter : public MetadataFormatter { -public: - UpstreamHostMetadataFormatter(const std::string& filter_namespace, - const std::vector& path, - absl::optional max_length); -}; - -/** - * FormatterProvider for FilterState from StreamInfo. - */ -class FilterStateFormatter : public FormatterProvider { -public: - static std::unique_ptr - create(const std::string& format, const absl::optional& max_length, bool is_upstream); - - FilterStateFormatter(const std::string& key, absl::optional max_length, - bool serialize_as_string, bool is_upstream = false); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - -private: - const Envoy::StreamInfo::FilterState::Object* - filterState(const StreamInfo::StreamInfo& stream_info) const; - - std::string key_; - absl::optional max_length_; - - bool serialize_as_string_; - const bool is_upstream_; -}; - -/** - * Base FormatterProvider for system times from StreamInfo. - */ -class SystemTimeFormatter : public FormatterProvider { -public: - using TimeFieldExtractor = - std::function(const StreamInfo::StreamInfo& stream_info)>; - using TimeFieldExtractorPtr = std::unique_ptr; - - SystemTimeFormatter(const std::string& format, TimeFieldExtractorPtr f); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - -private: - const Envoy::DateFormatter date_formatter_; - const TimeFieldExtractorPtr time_field_extractor_; -}; - -/** - * SystemTimeFormatter (FormatterProvider) for request start time from StreamInfo. - */ -class StartTimeFormatter : public SystemTimeFormatter { -public: - StartTimeFormatter(const std::string& format); -}; - -/** - * SystemTimeFormatter (FormatterProvider) for downstream cert start time from the StreamInfo's - * ConnectionInfo. - */ -class DownstreamPeerCertVStartFormatter : public SystemTimeFormatter { -public: - DownstreamPeerCertVStartFormatter(const std::string& format); -}; - -/** - * SystemTimeFormatter (FormatterProvider) for downstream cert end time from the StreamInfo's - * ConnectionInfo. - */ -class DownstreamPeerCertVEndFormatter : public SystemTimeFormatter { -public: - DownstreamPeerCertVEndFormatter(const std::string& format); -}; - -/** - * SystemTimeFormatter (FormatterProvider) for upstream cert start time from the StreamInfo's - * upstreamInfo. - */ -class UpstreamPeerCertVStartFormatter : public SystemTimeFormatter { -public: - UpstreamPeerCertVStartFormatter(const std::string& format); -}; - -/** - * SystemTimeFormatter (FormatterProvider) for upstream cert end time from the StreamInfo's - * upstreamInfo. - */ -class UpstreamPeerCertVEndFormatter : public SystemTimeFormatter { -public: - UpstreamPeerCertVEndFormatter(const std::string& format); -}; - -/** - * FormatterProvider for environment. If no valid environment value then - */ -class EnvironmentFormatter : public FormatterProvider { -public: - EnvironmentFormatter(const std::string& key, absl::optional max_length); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - -private: - ProtobufWkt::Value str_; -}; - -/** - * FormatterProvider for request headers from StreamInfo (rather than the request_headers param). - * Purely for testing. - */ -class StreamInfoRequestHeaderFormatter : public FormatterProvider, HeaderFormatter { -public: - StreamInfoRequestHeaderFormatter(const std::string& main_header, - const std::string& alternative_header, - absl::optional max_length); - - // FormatterProvider - absl::optional format(const Http::RequestHeaderMap& request_headers, - const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; - ProtobufWkt::Value formatValue(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, - const Http::ResponseTrailerMap&, const StreamInfo::StreamInfo&, - absl::string_view, AccessLog::AccessLogType) const override; -}; - } // namespace Formatter } // namespace Envoy diff --git a/source/common/grpc/async_client_impl.cc b/source/common/grpc/async_client_impl.cc index ab940d039850..7e03eaab7b28 100644 --- a/source/common/grpc/async_client_impl.cc +++ b/source/common/grpc/async_client_impl.cc @@ -194,10 +194,6 @@ void AsyncStreamImpl::onReset() { streamError(Status::WellKnownGrpcStatus::Internal); } -void AsyncStreamImpl::sendMessage(const Protobuf::Message& request, bool end_stream) { - stream_->sendData(*Common::serializeToGrpcFrame(request), end_stream); -} - void AsyncStreamImpl::sendMessageRaw(Buffer::InstancePtr&& buffer, bool end_stream) { Common::prependGrpcFrameHeader(*buffer); stream_->sendData(*buffer, end_stream); diff --git a/source/common/grpc/async_client_impl.h b/source/common/grpc/async_client_impl.h index ffd5c411cf50..84c83426258d 100644 --- a/source/common/grpc/async_client_impl.h +++ b/source/common/grpc/async_client_impl.h @@ -61,8 +61,6 @@ class AsyncStreamImpl : public RawAsyncStream, virtual void initialize(bool buffer_body_for_retry); - void sendMessage(const Protobuf::Message& request, bool end_stream); - // Http::AsyncClient::StreamCallbacks void onHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) override; void onData(Buffer::Instance& data, bool end_stream) override; diff --git a/source/common/html/BUILD b/source/common/html/BUILD index fc2b6c391ad1..12998895046f 100644 --- a/source/common/html/BUILD +++ b/source/common/html/BUILD @@ -12,4 +12,5 @@ envoy_cc_library( name = "utility_lib", srcs = ["utility.cc"], hdrs = ["utility.h"], + deps = ["@com_google_absl//absl/strings"], ) diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 6177bd4d8a59..acf2e80fdf70 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -12,9 +12,14 @@ envoy_package() envoy_cc_library( name = "async_client_lib", - srcs = ["async_client_impl.cc"], - hdrs = ["async_client_impl.h"], + srcs = [ + "async_client_impl.cc", + ], + hdrs = [ + "async_client_impl.h", + ], deps = [ + ":null_route_impl_lib", "//envoy/config:typed_metadata_interface", "//envoy/event:dispatcher_interface", "//envoy/http:async_client_interface", @@ -23,14 +28,12 @@ envoy_cc_library( "//envoy/http:header_map_interface", "//envoy/http:message_interface", "//envoy/router:context_interface", - "//envoy/router:router_interface", "//envoy/router:router_ratelimit_interface", "//envoy/router:shadow_writer_interface", "//envoy/ssl:connection_interface", "//envoy/stream_info:stream_info_interface", "//source/common/common:empty_string", "//source/common/common:linked_object", - "//source/common/router:router_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:http_tracer_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -39,6 +42,17 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "null_route_impl_lib", + srcs = ["null_route_impl.cc"], + hdrs = ["null_route_impl.h"], + deps = [ + ":hash_policy_lib", + "//envoy/router:router_interface", + "//source/common/router:router_lib", + ], +) + envoy_cc_library( name = "async_client_utility_lib", srcs = ["async_client_utility.cc"], diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index ce3ca75592b9..9bc49aeeda52 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -14,25 +14,6 @@ namespace Envoy { namespace Http { - -const std::vector> - AsyncStreamImpl::NullRateLimitPolicy::rate_limit_policy_entry_; -const AsyncStreamImpl::NullHedgePolicy AsyncStreamImpl::RouteEntryImpl::hedge_policy_; -const AsyncStreamImpl::NullRateLimitPolicy AsyncStreamImpl::RouteEntryImpl::rate_limit_policy_; -const Router::InternalRedirectPolicyImpl AsyncStreamImpl::RouteEntryImpl::internal_redirect_policy_; -const Router::PathMatcherSharedPtr AsyncStreamImpl::RouteEntryImpl::path_matcher_; -const Router::PathRewriterSharedPtr AsyncStreamImpl::RouteEntryImpl::path_rewriter_; -const std::vector AsyncStreamImpl::RouteEntryImpl::shadow_policies_; -const AsyncStreamImpl::NullVirtualHost AsyncStreamImpl::RouteEntryImpl::virtual_host_; -const AsyncStreamImpl::NullRateLimitPolicy AsyncStreamImpl::NullVirtualHost::rate_limit_policy_; -const AsyncStreamImpl::NullCommonConfig AsyncStreamImpl::NullVirtualHost::route_configuration_; -const std::multimap AsyncStreamImpl::RouteEntryImpl::opaque_config_; -const AsyncStreamImpl::NullPathMatchCriterion - AsyncStreamImpl::RouteEntryImpl::path_match_criterion_; -const AsyncStreamImpl::RouteEntryImpl::ConnectConfigOptRef - AsyncStreamImpl::RouteEntryImpl::connect_config_nullopt_; -const std::list AsyncStreamImpl::NullCommonConfig::internal_only_headers_; - AsyncClientImpl::AsyncClientImpl(Upstream::ClusterInfoConstSharedPtr cluster, Stats::Store& stats_store, Event::Dispatcher& dispatcher, const LocalInfo::LocalInfo& local_info, @@ -40,11 +21,11 @@ AsyncClientImpl::AsyncClientImpl(Upstream::ClusterInfoConstSharedPtr cluster, Random::RandomGenerator& random, Router::ShadowWriterPtr&& shadow_writer, Http::Context& http_context, Router::Context& router_context) - : cluster_(cluster), + : singleton_manager_(cm.clusterManagerFactory().singletonManager()), cluster_(cluster), config_(http_context.asyncClientStatPrefix(), local_info, *stats_store.rootScope(), cm, runtime, random, std::move(shadow_writer), true, false, false, false, false, false, {}, dispatcher.timeSource(), http_context, router_context), - dispatcher_(dispatcher), singleton_manager_(cm.clusterManagerFactory().singletonManager()) {} + dispatcher_(dispatcher) {} AsyncClientImpl::~AsyncClientImpl() { while (!active_streams_.empty()) { @@ -101,8 +82,9 @@ AsyncStreamImpl::AsyncStreamImpl(AsyncClientImpl& parent, AsyncClient::StreamCal parent.config_.async_stats_), stream_info_(Protocol::Http11, parent.dispatcher().timeSource(), nullptr), tracing_config_(Tracing::EgressConfig::get()), - route_(std::make_shared(parent_, options.timeout, options.hash_policy, - options.retry_policy)), + route_(std::make_shared(parent_.cluster_->name(), parent_.singleton_manager_, + options.timeout, options.hash_policy, + options.retry_policy)), account_(options.account_), buffer_limit_(options.buffer_limit_), send_xff_(options.send_xff) { stream_info_.dynamicMetadata().MergeFrom(options.metadata); @@ -323,10 +305,7 @@ void AsyncRequestSharedImpl::onHeaders(ResponseHeaderMapPtr&& headers, bool) { response_ = std::make_unique(std::move(headers)); } -void AsyncRequestSharedImpl::onData(Buffer::Instance& data, bool) { - streamInfo().addBytesReceived(data.length()); - response_->body().move(data); -} +void AsyncRequestSharedImpl::onData(Buffer::Instance& data, bool) { response_->body().move(data); } void AsyncRequestSharedImpl::onTrailers(ResponseTrailerMapPtr&& trailers) { response_->trailers(std::move(trailers)); diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 83cee970b40b..4d5c80527a33 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -37,7 +37,7 @@ #include "source/common/common/empty_string.h" #include "source/common/common/linked_object.h" #include "source/common/http/message_impl.h" -#include "source/common/protobuf/message_validator_impl.h" +#include "source/common/http/null_route_impl.h" #include "source/common/router/config_impl.h" #include "source/common/router/router.h" #include "source/common/stream_info/stream_info_impl.h" @@ -71,15 +71,15 @@ class AsyncClientImpl final : public AsyncClient { Stream* start(StreamCallbacks& callbacks, const AsyncClient::StreamOptions& options) override; OngoingRequest* startRequest(RequestHeaderMapPtr&& request_headers, Callbacks& callbacks, const AsyncClient::RequestOptions& options) override; + Singleton::Manager& singleton_manager_; + Upstream::ClusterInfoConstSharedPtr cluster_; Event::Dispatcher& dispatcher() override { return dispatcher_; } private: template T* internalStartRequest(T* async_request); - Upstream::ClusterInfoConstSharedPtr cluster_; Router::FilterConfig config_; Event::Dispatcher& dispatcher_; std::list> active_streams_; - Singleton::Manager& singleton_manager_; friend class AsyncStreamImpl; friend class AsyncRequestSharedImpl; @@ -153,234 +153,6 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, absl::optional> watermark_callbacks_; private: - struct NullHedgePolicy : public Router::HedgePolicy { - // Router::HedgePolicy - uint32_t initialRequests() const override { return 1; } - const envoy::type::v3::FractionalPercent& additionalRequestChance() const override { - return additional_request_chance_; - } - bool hedgeOnPerTryTimeout() const override { return false; } - - const envoy::type::v3::FractionalPercent additional_request_chance_; - }; - - struct NullRateLimitPolicy : public Router::RateLimitPolicy { - // Router::RateLimitPolicy - const std::vector>& - getApplicableRateLimit(uint64_t) const override { - return rate_limit_policy_entry_; - } - bool empty() const override { return true; } - - static const std::vector> - rate_limit_policy_entry_; - }; - - struct NullCommonConfig : public Router::CommonConfig { - const std::list& internalOnlyHeaders() const override { - return internal_only_headers_; - } - - const std::string& name() const override { return EMPTY_STRING; } - bool usesVhds() const override { return false; } - bool mostSpecificHeaderMutationsWins() const override { return false; } - uint32_t maxDirectResponseBodySizeBytes() const override { return 0; } - - static const std::list internal_only_headers_; - }; - - struct NullVirtualHost : public Router::VirtualHost { - // Router::VirtualHost - Stats::StatName statName() const override { return {}; } - const Router::RateLimitPolicy& rateLimitPolicy() const override { return rate_limit_policy_; } - const Router::CorsPolicy* corsPolicy() const override { return nullptr; } - const Router::CommonConfig& routeConfig() const override { return route_configuration_; } - bool includeAttemptCountInRequest() const override { return false; } - bool includeAttemptCountInResponse() const override { return false; } - bool includeIsTimeoutRetryHeader() const override { return false; } - uint32_t retryShadowBufferLimit() const override { - return std::numeric_limits::max(); - } - const Router::RouteSpecificFilterConfig* - mostSpecificPerFilterConfig(const std::string&) const override { - return nullptr; - } - void traversePerFilterConfig( - const std::string&, - std::function) const override {} - static const NullRateLimitPolicy rate_limit_policy_; - static const NullCommonConfig route_configuration_; - }; - - struct NullPathMatchCriterion : public Router::PathMatchCriterion { - Router::PathMatchType matchType() const override { return Router::PathMatchType::None; } - const std::string& matcher() const override { return EMPTY_STRING; } - }; - - struct RouteEntryImpl : public Router::RouteEntry { - RouteEntryImpl( - AsyncClientImpl& parent, const absl::optional& timeout, - const Protobuf::RepeatedPtrField& - hash_policy, - const absl::optional& retry_policy) - : cluster_name_(parent.cluster_->name()), timeout_(timeout) { - if (!hash_policy.empty()) { - hash_policy_ = std::make_unique(hash_policy); - } - if (retry_policy.has_value()) { - // ProtobufMessage::getStrictValidationVisitor() ? how often do we do this? - Upstream::RetryExtensionFactoryContextImpl factory_context(parent.singleton_manager_); - retry_policy_ = std::make_unique( - retry_policy.value(), ProtobufMessage::getNullValidationVisitor(), factory_context); - } else { - retry_policy_ = std::make_unique(); - } - } - - // Router::RouteEntry - const std::string& clusterName() const override { return cluster_name_; } - const Router::RouteStatsContextOptRef routeStatsContext() const override { - return Router::RouteStatsContextOptRef(); - } - Http::Code clusterNotFoundResponseCode() const override { - return Http::Code::InternalServerError; - } - const Router::CorsPolicy* corsPolicy() const override { return nullptr; } - absl::optional - currentUrlPathAfterRewrite(const Http::RequestHeaderMap&) const override { - return {}; - } - void finalizeRequestHeaders(Http::RequestHeaderMap&, const StreamInfo::StreamInfo&, - bool) const override {} - Http::HeaderTransforms requestHeaderTransforms(const StreamInfo::StreamInfo&, - bool) const override { - return {}; - } - void finalizeResponseHeaders(Http::ResponseHeaderMap&, - const StreamInfo::StreamInfo&) const override {} - Http::HeaderTransforms responseHeaderTransforms(const StreamInfo::StreamInfo&, - bool) const override { - return {}; - } - const HashPolicy* hashPolicy() const override { return hash_policy_.get(); } - const Router::HedgePolicy& hedgePolicy() const override { return hedge_policy_; } - const Router::MetadataMatchCriteria* metadataMatchCriteria() const override { return nullptr; } - Upstream::ResourcePriority priority() const override { - return Upstream::ResourcePriority::Default; - } - const Router::RateLimitPolicy& rateLimitPolicy() const override { return rate_limit_policy_; } - const Router::RetryPolicy& retryPolicy() const override { return *retry_policy_; } - const Router::InternalRedirectPolicy& internalRedirectPolicy() const override { - return internal_redirect_policy_; - } - const Router::PathMatcherSharedPtr& pathMatcher() const override { return path_matcher_; } - const Router::PathRewriterSharedPtr& pathRewriter() const override { return path_rewriter_; } - uint32_t retryShadowBufferLimit() const override { - return std::numeric_limits::max(); - } - const std::vector& shadowPolicies() const override { - return shadow_policies_; - } - std::chrono::milliseconds timeout() const override { - if (timeout_) { - return timeout_.value(); - } else { - return std::chrono::milliseconds(0); - } - } - bool usingNewTimeouts() const override { return false; } - absl::optional idleTimeout() const override { return absl::nullopt; } - absl::optional maxStreamDuration() const override { - return absl::nullopt; - } - absl::optional grpcTimeoutHeaderMax() const override { - return absl::nullopt; - } - absl::optional grpcTimeoutHeaderOffset() const override { - return absl::nullopt; - } - absl::optional maxGrpcTimeout() const override { - return absl::nullopt; - } - absl::optional grpcTimeoutOffset() const override { - return absl::nullopt; - } - const Router::VirtualCluster* virtualCluster(const Http::HeaderMap&) const override { - return nullptr; - } - const Router::TlsContextMatchCriteria* tlsContextMatchCriteria() const override { - return nullptr; - } - const std::multimap& opaqueConfig() const override { - return opaque_config_; - } - const Router::VirtualHost& virtualHost() const override { return virtual_host_; } - bool autoHostRewrite() const override { return false; } - bool appendXfh() const override { return false; } - bool includeVirtualHostRateLimits() const override { return true; } - const Router::PathMatchCriterion& pathMatchCriterion() const override { - return path_match_criterion_; - } - - const ConnectConfigOptRef connectConfig() const override { return connect_config_nullopt_; } - - bool includeAttemptCountInRequest() const override { return false; } - bool includeAttemptCountInResponse() const override { return false; } - const Router::RouteEntry::UpgradeMap& upgradeMap() const override { return upgrade_map_; } - const std::string& routeName() const override { return route_name_; } - const Router::EarlyDataPolicy& earlyDataPolicy() const override { return *early_data_policy_; } - - std::unique_ptr hash_policy_; - std::unique_ptr retry_policy_; - - static const NullHedgePolicy hedge_policy_; - static const NullRateLimitPolicy rate_limit_policy_; - static const Router::InternalRedirectPolicyImpl internal_redirect_policy_; - static const Router::PathMatcherSharedPtr path_matcher_; - static const Router::PathRewriterSharedPtr path_rewriter_; - static const std::vector shadow_policies_; - static const NullVirtualHost virtual_host_; - static const std::multimap opaque_config_; - static const NullPathMatchCriterion path_match_criterion_; - - Router::RouteEntry::UpgradeMap upgrade_map_; - const std::string& cluster_name_; - absl::optional timeout_; - static const ConnectConfigOptRef connect_config_nullopt_; - const std::string route_name_; - // Pass early data option config through StreamOptions. - std::unique_ptr early_data_policy_{ - new Router::DefaultEarlyDataPolicy(true)}; - }; - - struct RouteImpl : public Router::Route { - RouteImpl(AsyncClientImpl& parent, const absl::optional& timeout, - const Protobuf::RepeatedPtrField& - hash_policy, - const absl::optional& retry_policy) - : route_entry_(parent, timeout, hash_policy, retry_policy), typed_metadata_({}) {} - - // Router::Route - const Router::DirectResponseEntry* directResponseEntry() const override { return nullptr; } - const Router::RouteEntry* routeEntry() const override { return &route_entry_; } - const Router::Decorator* decorator() const override { return nullptr; } - const Router::RouteTracing* tracingConfig() const override { return nullptr; } - const Router::RouteSpecificFilterConfig* - mostSpecificPerFilterConfig(const std::string&) const override { - return nullptr; - } - void traversePerFilterConfig( - const std::string&, - std::function) const override {} - const envoy::config::core::v3::Metadata& metadata() const override { return metadata_; } - const Envoy::Config::TypedMetadata& typedMetadata() const override { return typed_metadata_; } - bool filterDisabled(absl::string_view) const override { return false; } - - RouteEntryImpl route_entry_; - const envoy::config::core::v3::Metadata metadata_; - const Envoy::Config::TypedMetadataImpl typed_metadata_; - }; - void cleanup(); void closeRemote(bool end_stream); bool complete() { return local_closed_ && remote_closed_; } @@ -496,7 +268,7 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, StreamInfo::StreamInfoImpl stream_info_; Tracing::NullSpan active_span_; const Tracing::Config& tracing_config_; - std::shared_ptr route_; + std::shared_ptr route_; uint32_t high_watermark_calls_{}; bool local_closed_{}; bool remote_closed_{}; diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index b31735e5c5df..e0c07d6d8f42 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -111,7 +111,12 @@ ConnectionManagerImpl::ConnectionManagerImpl(ConnectionManagerConfig& config, /*server_name=*/config_.serverName(), /*proxy_status_config=*/config_.proxyStatusConfig())), refresh_rtt_after_request_( - Runtime::runtimeFeatureEnabled("envoy.reloadable_features.refresh_rtt_after_request")) {} + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.refresh_rtt_after_request")) { + ENVOY_LOG_ONCE_IF( + trace, accept_new_http_stream_ == nullptr, + "LoadShedPoint envoy.load_shed_points.http_connection_manager_decode_headers is not " + "found. Is it configured?"); +} const ResponseHeaderMap& ConnectionManagerImpl::continueHeader() { static const auto headers = createHeaderMap( @@ -973,6 +978,17 @@ uint32_t ConnectionManagerImpl::ActiveStream::localPort() { return ip->port(); } +namespace { +bool streamErrorOnlyErrors(absl::string_view error_details) { + // Pre UHV HCM did not respect stream_error_on_invalid_http_message + // and only sent 400 for specific errors. + // TODO(#28555): make these errors respect the stream_error_on_invalid_http_message + return error_details == UhvResponseCodeDetail::get().FragmentInUrlPath || + error_details == UhvResponseCodeDetail::get().EscapedSlashesInPath || + error_details == UhvResponseCodeDetail::get().Percent00InPath; +} +} // namespace + bool ConnectionManagerImpl::ActiveStream::validateHeaders() { if (header_validator_) { auto validation_result = header_validator_->validateRequestHeaders(*request_headers_); @@ -1017,12 +1033,8 @@ bool ConnectionManagerImpl::ActiveStream::validateHeaders() { resetStream(); } else { sendLocalReply(response_code, "", modify_headers, grpc_status, failure_details); - // Pre UHV HCM did not respect stream_error_on_invalid_http_message - // and only sent 400 when :path contained fragment or encoded slashes - // TODO(#28555): make these errors respect the stream_error_on_invalid_http_message if (!response_encoder_->streamErrorOnInvalidHttpMessage() && - failure_details != UhvResponseCodeDetail::get().FragmentInUrlPath && - failure_details != UhvResponseCodeDetail::get().EscapedSlashesInPath) { + !streamErrorOnlyErrors(failure_details)) { connection_manager_.handleCodecError(failure_details); } } diff --git a/source/common/http/filter_chain_helper.h b/source/common/http/filter_chain_helper.h index fe925ef30610..b6b516620127 100644 --- a/source/common/http/filter_chain_helper.h +++ b/source/common/http/filter_chain_helper.h @@ -55,10 +55,11 @@ class FilterChainHelper : Logger::Loggable { FilterChainHelper(FilterConfigProviderManager& filter_config_provider_manager, Server::Configuration::ServerFactoryContext& server_context, - FilterCtx& factory_context, const std::string& stats_prefix) + Upstream::ClusterManager& cluster_manager, FilterCtx& factory_context, + const std::string& stats_prefix) : filter_config_provider_manager_(filter_config_provider_manager), - server_context_(server_context), factory_context_(factory_context), - stats_prefix_(stats_prefix) {} + server_context_(server_context), cluster_manager_(cluster_manager), + factory_context_(factory_context), stats_prefix_(stats_prefix) {} using FiltersList = Protobuf::RepeatedPtrField< envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter>; @@ -148,13 +149,14 @@ class FilterChainHelper : Logger::Loggable { } auto filter_config_provider = filter_config_provider_manager_.createDynamicFilterConfigProvider( - config_discovery, name, server_context_, factory_context_, last_filter_in_current_config, - filter_chain_type, nullptr); + config_discovery, name, server_context_, factory_context_, cluster_manager_, + last_filter_in_current_config, filter_chain_type, nullptr); filter_factories.push_back(std::move(filter_config_provider)); } FilterConfigProviderManager& filter_config_provider_manager_; Server::Configuration::ServerFactoryContext& server_context_; + Upstream::ClusterManager& cluster_manager_; FilterCtx& factory_context_; const std::string& stats_prefix_; }; diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 5f71e1cfb5f4..f430ebb0996a 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -908,6 +908,13 @@ void DownstreamFilterManager::sendLocalReply( !filter_manager_callbacks_.informationalHeaders().has_value()) { // If the response has not started at all, send the response through the filter chain. + if (auto cb = filter_manager_callbacks_.downstreamCallbacks(); cb.has_value()) { + // The initial route maybe never be set or the cached route maybe cleared by the filters. + // This will force route refreshment if there is not a cached route to avoid potential + // route refreshment in the response filter chain. + cb->route(nullptr); + } + // We only prepare a local reply to execute later if we're actively // invoking filters to avoid re-entrant in filters. if (avoid_reentrant_filter_invocation_during_local_reply_ && diff --git a/source/common/http/header_map_impl.cc b/source/common/http/header_map_impl.cc index 43f7697cba04..d296a22c2eb7 100644 --- a/source/common/http/header_map_impl.cc +++ b/source/common/http/header_map_impl.cc @@ -18,6 +18,8 @@ namespace Envoy { namespace Http { +bool HeaderStringValidator::disable_validation_for_tests_ = false; + namespace { constexpr absl::string_view DelimiterForInlineHeaders{","}; diff --git a/source/common/http/http1/balsa_parser.cc b/source/common/http/http1/balsa_parser.cc index b69800c6e0e5..8f4671a52045 100644 --- a/source/common/http/http1/balsa_parser.cc +++ b/source/common/http/http1/balsa_parser.cc @@ -151,7 +151,7 @@ BalsaParser::BalsaParser(MessageType type, ParserCallbacks* connection, size_t m quiche::HttpValidationPolicy http_validation_policy; http_validation_policy.disallow_header_continuation_lines = true; http_validation_policy.require_header_colon = true; - http_validation_policy.disallow_multiple_content_length = false; + http_validation_policy.disallow_multiple_content_length = true; http_validation_policy.disallow_transfer_encoding_with_content_length = false; http_validation_policy.validate_transfer_encoding = false; http_validation_policy.require_content_length_if_body_required = false; @@ -378,6 +378,9 @@ void BalsaParser::HandleError(BalsaFrameEnums::ErrorCode error_code) { case BalsaFrameEnums::INVALID_HEADER_CHARACTER: error_message_ = "header value contains invalid chars"; break; + case BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS: + error_message_ = "HPE_UNEXPECTED_CONTENT_LENGTH"; + break; default: error_message_ = BalsaFrameEnums::ErrorCodeToString(error_code); } diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index 7fe3e00f6168..c0c5f9a1599b 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -1071,6 +1071,9 @@ ServerConnectionImpl::ServerConnectionImpl( headers_with_underscores_action_(headers_with_underscores_action), abort_dispatch_( overload_manager.getLoadShedPoint("envoy.load_shed_points.http1_server_abort_dispatch")) { + ENVOY_LOG_ONCE_IF(trace, abort_dispatch_ == nullptr, + "LoadShedPoint envoy.load_shed_points.http1_server_abort_dispatch is not " + "found. Is it configured?"); owned_output_buffer_->setWatermarks(connection.bufferLimit()); // Inform parent output_buffer_ = owned_output_buffer_.get(); diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index ad7c29b423dc..7ddc09e0c448 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -2056,6 +2056,9 @@ ServerConnectionImpl::ServerConnectionImpl( callbacks_(callbacks), headers_with_underscores_action_(headers_with_underscores_action), should_send_go_away_on_dispatch_(overload_manager.getLoadShedPoint( "envoy.load_shed_points.http2_server_go_away_on_dispatch")) { + ENVOY_LOG_ONCE_IF(trace, should_send_go_away_on_dispatch_ == nullptr, + "LoadShedPoint envoy.load_shed_points.http2_server_go_away_on_dispatch is not " + "found. Is it configured?"); Http2Options h2_options(http2_options, max_request_headers_kb); auto visitor = std::make_unique( diff --git a/source/common/http/null_route_impl.cc b/source/common/http/null_route_impl.cc new file mode 100644 index 000000000000..e562829c084c --- /dev/null +++ b/source/common/http/null_route_impl.cc @@ -0,0 +1,23 @@ +#include "null_route_impl.h" + +#include + +namespace Envoy { +namespace Http { +const std::vector> + NullRateLimitPolicy::rate_limit_policy_entry_; +const NullHedgePolicy RouteEntryImpl::hedge_policy_; +const NullRateLimitPolicy RouteEntryImpl::rate_limit_policy_; +const Router::InternalRedirectPolicyImpl RouteEntryImpl::internal_redirect_policy_; +const Router::PathMatcherSharedPtr RouteEntryImpl::path_matcher_; +const Router::PathRewriterSharedPtr RouteEntryImpl::path_rewriter_; +const std::vector RouteEntryImpl::shadow_policies_; +const NullVirtualHost RouteEntryImpl::virtual_host_; +const NullRateLimitPolicy NullVirtualHost::rate_limit_policy_; +const NullCommonConfig NullVirtualHost::route_configuration_; +const std::multimap RouteEntryImpl::opaque_config_; +const NullPathMatchCriterion RouteEntryImpl::path_match_criterion_; +const RouteEntryImpl::ConnectConfigOptRef RouteEntryImpl::connect_config_nullopt_; +const std::list NullCommonConfig::internal_only_headers_; +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/null_route_impl.h b/source/common/http/null_route_impl.h new file mode 100644 index 000000000000..5b42fca13429 --- /dev/null +++ b/source/common/http/null_route_impl.h @@ -0,0 +1,242 @@ +#pragma once + +#include "envoy/router/router.h" + +#include "source/common/http/hash_policy.h" +#include "source/common/protobuf/message_validator_impl.h" +#include "source/common/router/config_impl.h" +#include "source/common/upstream/retry_factory.h" +#include "source/extensions/early_data/default_early_data_policy.h" + +namespace Envoy { +namespace Http { + +struct NullHedgePolicy : public Router::HedgePolicy { + // Router::HedgePolicy + uint32_t initialRequests() const override { return 1; } + const envoy::type::v3::FractionalPercent& additionalRequestChance() const override { + return additional_request_chance_; + } + bool hedgeOnPerTryTimeout() const override { return false; } + + const envoy::type::v3::FractionalPercent additional_request_chance_; +}; + +struct NullRateLimitPolicy : public Router::RateLimitPolicy { + // Router::RateLimitPolicy + const std::vector>& + getApplicableRateLimit(uint64_t) const override { + return rate_limit_policy_entry_; + } + bool empty() const override { return true; } + + static const std::vector> + rate_limit_policy_entry_; +}; + +struct NullCommonConfig : public Router::CommonConfig { + const std::list& internalOnlyHeaders() const override { + return internal_only_headers_; + } + + const std::string& name() const override { return EMPTY_STRING; } + bool usesVhds() const override { return false; } + bool mostSpecificHeaderMutationsWins() const override { return false; } + uint32_t maxDirectResponseBodySizeBytes() const override { return 0; } + + static const std::list internal_only_headers_; +}; + +struct NullVirtualHost : public Router::VirtualHost { + // Router::VirtualHost + Stats::StatName statName() const override { return {}; } + const Router::RateLimitPolicy& rateLimitPolicy() const override { return rate_limit_policy_; } + const Router::CorsPolicy* corsPolicy() const override { return nullptr; } + const Router::CommonConfig& routeConfig() const override { return route_configuration_; } + bool includeAttemptCountInRequest() const override { return false; } + bool includeAttemptCountInResponse() const override { return false; } + bool includeIsTimeoutRetryHeader() const override { return false; } + uint32_t retryShadowBufferLimit() const override { return std::numeric_limits::max(); } + const Router::RouteSpecificFilterConfig* + mostSpecificPerFilterConfig(const std::string&) const override { + return nullptr; + } + void traversePerFilterConfig( + const std::string&, + std::function) const override {} + static const NullRateLimitPolicy rate_limit_policy_; + static const NullCommonConfig route_configuration_; +}; + +struct NullPathMatchCriterion : public Router::PathMatchCriterion { + Router::PathMatchType matchType() const override { return Router::PathMatchType::None; } + const std::string& matcher() const override { return EMPTY_STRING; } +}; + +struct RouteEntryImpl : public Router::RouteEntry { + RouteEntryImpl( + const std::string& cluster_name, Singleton::Manager& singleton_manager, + const absl::optional& timeout, + const Protobuf::RepeatedPtrField& + hash_policy, + const absl::optional& retry_policy) + : cluster_name_(cluster_name), timeout_(timeout) { + if (!hash_policy.empty()) { + hash_policy_ = std::make_unique(hash_policy); + } + if (retry_policy.has_value()) { + // ProtobufMessage::getStrictValidationVisitor() ? how often do we do this? + Upstream::RetryExtensionFactoryContextImpl factory_context(singleton_manager); + retry_policy_ = std::make_unique( + retry_policy.value(), ProtobufMessage::getNullValidationVisitor(), factory_context); + } else { + retry_policy_ = std::make_unique(); + } + } + + // Router::RouteEntry + const std::string& clusterName() const override { return cluster_name_; } + const Router::RouteStatsContextOptRef routeStatsContext() const override { + return Router::RouteStatsContextOptRef(); + } + Http::Code clusterNotFoundResponseCode() const override { + return Http::Code::InternalServerError; + } + const Router::CorsPolicy* corsPolicy() const override { return nullptr; } + absl::optional + currentUrlPathAfterRewrite(const Http::RequestHeaderMap&) const override { + return {}; + } + void finalizeRequestHeaders(Http::RequestHeaderMap&, const StreamInfo::StreamInfo&, + bool) const override {} + Http::HeaderTransforms requestHeaderTransforms(const StreamInfo::StreamInfo&, + bool) const override { + return {}; + } + void finalizeResponseHeaders(Http::ResponseHeaderMap&, + const StreamInfo::StreamInfo&) const override {} + Http::HeaderTransforms responseHeaderTransforms(const StreamInfo::StreamInfo&, + bool) const override { + return {}; + } + const HashPolicy* hashPolicy() const override { return hash_policy_.get(); } + const Router::HedgePolicy& hedgePolicy() const override { return hedge_policy_; } + const Router::MetadataMatchCriteria* metadataMatchCriteria() const override { return nullptr; } + Upstream::ResourcePriority priority() const override { + return Upstream::ResourcePriority::Default; + } + const Router::RateLimitPolicy& rateLimitPolicy() const override { return rate_limit_policy_; } + const Router::RetryPolicy& retryPolicy() const override { return *retry_policy_; } + const Router::InternalRedirectPolicy& internalRedirectPolicy() const override { + return internal_redirect_policy_; + } + const Router::PathMatcherSharedPtr& pathMatcher() const override { return path_matcher_; } + const Router::PathRewriterSharedPtr& pathRewriter() const override { return path_rewriter_; } + uint32_t retryShadowBufferLimit() const override { return std::numeric_limits::max(); } + const std::vector& shadowPolicies() const override { + return shadow_policies_; + } + std::chrono::milliseconds timeout() const override { + if (timeout_) { + return timeout_.value(); + } else { + return std::chrono::milliseconds(0); + } + } + bool usingNewTimeouts() const override { return false; } + absl::optional idleTimeout() const override { return absl::nullopt; } + absl::optional maxStreamDuration() const override { + return absl::nullopt; + } + absl::optional grpcTimeoutHeaderMax() const override { + return absl::nullopt; + } + absl::optional grpcTimeoutHeaderOffset() const override { + return absl::nullopt; + } + absl::optional maxGrpcTimeout() const override { + return absl::nullopt; + } + absl::optional grpcTimeoutOffset() const override { + return absl::nullopt; + } + const Router::VirtualCluster* virtualCluster(const Http::HeaderMap&) const override { + return nullptr; + } + const Router::TlsContextMatchCriteria* tlsContextMatchCriteria() const override { + return nullptr; + } + const std::multimap& opaqueConfig() const override { + return opaque_config_; + } + const Router::VirtualHost& virtualHost() const override { return virtual_host_; } + bool autoHostRewrite() const override { return false; } + bool appendXfh() const override { return false; } + bool includeVirtualHostRateLimits() const override { return true; } + const Router::PathMatchCriterion& pathMatchCriterion() const override { + return path_match_criterion_; + } + + const ConnectConfigOptRef connectConfig() const override { return connect_config_nullopt_; } + + bool includeAttemptCountInRequest() const override { return false; } + bool includeAttemptCountInResponse() const override { return false; } + const Router::RouteEntry::UpgradeMap& upgradeMap() const override { return upgrade_map_; } + const std::string& routeName() const override { return route_name_; } + const Router::EarlyDataPolicy& earlyDataPolicy() const override { return *early_data_policy_; } + + std::unique_ptr hash_policy_; + std::unique_ptr retry_policy_; + + static const NullHedgePolicy hedge_policy_; + static const NullRateLimitPolicy rate_limit_policy_; + static const Router::InternalRedirectPolicyImpl internal_redirect_policy_; + static const Router::PathMatcherSharedPtr path_matcher_; + static const Router::PathRewriterSharedPtr path_rewriter_; + static const std::vector shadow_policies_; + static const NullVirtualHost virtual_host_; + static const std::multimap opaque_config_; + static const NullPathMatchCriterion path_match_criterion_; + + Router::RouteEntry::UpgradeMap upgrade_map_; + const std::string cluster_name_; + absl::optional timeout_; + static const ConnectConfigOptRef connect_config_nullopt_; + const std::string route_name_; + // Pass early data option config through StreamOptions. + std::unique_ptr early_data_policy_{ + new Router::DefaultEarlyDataPolicy(true)}; +}; + +struct NullRouteImpl : public Router::Route { + NullRouteImpl(const std::string cluster_name, Singleton::Manager& singleton_manager, + const absl::optional& timeout = {}, + const Protobuf::RepeatedPtrField& + hash_policy = {}, + const absl::optional& retry_policy = {}) + : route_entry_(cluster_name, singleton_manager, timeout, hash_policy, retry_policy), + typed_metadata_({}) {} + + // Router::Route + const Router::DirectResponseEntry* directResponseEntry() const override { return nullptr; } + const Router::RouteEntry* routeEntry() const override { return &route_entry_; } + const Router::Decorator* decorator() const override { return nullptr; } + const Router::RouteTracing* tracingConfig() const override { return nullptr; } + const Router::RouteSpecificFilterConfig* + mostSpecificPerFilterConfig(const std::string&) const override { + return nullptr; + } + void traversePerFilterConfig( + const std::string&, + std::function) const override {} + const envoy::config::core::v3::Metadata& metadata() const override { return metadata_; } + const Envoy::Config::TypedMetadata& typedMetadata() const override { return typed_metadata_; } + bool filterDisabled(absl::string_view) const override { return false; } + + RouteEntryImpl route_entry_; + const envoy::config::core::v3::Metadata metadata_; + const Envoy::Config::TypedMetadataImpl typed_metadata_; +}; + +} // namespace Http +} // namespace Envoy diff --git a/source/common/io/BUILD b/source/common/io/BUILD index b8684f33999e..1dab89acf9df 100644 --- a/source/common/io/BUILD +++ b/source/common/io/BUILD @@ -8,16 +8,6 @@ licenses(["notice"]) # Apache 2 envoy_package() -envoy_cc_library( - name = "io_uring_interface", - hdrs = [ - "io_uring.h", - ], - deps = [ - "//source/common/network:address_lib", - ], -) - envoy_cc_library( name = "io_uring_impl_lib", srcs = [ @@ -29,6 +19,7 @@ envoy_cc_library( external_deps = ["uring"], tags = ["nocompdb"], deps = [ - ":io_uring_interface", + "//envoy/common/io:io_uring_interface", + "//envoy/thread_local:thread_local_interface", ], ) diff --git a/source/common/io/io_uring_impl.cc b/source/common/io/io_uring_impl.cc index ff9e77d50076..03c658f6d12c 100644 --- a/source/common/io/io_uring_impl.cc +++ b/source/common/io/io_uring_impl.cc @@ -34,12 +34,15 @@ void IoUringFactoryImpl::onServerInitialized() { } IoUringImpl::IoUringImpl(uint32_t io_uring_size, bool use_submission_queue_polling) - : io_uring_size_(io_uring_size), cqes_(io_uring_size_, nullptr) { + : cqes_(io_uring_size, nullptr) { struct io_uring_params p {}; if (use_submission_queue_polling) { p.flags |= IORING_SETUP_SQPOLL; } - int ret = io_uring_queue_init_params(io_uring_size_, &ring_, &p); + // TODO (soulxu): According to the man page: `By default, the CQ ring will have twice the number + // of entries as specified by entries for the SQ ring`. But currently we only use the same size + // with SQ ring. We will figure out better handle of entries number in the future. + int ret = io_uring_queue_init_params(io_uring_size, &ring_, &p); RELEASE_ASSERT(ret == 0, fmt::format("unable to initialize io_uring: {}", errorDetails(-ret))); } @@ -47,13 +50,18 @@ IoUringImpl::~IoUringImpl() { io_uring_queue_exit(&ring_); } os_fd_t IoUringImpl::registerEventfd() { ASSERT(!isEventfdRegistered()); - event_fd_ = eventfd(0, 0); + // Mark the eventfd as non-blocking. since after injected completion is added. the eventfd + // will be activated to trigger the event callback. For the case of only injected completion + // is added and no actual iouring event. Then non-blocking can avoid the reading of eventfd + // blocking. + event_fd_ = eventfd(0, EFD_NONBLOCK); int res = io_uring_register_eventfd(&ring_, event_fd_); RELEASE_ASSERT(res == 0, fmt::format("unable to register eventfd: {}", errorDetails(-res))); return event_fd_; } void IoUringImpl::unregisterEventfd() { + ASSERT(isEventfdRegistered()); int res = io_uring_unregister_eventfd(&ring_); RELEASE_ASSERT(res == 0, fmt::format("unable to unregister eventfd: {}", errorDetails(-res))); SET_SOCKET_INVALID(event_fd_); @@ -61,24 +69,48 @@ void IoUringImpl::unregisterEventfd() { bool IoUringImpl::isEventfdRegistered() const { return SOCKET_VALID(event_fd_); } -void IoUringImpl::forEveryCompletion(CompletionCb completion_cb) { +void IoUringImpl::forEveryCompletion(const CompletionCb& completion_cb) { ASSERT(SOCKET_VALID(event_fd_)); eventfd_t v; - int ret = eventfd_read(event_fd_, &v); - RELEASE_ASSERT(ret == 0, "unable to drain eventfd"); + while (true) { + int ret = eventfd_read(event_fd_, &v); + if (ret != 0) { + ASSERT(errno == EAGAIN); + break; + } + } - unsigned count = io_uring_peek_batch_cqe(&ring_, cqes_.data(), io_uring_size_); + unsigned count = io_uring_peek_batch_cqe(&ring_, cqes_.data(), cqes_.size()); for (unsigned i = 0; i < count; ++i) { struct io_uring_cqe* cqe = cqes_[i]; - completion_cb(reinterpret_cast(cqe->user_data), cqe->res); + completion_cb(reinterpret_cast(cqe->user_data), cqe->res, false); } + io_uring_cq_advance(&ring_, count); + + ENVOY_LOG(trace, "the num of injected completion is {}", injected_completions_.size()); + // TODO(soulxu): Add bound here to avoid too many completion to stuck the thread too + // long. + // Iterate the injected completion. + while (!injected_completions_.empty()) { + auto& completion = injected_completions_.front(); + completion_cb(completion.user_data_, completion.result_, true); + // The socket may closed in the completion_cb and all the related completions are + // removed. + if (injected_completions_.empty()) { + break; + } + injected_completions_.pop_front(); + } } IoUringResult IoUringImpl::prepareAccept(os_fd_t fd, struct sockaddr* remote_addr, - socklen_t* remote_addr_len, void* user_data) { + socklen_t* remote_addr_len, Request* user_data) { + ENVOY_LOG(trace, "prepare close for fd = {}", fd); + // TODO (soulxu): Handling the case of CQ ring is overflow. + ASSERT(!(*(ring_.sq.kflags) & IORING_SQ_CQ_OVERFLOW)); struct io_uring_sqe* sqe = io_uring_get_sqe(&ring_); if (sqe == nullptr) { return IoUringResult::Failed; @@ -91,7 +123,10 @@ IoUringResult IoUringImpl::prepareAccept(os_fd_t fd, struct sockaddr* remote_add IoUringResult IoUringImpl::prepareConnect(os_fd_t fd, const Network::Address::InstanceConstSharedPtr& address, - void* user_data) { + Request* user_data) { + ENVOY_LOG(trace, "prepare connect for fd = {}", fd); + // TODO (soulxu): Handling the case of CQ ring is overflow. + ASSERT(!(*(ring_.sq.kflags) & IORING_SQ_CQ_OVERFLOW)); struct io_uring_sqe* sqe = io_uring_get_sqe(&ring_); if (sqe == nullptr) { return IoUringResult::Failed; @@ -103,7 +138,10 @@ IoUringResult IoUringImpl::prepareConnect(os_fd_t fd, } IoUringResult IoUringImpl::prepareReadv(os_fd_t fd, const struct iovec* iovecs, unsigned nr_vecs, - off_t offset, void* user_data) { + off_t offset, Request* user_data) { + ENVOY_LOG(trace, "prepare readv for fd = {}", fd); + // TODO (soulxu): Handling the case of CQ ring is overflow. + ASSERT(!(*(ring_.sq.kflags) & IORING_SQ_CQ_OVERFLOW)); struct io_uring_sqe* sqe = io_uring_get_sqe(&ring_); if (sqe == nullptr) { return IoUringResult::Failed; @@ -115,7 +153,10 @@ IoUringResult IoUringImpl::prepareReadv(os_fd_t fd, const struct iovec* iovecs, } IoUringResult IoUringImpl::prepareWritev(os_fd_t fd, const struct iovec* iovecs, unsigned nr_vecs, - off_t offset, void* user_data) { + off_t offset, Request* user_data) { + ENVOY_LOG(trace, "prepare writev for fd = {}", fd); + // TODO (soulxu): Handling the case of CQ ring is overflow. + ASSERT(!(*(ring_.sq.kflags) & IORING_SQ_CQ_OVERFLOW)); struct io_uring_sqe* sqe = io_uring_get_sqe(&ring_); if (sqe == nullptr) { return IoUringResult::Failed; @@ -126,7 +167,10 @@ IoUringResult IoUringImpl::prepareWritev(os_fd_t fd, const struct iovec* iovecs, return IoUringResult::Ok; } -IoUringResult IoUringImpl::prepareClose(os_fd_t fd, void* user_data) { +IoUringResult IoUringImpl::prepareClose(os_fd_t fd, Request* user_data) { + ENVOY_LOG(trace, "prepare close for fd = {}", fd); + // TODO (soulxu): Handling the case of CQ ring is overflow. + ASSERT(!(*(ring_.sq.kflags) & IORING_SQ_CQ_OVERFLOW)); struct io_uring_sqe* sqe = io_uring_get_sqe(&ring_); if (sqe == nullptr) { return IoUringResult::Failed; @@ -143,5 +187,23 @@ IoUringResult IoUringImpl::submit() { return res == -EBUSY ? IoUringResult::Busy : IoUringResult::Ok; } +void IoUringImpl::injectCompletion(os_fd_t fd, Request* user_data, int32_t result) { + injected_completions_.emplace_back(fd, user_data, result); + ENVOY_LOG(trace, "inject completion, fd = {}, req = {}, num injects = {}", fd, + fmt::ptr(user_data), injected_completions_.size()); +} + +void IoUringImpl::removeInjectedCompletion(os_fd_t fd) { + ENVOY_LOG(trace, "remove injected completions for fd = {}, size = {}", fd, + injected_completions_.size()); + injected_completions_.remove_if([fd](InjectedCompletion& completion) { + if (fd == completion.fd_) { + // Release the user data before remove this completion. + delete reinterpret_cast(completion.user_data_); + } + return fd == completion.fd_; + }); +} + } // namespace Io } // namespace Envoy diff --git a/source/common/io/io_uring_impl.h b/source/common/io/io_uring_impl.h index af6a46297d9f..5afe05df170e 100644 --- a/source/common/io/io_uring_impl.h +++ b/source/common/io/io_uring_impl.h @@ -1,8 +1,9 @@ #pragma once +#include "envoy/common/io/io_uring.h" #include "envoy/thread_local/thread_local.h" -#include "source/common/io/io_uring.h" +#include "source/common/common/logger.h" #include "liburing.h" @@ -11,7 +12,18 @@ namespace Io { bool isIoUringSupported(); -class IoUringImpl : public IoUring, public ThreadLocal::ThreadLocalObject { +struct InjectedCompletion { + InjectedCompletion(os_fd_t fd, Request* user_data, int32_t result) + : fd_(fd), user_data_(user_data), result_(result) {} + + const os_fd_t fd_; + Request* user_data_; + const int32_t result_; +}; + +class IoUringImpl : public IoUring, + public ThreadLocal::ThreadLocalObject, + protected Logger::Loggable { public: IoUringImpl(uint32_t io_uring_size, bool use_submission_queue_polling); ~IoUringImpl() override; @@ -19,23 +31,25 @@ class IoUringImpl : public IoUring, public ThreadLocal::ThreadLocalObject { os_fd_t registerEventfd() override; void unregisterEventfd() override; bool isEventfdRegistered() const override; - void forEveryCompletion(CompletionCb completion_cb) override; + void forEveryCompletion(const CompletionCb& completion_cb) override; IoUringResult prepareAccept(os_fd_t fd, struct sockaddr* remote_addr, socklen_t* remote_addr_len, - void* user_data) override; + Request* user_data) override; IoUringResult prepareConnect(os_fd_t fd, const Network::Address::InstanceConstSharedPtr& address, - void* user_data) override; + Request* user_data) override; IoUringResult prepareReadv(os_fd_t fd, const struct iovec* iovecs, unsigned nr_vecs, off_t offset, - void* user_data) override; + Request* user_data) override; IoUringResult prepareWritev(os_fd_t fd, const struct iovec* iovecs, unsigned nr_vecs, - off_t offset, void* user_data) override; - IoUringResult prepareClose(os_fd_t fd, void* user_data) override; + off_t offset, Request* user_data) override; + IoUringResult prepareClose(os_fd_t fd, Request* user_data) override; IoUringResult submit() override; + void injectCompletion(os_fd_t fd, Request* user_data, int32_t result) override; + void removeInjectedCompletion(os_fd_t fd) override; private: - const uint32_t io_uring_size_; struct io_uring ring_ {}; std::vector cqes_; os_fd_t event_fd_{INVALID_SOCKET}; + std::list injected_completions_; }; class IoUringFactoryImpl : public IoUringFactory { diff --git a/source/common/network/tcp_listener_impl.cc b/source/common/network/tcp_listener_impl.cc index 0f5c3d5aadda..2ebef7a30c1d 100644 --- a/source/common/network/tcp_listener_impl.cc +++ b/source/common/network/tcp_listener_impl.cc @@ -140,6 +140,9 @@ void TcpListenerImpl::configureLoadShedPoints( Server::LoadShedPointProvider& load_shed_point_provider) { listener_accept_ = load_shed_point_provider.getLoadShedPoint("envoy.load_shed_points.tcp_listener_accept"); + ENVOY_LOG_ONCE_MISC_IF( + trace, listener_accept_ == nullptr, + "LoadShedPoint envoy.load_shed_points.tcp_listener_accept is not found. Is it configured?"); } } // namespace Network diff --git a/source/common/protobuf/BUILD b/source/common/protobuf/BUILD index a64428db3f72..47c07d6ebe71 100644 --- a/source/common/protobuf/BUILD +++ b/source/common/protobuf/BUILD @@ -4,7 +4,11 @@ load( "envoy_cc_library", "envoy_package", ) -load("//bazel:envoy_select.bzl", "envoy_select_enable_yaml") +load( + "//bazel:envoy_select.bzl", + "envoy_select_enable_lite_protos", + "envoy_select_enable_yaml", +) licenses(["notice"]) # Apache 2 @@ -47,7 +51,10 @@ envoy_cc_library( external_deps = [ "protobuf", ], - deps = [":cc_wkt_protos"], + deps = [ + ":cc_wkt_protos", + "//envoy/common:base_includes", + ], ) envoy_cc_library( @@ -87,6 +94,105 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "create_reflectable_message_lib", + srcs = [ + "create_reflectable_message.cc", + ], + external_deps = [ + "protobuf", + ], + deps = [ + "utility_lib_header", + ] + envoy_select_enable_lite_protos([ + "//bazel/cc_proto_descriptor_library:create_dynamic_message", + "//bazel/cc_proto_descriptor_library:text_format_transcoder", + "@envoy_api//envoy/admin/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/annotations:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/common/key_value/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/common/matcher/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/listener/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/metrics/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/overload/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/route/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/data/accesslog/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/data/cluster/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/data/core/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/access_loggers/stream/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/common/dynamic_forward_proxy/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/common/matching/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/compression/brotli/decompressor/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/compression/gzip/decompressor/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/early_data/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/common/dependency/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/common/matcher/action/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/http/alternate_protocols_cache/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/http/buffer/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/http/decompressor/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/http/dynamic_forward_proxy/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/http/health_check/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/http/on_demand/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/http/router/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/http/upstream_codec/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/listener/proxy_protocol/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/filters/udp/dns_filter/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/http/header_formatters/preserve_case/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/http/header_validators/envoy_default/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/http/original_ip_detection/xff/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/load_balancing_policies/common/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/load_balancing_policies/least_request/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/load_balancing_policies/random/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/load_balancing_policies/round_robin/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/matching/common_inputs/network/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/network/dns_resolver/apple/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/network/dns_resolver/cares/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/network/socket_interface/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/path/match/uri_template/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/path/rewrite/uri_template/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/quic/connection_id_generator/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/quic/crypto_stream/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/quic/proof_source/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/regex_engines/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/request_id/uuid/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/udp_packet_writer/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/upstreams/http/generic/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/upstreams/http/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/extensions/upstreams/tcp/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/cluster/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/endpoint/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/extension/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/health/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/listener/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/load_stats/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/metrics/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/ratelimit/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/route/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/runtime/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/service/secret/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/type/matcher:pkg_cc_proto_descriptor", + "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/type/metadata/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/type/tracing/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/type/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/watchdog/v3:pkg_cc_proto_descriptor", + ]), +) + envoy_cc_library( name = "utility_lib", srcs = ["utility.cc"], @@ -121,6 +227,7 @@ envoy_cc_library( "visitor_helper.h", ], deps = [ + ":create_reflectable_message_lib", ":message_validator_lib", ":protobuf", ":utility_lib_header", diff --git a/source/common/protobuf/create_reflectable_message.cc b/source/common/protobuf/create_reflectable_message.cc new file mode 100644 index 000000000000..04ec37c1c4be --- /dev/null +++ b/source/common/protobuf/create_reflectable_message.cc @@ -0,0 +1,426 @@ +#include "source/common/protobuf/utility.h" + +#ifdef ENVOY_ENABLE_FULL_PROTOS + +namespace Envoy { +Protobuf::ReflectableMessage createReflectableMessage(const Protobuf::Message& message) { + return const_cast(&message); +} +} // namespace Envoy + +#else + +#include "bazel/cc_proto_descriptor_library/create_dynamic_message.h" +#include "bazel/cc_proto_descriptor_library/text_format_transcoder.h" +#include "bazel/cc_proto_descriptor_library/file_descriptor_info.h" + +#include "envoy/config/core/v3/base_descriptor.pb.h" +#include "envoy/admin/v3/certs_descriptor.pb.h" +#include "envoy/admin/v3/clusters_descriptor.pb.h" +#include "envoy/admin/v3/config_dump_descriptor.pb.h" +#include "envoy/admin/v3/config_dump_shared_descriptor.pb.h" +#include "envoy/admin/v3/init_dump_descriptor.pb.h" +#include "envoy/admin/v3/listeners_descriptor.pb.h" +#include "envoy/admin/v3/memory_descriptor.pb.h" +#include "envoy/admin/v3/metrics_descriptor.pb.h" +#include "envoy/admin/v3/mutex_stats_descriptor.pb.h" +#include "envoy/admin/v3/server_info_descriptor.pb.h" +#include "envoy/admin/v3/tap_descriptor.pb.h" +#include "envoy/annotations/deprecation_descriptor.pb.h" +#include "envoy/annotations/resource_descriptor.pb.h" +#include "envoy/config/accesslog/v3/accesslog_descriptor.pb.h" +#include "envoy/config/bootstrap/v3/bootstrap_descriptor.pb.h" +#include "envoy/config/cluster/v3/circuit_breaker_descriptor.pb.h" +#include "envoy/config/cluster/v3/cluster_descriptor.pb.h" +#include "envoy/config/cluster/v3/filter_descriptor.pb.h" +#include "envoy/config/cluster/v3/outlier_detection_descriptor.pb.h" +#include "envoy/config/common/key_value/v3/config_descriptor.pb.h" +#include "envoy/config/common/matcher/v3/matcher_descriptor.pb.h" +#include "envoy/config/core/v3/address_descriptor.pb.h" +#include "envoy/config/core/v3/backoff_descriptor.pb.h" +#include "envoy/config/core/v3/base_descriptor.pb.h" +#include "envoy/config/core/v3/config_source_descriptor.pb.h" +#include "envoy/config/core/v3/event_service_config_descriptor.pb.h" +#include "envoy/config/core/v3/extension_descriptor.pb.h" +#include "envoy/config/core/v3/grpc_method_list_descriptor.pb.h" +#include "envoy/config/core/v3/grpc_service_descriptor.pb.h" +#include "envoy/config/core/v3/health_check_descriptor.pb.h" +#include "envoy/config/core/v3/http_uri_descriptor.pb.h" +#include "envoy/config/core/v3/protocol_descriptor.pb.h" +#include "envoy/config/core/v3/proxy_protocol_descriptor.pb.h" +#include "envoy/config/core/v3/resolver_descriptor.pb.h" +#include "envoy/config/core/v3/socket_option_descriptor.pb.h" +#include "envoy/config/core/v3/substitution_format_string_descriptor.pb.h" +#include "envoy/config/core/v3/udp_socket_config_descriptor.pb.h" +#include "envoy/config/endpoint/v3/endpoint_descriptor.pb.h" +#include "envoy/config/endpoint/v3/endpoint_components_descriptor.pb.h" +#include "envoy/config/endpoint/v3/load_report_descriptor.pb.h" +#include "envoy/config/listener/v3/api_listener_descriptor.pb.h" +#include "envoy/config/listener/v3/listener_descriptor.pb.h" +#include "envoy/config/listener/v3/listener_components_descriptor.pb.h" +#include "envoy/config/listener/v3/quic_config_descriptor.pb.h" +#include "envoy/config/listener/v3/udp_listener_config_descriptor.pb.h" +#include "envoy/config/metrics/v3/metrics_service_descriptor.pb.h" +#include "envoy/config/metrics/v3/stats_descriptor.pb.h" +#include "envoy/config/overload/v3/overload_descriptor.pb.h" +#include "envoy/config/route/v3/route_descriptor.pb.h" +#include "envoy/config/route/v3/route_components_descriptor.pb.h" +#include "envoy/config/route/v3/scoped_route_descriptor.pb.h" +#include "envoy/config/trace/v3/datadog_descriptor.pb.h" +#include "envoy/config/trace/v3/dynamic_ot_descriptor.pb.h" +#include "envoy/config/trace/v3/http_tracer_descriptor.pb.h" +#include "envoy/config/trace/v3/lightstep_descriptor.pb.h" +#include "envoy/config/trace/v3/opencensus_descriptor.pb.h" +#include "envoy/config/trace/v3/opentelemetry_descriptor.pb.h" +#include "envoy/config/trace/v3/service_descriptor.pb.h" +#include "envoy/config/trace/v3/skywalking_descriptor.pb.h" +#include "envoy/config/trace/v3/trace_descriptor.pb.h" +#include "envoy/config/trace/v3/xray_descriptor.pb.h" +#include "envoy/config/trace/v3/zipkin_descriptor.pb.h" +#include "envoy/data/accesslog/v3/accesslog_descriptor.pb.h" +#include "envoy/data/cluster/v3/outlier_detection_event_descriptor.pb.h" +#include "envoy/data/core/v3/health_check_event_descriptor.pb.h" +#include "envoy/extensions/access_loggers/file/v3/file_descriptor.pb.h" +#include "envoy/extensions/access_loggers/stream/v3/stream_descriptor.pb.h" +#include "envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster_descriptor.pb.h" +#include "envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache_descriptor.pb.h" +#include "envoy/extensions/common/matching/v3/extension_matcher_descriptor.pb.h" +#include "envoy/extensions/compression/brotli/decompressor/v3/brotli_descriptor.pb.h" +#include "envoy/extensions/compression/gzip/decompressor/v3/gzip_descriptor.pb.h" +#include "envoy/extensions/early_data/v3/default_early_data_policy_descriptor.pb.h" +#include "envoy/extensions/filters/common/dependency/v3/dependency_descriptor.pb.h" +#include "envoy/extensions/filters/common/matcher/action/v3/skip_action_descriptor.pb.h" +#include "envoy/extensions/filters/http/alternate_protocols_cache/v3/alternate_protocols_cache_descriptor.pb.h" +#include "envoy/extensions/filters/http/buffer/v3/buffer_descriptor.pb.h" +#include "envoy/extensions/filters/http/decompressor/v3/decompressor_descriptor.pb.h" +#include "envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy_descriptor.pb.h" +#include "envoy/extensions/filters/http/health_check/v3/health_check_descriptor.pb.h" +#include "envoy/extensions/filters/http/on_demand/v3/on_demand_descriptor.pb.h" +#include "envoy/extensions/filters/http/router/v3/router_descriptor.pb.h" +#include "envoy/extensions/filters/http/upstream_codec/v3/upstream_codec_descriptor.pb.h" +#include "envoy/extensions/filters/listener/proxy_protocol/v3/proxy_protocol_descriptor.pb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager_descriptor.pb.h" +#include "envoy/extensions/filters/udp/dns_filter/v3/dns_filter_descriptor.pb.h" +#include "envoy/extensions/http/header_formatters/preserve_case/v3/preserve_case_descriptor.pb.h" +#include "envoy/extensions/http/header_validators/envoy_default/v3/header_validator_descriptor.pb.h" +#include "envoy/extensions/http/original_ip_detection/xff/v3/xff_descriptor.pb.h" +#include "envoy/extensions/load_balancing_policies/common/v3/common_descriptor.pb.h" +#include "envoy/extensions/load_balancing_policies/least_request/v3/least_request_descriptor.pb.h" +#include "envoy/extensions/load_balancing_policies/random/v3/random_descriptor.pb.h" +#include "envoy/extensions/load_balancing_policies/round_robin/v3/round_robin_descriptor.pb.h" +#include "envoy/extensions/matching/common_inputs/network/v3/network_inputs_descriptor.pb.h" +#include "envoy/extensions/network/dns_resolver/apple/v3/apple_dns_resolver_descriptor.pb.h" +#include "envoy/extensions/network/dns_resolver/cares/v3/cares_dns_resolver_descriptor.pb.h" +#include "envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver_descriptor.pb.h" +#include "envoy/extensions/network/socket_interface/v3/default_socket_interface_descriptor.pb.h" +#include "envoy/extensions/path/match/uri_template/v3/uri_template_match_descriptor.pb.h" +#include "envoy/extensions/path/rewrite/uri_template/v3/uri_template_rewrite_descriptor.pb.h" +#include "envoy/extensions/quic/connection_id_generator/v3/envoy_deterministic_connection_id_generator_descriptor.pb.h" +#include "envoy/extensions/quic/crypto_stream/v3/crypto_stream_descriptor.pb.h" +#include "envoy/extensions/quic/proof_source/v3/proof_source_descriptor.pb.h" +#include "envoy/extensions/regex_engines/v3/google_re2_descriptor.pb.h" +#include "envoy/extensions/request_id/uuid/v3/uuid_descriptor.pb.h" +#include "envoy/extensions/transport_sockets/http_11_proxy/v3/upstream_http_11_connect_descriptor.pb.h" +#include "envoy/extensions/transport_sockets/quic/v3/quic_transport_descriptor.pb.h" +#include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer_descriptor.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/cert_descriptor.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/common_descriptor.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/secret_descriptor.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls_descriptor.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config_descriptor.pb.h" +#include "envoy/extensions/udp_packet_writer/v3/udp_default_writer_factory_descriptor.pb.h" +#include "envoy/extensions/udp_packet_writer/v3/udp_gso_batch_writer_factory_descriptor.pb.h" +#include "envoy/extensions/upstreams/http/generic/v3/generic_connection_pool_descriptor.pb.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options_descriptor.pb.h" +#include "envoy/extensions/upstreams/tcp/v3/tcp_protocol_options_descriptor.pb.h" +#include "envoy/service/cluster/v3/cds_descriptor.pb.h" +#include "envoy/service/discovery/v3/ads_descriptor.pb.h" +#include "envoy/service/discovery/v3/discovery_descriptor.pb.h" +#include "envoy/service/endpoint/v3/eds_descriptor.pb.h" +#include "envoy/service/endpoint/v3/leds_descriptor.pb.h" +#include "envoy/service/extension/v3/config_discovery_descriptor.pb.h" +#include "envoy/service/health/v3/hds_descriptor.pb.h" +#include "envoy/service/listener/v3/lds_descriptor.pb.h" +#include "envoy/service/load_stats/v3/lrs_descriptor.pb.h" +#include "envoy/service/metrics/v3/metrics_service_descriptor.pb.h" +#include "envoy/service/ratelimit/v3/rls_descriptor.pb.h" +#include "envoy/service/route/v3/rds_descriptor.pb.h" +#include "envoy/service/route/v3/srds_descriptor.pb.h" +#include "envoy/service/runtime/v3/rtds_descriptor.pb.h" +#include "envoy/service/secret/v3/sds_descriptor.pb.h" +#include "envoy/type/matcher/metadata_descriptor.pb.h" +#include "envoy/type/matcher/node_descriptor.pb.h" +#include "envoy/type/matcher/number_descriptor.pb.h" +#include "envoy/type/matcher/path_descriptor.pb.h" +#include "envoy/type/matcher/regex_descriptor.pb.h" +#include "envoy/type/matcher/string_descriptor.pb.h" +#include "envoy/type/matcher/struct_descriptor.pb.h" +#include "envoy/type/matcher/value_descriptor.pb.h" +#include "envoy/type/matcher/v3/filter_state_descriptor.pb.h" +#include "envoy/type/matcher/v3/http_inputs_descriptor.pb.h" +#include "envoy/type/matcher/v3/metadata_descriptor.pb.h" +#include "envoy/type/matcher/v3/node_descriptor.pb.h" +#include "envoy/type/matcher/v3/number_descriptor.pb.h" +#include "envoy/type/matcher/v3/path_descriptor.pb.h" +#include "envoy/type/matcher/v3/regex_descriptor.pb.h" +#include "envoy/type/matcher/v3/status_code_input_descriptor.pb.h" +#include "envoy/type/matcher/v3/string_descriptor.pb.h" +#include "envoy/type/matcher/v3/struct_descriptor.pb.h" +#include "envoy/type/matcher/v3/value_descriptor.pb.h" +#include "envoy/type/metadata/v3/metadata_descriptor.pb.h" +#include "envoy/type/tracing/v3/custom_tag_descriptor.pb.h" +#include "envoy/type/v3/hash_policy_descriptor.pb.h" +#include "envoy/type/v3/http_descriptor.pb.h" +#include "envoy/type/v3/http_status_descriptor.pb.h" +#include "envoy/type/v3/percent_descriptor.pb.h" +#include "envoy/type/v3/range_descriptor.pb.h" +#include "envoy/type/v3/ratelimit_strategy_descriptor.pb.h" +#include "envoy/type/v3/ratelimit_unit_descriptor.pb.h" +#include "envoy/type/v3/semantic_version_descriptor.pb.h" +#include "envoy/type/v3/token_bucket_descriptor.pb.h" +#include "envoy/watchdog/v3/abort_action_descriptor.pb.h" + +using ::cc_proto_descriptor_library::internal::FileDescriptorInfo; + +namespace { + +std::unique_ptr createTranscoder() { + std::unique_ptr transcoder = + std::make_unique(); + std::vector file_descriptors = { + protobuf::reflection::envoy_config_core_v3_base::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_certs::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_clusters::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_config_dump::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_config_dump_shared::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_init_dump::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_listeners::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_memory::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_metrics::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_mutex_stats::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_server_info::kFileDescriptorInfo, + protobuf::reflection::envoy_admin_v3_tap::kFileDescriptorInfo, + protobuf::reflection::envoy_annotations_deprecation::kFileDescriptorInfo, + protobuf::reflection::envoy_annotations_resource::kFileDescriptorInfo, + protobuf::reflection::envoy_config_accesslog_v3_accesslog::kFileDescriptorInfo, + protobuf::reflection::envoy_config_bootstrap_v3_bootstrap::kFileDescriptorInfo, + protobuf::reflection::envoy_config_cluster_v3_circuit_breaker::kFileDescriptorInfo, + protobuf::reflection::envoy_config_cluster_v3_cluster::kFileDescriptorInfo, + protobuf::reflection::envoy_config_cluster_v3_filter::kFileDescriptorInfo, + protobuf::reflection::envoy_config_cluster_v3_outlier_detection::kFileDescriptorInfo, + protobuf::reflection::envoy_config_common_key_value_v3_config::kFileDescriptorInfo, + protobuf::reflection::envoy_config_common_matcher_v3_matcher::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_address::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_backoff::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_base::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_config_source::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_event_service_config::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_extension::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_grpc_method_list::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_grpc_service::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_health_check::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_http_uri::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_protocol::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_proxy_protocol::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_resolver::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_socket_option::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_substitution_format_string::kFileDescriptorInfo, + protobuf::reflection::envoy_config_core_v3_udp_socket_config::kFileDescriptorInfo, + protobuf::reflection::envoy_config_endpoint_v3_endpoint::kFileDescriptorInfo, + protobuf::reflection::envoy_config_endpoint_v3_endpoint_components::kFileDescriptorInfo, + protobuf::reflection::envoy_config_endpoint_v3_load_report::kFileDescriptorInfo, + protobuf::reflection::envoy_config_listener_v3_api_listener::kFileDescriptorInfo, + protobuf::reflection::envoy_config_listener_v3_listener::kFileDescriptorInfo, + protobuf::reflection::envoy_config_listener_v3_listener_components::kFileDescriptorInfo, + protobuf::reflection::envoy_config_listener_v3_quic_config::kFileDescriptorInfo, + protobuf::reflection::envoy_config_listener_v3_udp_listener_config::kFileDescriptorInfo, + protobuf::reflection::envoy_config_metrics_v3_metrics_service::kFileDescriptorInfo, + protobuf::reflection::envoy_config_metrics_v3_stats::kFileDescriptorInfo, + protobuf::reflection::envoy_config_overload_v3_overload::kFileDescriptorInfo, + protobuf::reflection::envoy_config_route_v3_route::kFileDescriptorInfo, + protobuf::reflection::envoy_config_route_v3_route_components::kFileDescriptorInfo, + protobuf::reflection::envoy_config_route_v3_scoped_route::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_datadog::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_dynamic_ot::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_http_tracer::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_lightstep::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_opencensus::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_opentelemetry::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_service::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_skywalking::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_trace::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_xray::kFileDescriptorInfo, + protobuf::reflection::envoy_config_trace_v3_zipkin::kFileDescriptorInfo, + protobuf::reflection::envoy_data_accesslog_v3_accesslog::kFileDescriptorInfo, + protobuf::reflection::envoy_data_cluster_v3_outlier_detection_event::kFileDescriptorInfo, + protobuf::reflection::envoy_data_core_v3_health_check_event::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_access_loggers_file_v3_file::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_access_loggers_stream_v3_stream::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_common_matching_v3_extension_matcher:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_compression_brotli_decompressor_v3_brotli:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_compression_gzip_decompressor_v3_gzip:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_early_data_v3_default_early_data_policy:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_common_dependency_v3_dependency:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_common_matcher_action_v3_skip_action:: + kFileDescriptorInfo, + protobuf::reflection:: + envoy_extensions_filters_http_alternate_protocols_cache_v3_alternate_protocols_cache:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_http_buffer_v3_buffer::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_http_decompressor_v3_decompressor:: + kFileDescriptorInfo, + protobuf::reflection:: + envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_http_health_check_v3_health_check:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_http_on_demand_v3_on_demand:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_http_router_v3_router::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_http_upstream_codec_v3_upstream_codec:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_listener_proxy_protocol_v3_proxy_protocol:: + kFileDescriptorInfo, + protobuf::reflection:: + envoy_extensions_filters_network_http_connection_manager_v3_http_connection_manager:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_filters_udp_dns_filter_v3_dns_filter:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_http_header_formatters_preserve_case_v3_preserve_case:: + kFileDescriptorInfo, + protobuf::reflection:: + envoy_extensions_http_header_validators_envoy_default_v3_header_validator:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_http_original_ip_detection_xff_v3_xff:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_load_balancing_policies_common_v3_common:: + kFileDescriptorInfo, + protobuf::reflection:: + envoy_extensions_load_balancing_policies_least_request_v3_least_request:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_load_balancing_policies_random_v3_random:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_load_balancing_policies_round_robin_v3_round_robin:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_matching_common_inputs_network_v3_network_inputs:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_network_dns_resolver_apple_v3_apple_dns_resolver:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_network_dns_resolver_cares_v3_cares_dns_resolver:: + kFileDescriptorInfo, + protobuf::reflection:: + envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_network_socket_interface_v3_default_socket_interface:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_path_match_uri_template_v3_uri_template_match:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_path_rewrite_uri_template_v3_uri_template_rewrite:: + kFileDescriptorInfo, + protobuf::reflection:: + envoy_extensions_quic_connection_id_generator_v3_envoy_deterministic_connection_id_generator:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_quic_crypto_stream_v3_crypto_stream:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_quic_proof_source_v3_proof_source::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_regex_engines_v3_google_re2::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_request_id_uuid_v3_uuid::kFileDescriptorInfo, + protobuf::reflection:: + envoy_extensions_transport_sockets_http_11_proxy_v3_upstream_http_11_connect:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_transport_sockets_quic_v3_quic_transport:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_transport_sockets_raw_buffer_v3_raw_buffer:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_transport_sockets_tls_v3_cert::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_transport_sockets_tls_v3_common::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_transport_sockets_tls_v3_secret::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_transport_sockets_tls_v3_tls::kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_transport_sockets_tls_v3_tls_spiffe_validator_config:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_udp_packet_writer_v3_udp_default_writer_factory:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_udp_packet_writer_v3_udp_gso_batch_writer_factory:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_upstreams_http_generic_v3_generic_connection_pool:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_upstreams_http_v3_http_protocol_options:: + kFileDescriptorInfo, + protobuf::reflection::envoy_extensions_upstreams_tcp_v3_tcp_protocol_options:: + kFileDescriptorInfo, + protobuf::reflection::envoy_service_cluster_v3_cds::kFileDescriptorInfo, + protobuf::reflection::envoy_service_discovery_v3_ads::kFileDescriptorInfo, + protobuf::reflection::envoy_service_discovery_v3_discovery::kFileDescriptorInfo, + protobuf::reflection::envoy_service_endpoint_v3_eds::kFileDescriptorInfo, + protobuf::reflection::envoy_service_endpoint_v3_leds::kFileDescriptorInfo, + protobuf::reflection::envoy_service_extension_v3_config_discovery::kFileDescriptorInfo, + protobuf::reflection::envoy_service_health_v3_hds::kFileDescriptorInfo, + protobuf::reflection::envoy_service_listener_v3_lds::kFileDescriptorInfo, + protobuf::reflection::envoy_service_load_stats_v3_lrs::kFileDescriptorInfo, + protobuf::reflection::envoy_service_metrics_v3_metrics_service::kFileDescriptorInfo, + protobuf::reflection::envoy_service_ratelimit_v3_rls::kFileDescriptorInfo, + protobuf::reflection::envoy_service_route_v3_rds::kFileDescriptorInfo, + protobuf::reflection::envoy_service_route_v3_srds::kFileDescriptorInfo, + protobuf::reflection::envoy_service_runtime_v3_rtds::kFileDescriptorInfo, + protobuf::reflection::envoy_service_secret_v3_sds::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_metadata::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_node::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_number::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_path::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_regex::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_string::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_struct::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_value::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_filter_state::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_http_inputs::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_metadata::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_node::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_number::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_path::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_regex::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_status_code_input::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_string::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_struct::kFileDescriptorInfo, + protobuf::reflection::envoy_type_matcher_v3_value::kFileDescriptorInfo, + protobuf::reflection::envoy_type_metadata_v3_metadata::kFileDescriptorInfo, + protobuf::reflection::envoy_type_tracing_v3_custom_tag::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_hash_policy::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_http::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_http_status::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_percent::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_range::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_ratelimit_strategy::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_ratelimit_unit::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_semantic_version::kFileDescriptorInfo, + protobuf::reflection::envoy_type_v3_token_bucket::kFileDescriptorInfo, + protobuf::reflection::envoy_watchdog_v3_abort_action::kFileDescriptorInfo, + }; + for (const FileDescriptorInfo& descriptor : file_descriptors) { + transcoder->loadFileDescriptors(descriptor); + } + return transcoder; +} +} // namespace + +namespace Envoy { + +Protobuf::ReflectableMessage createReflectableMessage(const Protobuf::Message& message) { + // This transcoder is used by createDynamicMessage() to convert an instance of a MessageLite + // subclass into an instance of Message. This Message instance has reflection capabilities + // but does not have the per-field accessors that the generated C++ subclasses have. + // As such it is only useful for reflection uses. + static std::unique_ptr transcoder = + createTranscoder(); + return createDynamicMessage(*transcoder, message); +} +} // namespace Envoy +#endif diff --git a/source/common/protobuf/protobuf.h b/source/common/protobuf/protobuf.h index df24ccb62a9f..35d5bd813e33 100644 --- a/source/common/protobuf/protobuf.h +++ b/source/common/protobuf/protobuf.h @@ -29,20 +29,129 @@ #include "google/protobuf/util/type_resolver_util.h" #include "google/protobuf/wrappers.pb.h" -namespace Envoy { +#ifdef ENVOY_ENABLE_FULL_PROTOS + +namespace google::protobuf { +using ReflectableMessage = ::google::protobuf::Message*; +} // namespace google::protobuf +namespace Envoy { // All references to google::protobuf in Envoy need to be made via the // Envoy::Protobuf namespace. This is required to allow remapping of protobuf to // alternative implementations during import into other repositories. E.g. at // Google we have more than one protobuf implementation. namespace Protobuf = google::protobuf; +} // namespace Envoy + +#else + +// Forward declarations +namespace google::protobuf { +class FileDescriptorSet; +} + +namespace Envoy { +class MessageLiteDifferencer; +// All references to google::protobuf in Envoy need to be made via the +// Envoy::Protobuf namespace. This is required to allow remapping of protobuf to +// alternative implementations during import into other repositories. E.g. at +// Google we have more than one protobuf implementation. +namespace Protobuf { + +using Closure = ::google::protobuf::Closure; + +using ::google::protobuf::Arena; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::BytesValue; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::Descriptor; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::DescriptorPool; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::DescriptorPoolDatabase; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::DynamicCastToGenerated; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::DynamicMessageFactory; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::EnumValueDescriptor; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::FieldDescriptor; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::FieldMask; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::FileDescriptor; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::FileDescriptorProto; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::FileDescriptorSet; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::Map; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::MapPair; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::MessageFactory; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::MethodDescriptor; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::OneofDescriptor; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::Reflection; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::RepeatedField; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::RepeatedFieldBackInserter; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::RepeatedPtrField; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::RepeatedPtrFieldBackInserter; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::TextFormat; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::Type; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::UInt32Value; // NOLINT(misc-unused-using-decls) + +using Message = ::google::protobuf::MessageLite; + +template const T* DynamicCastToGenerated(const Message* from) { + return static_cast(from); +} + +template T* DynamicCastToGenerated(Message* from) { + const Message* message_const = from; + return const_cast(DynamicCastToGenerated(message_const)); +} + +using ReflectableMessage = std::unique_ptr<::google::protobuf::Message>; +using uint32 = uint32_t; +using int32 = int32_t; + +namespace internal { +using ::google::protobuf::internal::WireFormatLite; // NOLINT(misc-unused-using-decls) +} // namespace internal + +namespace io { +using ::google::protobuf::io::ArrayOutputStream; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::io::CodedInputStream; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::io::CodedOutputStream; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::io::IstreamInputStream; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::io::OstreamOutputStream; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::io::StringOutputStream; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::io::ZeroCopyInputStream; // NOLINT(misc-unused-using-decls) +} // namespace io + +namespace util { +using ::google::protobuf::util::JsonStringToMessage; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::util::MessageToJsonString; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::util::NewTypeResolverForDescriptorPool; // NOLINT(misc-unused-using-decls) +using MessageDifferencer = MessageLiteDifferencer; +using ::google::protobuf::util::JsonParseOptions; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::util::JsonPrintOptions; // NOLINT(misc-unused-using-decls) +using ::google::protobuf::util::TimeUtil; // NOLINT(misc-unused-using-decls) +} // namespace util + +} // namespace Protobuf + +class MessageLiteDifferencer { +public: + // NOLINTNEXTLINE(readability-identifier-naming) + static bool Equals(const Protobuf::Message& message1, const Protobuf::Message& message2); + + // NOLINTNEXTLINE(readability-identifier-naming) + static bool Equivalent(const Protobuf::Message& message1, const Protobuf::Message& message2); +}; + +using ConstMessagePtrVector = std::vector>; + +} // namespace Envoy + +#endif + +namespace Envoy { + // Allows mapping from google::protobuf::util to other util libraries. -namespace ProtobufUtil = google::protobuf::util; +namespace ProtobufUtil = ::google::protobuf::util; // Protobuf well-known types (WKT) should be referenced via the ProtobufWkt // namespace. -namespace ProtobufWkt = google::protobuf; +namespace ProtobufWkt = ::google::protobuf; // Alternative protobuf implementations might not have the same basic types. // Below we provide wrappers to facilitate remapping of the type during import. @@ -54,4 +163,7 @@ using ConstMessagePtrVector = std::vectorHasField(message, field); + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); + bool default_value = !reflection->HasField(*reflectable_message, field); - const Protobuf::EnumValueDescriptor* enum_value_descriptor = reflection->GetEnum(message, field); + const Protobuf::EnumValueDescriptor* enum_value_descriptor = + reflection->GetEnum(*reflectable_message, field); if (!enum_value_descriptor->options().deprecated()) { return; } @@ -194,7 +214,8 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { : validation_visitor_(validation_visitor), runtime_(runtime) {} void onField(const Protobuf::Message& message, const Protobuf::FieldDescriptor& field) override { - const Protobuf::Reflection* reflection = message.GetReflection(); + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); + const Protobuf::Reflection* reflection = reflectable_message->GetReflection(); absl::string_view filename = filenameFromPath(field.file()->name()); // Before we check to see if the field is in use, see if there's a @@ -203,8 +224,8 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { validation_visitor_); // If this field is not in use, continue. - if ((field.is_repeated() && reflection->FieldSize(message, &field) == 0) || - (!field.is_repeated() && !reflection->HasField(message, &field))) { + if ((field.is_repeated() && reflection->FieldSize(*reflectable_message, &field) == 0) || + (!field.is_repeated() && !reflection->HasField(*reflectable_message, &field))) { return; } @@ -230,7 +251,8 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { void onMessage(const Protobuf::Message& message, absl::Span parents, bool) override { - if (message.GetDescriptor() + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); + if (reflectable_message->GetDescriptor() ->options() .GetExtension(xds::annotations::v3::message_status) .work_in_progress()) { @@ -239,17 +261,20 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { } const auto& udpa_file_options = - message.GetDescriptor()->file()->options().GetExtension(udpa::annotations::file_status); + reflectable_message->GetDescriptor()->file()->options().GetExtension( + udpa::annotations::file_status); const auto& xds_file_options = - message.GetDescriptor()->file()->options().GetExtension(xds::annotations::v3::file_status); + reflectable_message->GetDescriptor()->file()->options().GetExtension( + xds::annotations::v3::file_status); if (udpa_file_options.work_in_progress() || xds_file_options.work_in_progress()) { - validation_visitor_.onWorkInProgress( - fmt::format("message '{}' is contained in proto file '{}' marked as work-in-progress. {}", - message.GetTypeName(), message.GetDescriptor()->file()->name(), WipWarning)); + validation_visitor_.onWorkInProgress(fmt::format( + "message '{}' is contained in proto file '{}' marked as work-in-progress. {}", + message.GetTypeName(), reflectable_message->GetDescriptor()->file()->name(), WipWarning)); } // Reject unknown fields. - const auto& unknown_fields = message.GetReflection()->GetUnknownFields(message); + const auto& unknown_fields = + reflectable_message->GetReflection()->GetUnknownFields(*reflectable_message); if (!unknown_fields.empty()) { std::string error_msg; for (int n = 0; n < unknown_fields.field_count(); ++n) { @@ -292,12 +317,14 @@ class PgvCheckVisitor : public ProtobufMessage::ConstProtoVisitor { public: void onMessage(const Protobuf::Message& message, absl::Span, bool was_any_or_top_level) override { + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); std::string err; // PGV verification is itself recursive up to the point at which it hits an Any message. As // such, to avoid N^2 checking of the tree, we only perform an additional check at the point // at which PGV would have stopped because it does not itself check within Any messages. - if (was_any_or_top_level && !pgv::BaseValidator::AbstractCheckMessage(message, &err)) { - ProtoExceptionUtil::throwProtoValidationException(err, message); + if (was_any_or_top_level && + !pgv::BaseValidator::AbstractCheckMessage(*reflectable_message, &err)) { + ProtoExceptionUtil::throwProtoValidationException(err, *reflectable_message); } } @@ -311,20 +338,41 @@ void MessageUtil::recursivePgvCheck(const Protobuf::Message& message) { ProtobufMessage::traverseMessage(visitor, message, true); } +void MessageUtil::packFrom(ProtobufWkt::Any& any_message, const Protobuf::Message& message) { +#ifdef ENVOY_ENABLE_FULL_PROTOS + any_message.PackFrom(message); +#else + any_message.set_type_url(message.GetTypeName()); + any_message.set_value(message.SerializeAsString()); +#endif +} + void MessageUtil::unpackTo(const ProtobufWkt::Any& any_message, Protobuf::Message& message) { +#ifdef ENVOY_ENABLE_FULL_PROTOS if (!any_message.UnpackTo(&message)) { throw EnvoyException(fmt::format("Unable to unpack as {}: {}", message.GetDescriptor()->full_name(), any_message.DebugString())); +#else + if (!message.ParseFromString(any_message.value())) { + throw EnvoyException( + fmt::format("Unable to unpack as {}: {}", message.GetTypeName(), any_message.type_url())); +#endif } } absl::Status MessageUtil::unpackToNoThrow(const ProtobufWkt::Any& any_message, Protobuf::Message& message) { +#ifdef ENVOY_ENABLE_FULL_PROTOS if (!any_message.UnpackTo(&message)) { return absl::InternalError(absl::StrCat("Unable to unpack as ", message.GetDescriptor()->full_name(), ": ", any_message.DebugString())); +#else + if (!message.ParseFromString(any_message.value())) { + return absl::InternalError( + absl::StrCat("Unable to unpack as ", message.GetTypeName(), ": ", any_message.type_url())); +#endif } // Ok Status is returned if `UnpackTo` succeeded. return absl::OkStatus(); @@ -371,29 +419,31 @@ using Transform = std::functionGetDescriptor(); + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message); + const auto* opaque_descriptor = reflectable_message->GetDescriptor(); if (opaque_descriptor->full_name() != opaque_type_name) { return false; } // Find descriptors for the `type_url` and `value` fields. The `type_url` field must not be // empty, but `value` may be (in which case our work is done). - const auto* reflection = message->GetReflection(); + const auto* reflection = reflectable_message->GetReflection(); const auto* type_url_field_descriptor = opaque_descriptor->FindFieldByName("type_url"); const auto* value_field_descriptor = opaque_descriptor->FindFieldByName("value"); ASSERT(type_url_field_descriptor != nullptr && value_field_descriptor != nullptr); - if (!reflection->HasField(*message, type_url_field_descriptor) && - !reflection->HasField(*message, value_field_descriptor)) { + if (!reflection->HasField(*reflectable_message, type_url_field_descriptor) && + !reflection->HasField(*reflectable_message, value_field_descriptor)) { return true; } - if (!reflection->HasField(*message, type_url_field_descriptor) || - !reflection->HasField(*message, value_field_descriptor)) { + if (!reflection->HasField(*reflectable_message, type_url_field_descriptor) || + !reflection->HasField(*reflectable_message, value_field_descriptor)) { return false; } // Try to find a descriptor for `type_url` in the pool and instantiate a new message of the // correct concrete type. - const std::string type_url(reflection->GetString(*message, type_url_field_descriptor)); + const std::string type_url( + reflection->GetString(*reflectable_message, type_url_field_descriptor)); const std::string concrete_type_name(TypeUtil::typeUrlToDescriptorFullName(type_url)); const auto* concrete_descriptor = Protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(concrete_type_name); @@ -409,7 +459,15 @@ bool redactOpaque(Protobuf::Message* message, bool ancestor_is_sensitive, message_factory.GetPrototype(concrete_descriptor)->New()); // Finally we can unpack, redact, and repack the opaque message using the provided callbacks. - unpack(typed_message.get(), reflection, value_field_descriptor); + + // Note: the content of opaque types may contain illegal content that mismatches the type_url + // which may cause unpacking to fail. We catch the exception here to avoid crashing Envoy. + TRY_ASSERT_MAIN_THREAD { unpack(typed_message.get(), reflection, value_field_descriptor); } + END_TRY CATCH(const EnvoyException& e, { + ENVOY_LOG_MISC(warn, "Could not unpack {} with type URL {}: {}", opaque_type_name, type_url, + e.what()); + return false; + }); redact(typed_message.get(), ancestor_is_sensitive); repack(typed_message.get(), reflection, value_field_descriptor); return true; @@ -420,13 +478,17 @@ bool redactAny(Protobuf::Message* message, bool ancestor_is_sensitive) { message, ancestor_is_sensitive, "google.protobuf.Any", [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection, const Protobuf::FieldDescriptor* field_descriptor) { + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message); // To unpack an `Any`, parse the serialized proto. - typed_message->ParseFromString(reflection->GetString(*message, field_descriptor)); + typed_message->ParseFromString( + reflection->GetString(*reflectable_message, field_descriptor)); }, [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection, const Protobuf::FieldDescriptor* field_descriptor) { + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message); // To repack an `Any`, reserialize its proto. - reflection->SetString(message, field_descriptor, typed_message->SerializeAsString()); + reflection->SetString(&(*reflectable_message), field_descriptor, + typed_message->SerializeAsString()); }); } @@ -473,8 +535,9 @@ void redact(Protobuf::Message* message, bool ancestor_is_sensitive) { return; } - const auto* descriptor = message->GetDescriptor(); - const auto* reflection = message->GetReflection(); + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message); + const auto* descriptor = reflectable_message->GetDescriptor(); + const auto* reflection = reflectable_message->GetReflection(); for (int i = 0; i < descriptor->field_count(); ++i) { const auto* field_descriptor = descriptor->field(i); @@ -487,43 +550,46 @@ void redact(Protobuf::Message* message, bool ancestor_is_sensitive) { if (field_descriptor->is_map()) { // Redact values of maps only. Redacting both leaves the map with multiple "[redacted]" // keys. - const int field_size = reflection->FieldSize(*message, field_descriptor); + const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor); for (int i = 0; i < field_size; ++i) { - Protobuf::Message* map_pair = - reflection->MutableRepeatedMessage(message, field_descriptor, i); + Protobuf::Message* map_pair_base = + reflection->MutableRepeatedMessage(&(*reflectable_message), field_descriptor, i); + Protobuf::ReflectableMessage map_pair = createReflectableMessage(*map_pair_base); auto* value_field_desc = map_pair->GetDescriptor()->FindFieldByName("value"); if (sensitive && (value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_STRING || value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_BYTES)) { - map_pair->GetReflection()->SetString(map_pair, value_field_desc, "[redacted]"); + map_pair->GetReflection()->SetString(&(*map_pair), value_field_desc, "[redacted]"); } else if (value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_MESSAGE) { - redact(map_pair->GetReflection()->MutableMessage(map_pair, value_field_desc), + redact(map_pair->GetReflection()->MutableMessage(&(*map_pair), value_field_desc), sensitive); } else if (sensitive) { - map_pair->GetReflection()->ClearField(map_pair, value_field_desc); + map_pair->GetReflection()->ClearField(&(*map_pair), value_field_desc); } } } else if (field_descriptor->is_repeated()) { - const int field_size = reflection->FieldSize(*message, field_descriptor); + const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor); for (int i = 0; i < field_size; ++i) { - redact(reflection->MutableRepeatedMessage(message, field_descriptor, i), sensitive); + redact(reflection->MutableRepeatedMessage(&(*reflectable_message), field_descriptor, i), + sensitive); } - } else if (reflection->HasField(*message, field_descriptor)) { - redact(reflection->MutableMessage(message, field_descriptor), sensitive); + } else if (reflection->HasField(*reflectable_message, field_descriptor)) { + redact(reflection->MutableMessage(&(*reflectable_message), field_descriptor), sensitive); } } else if (sensitive) { // Base case: replace strings and bytes with "[redacted]" and clear all others. if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_STRING || field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_BYTES) { if (field_descriptor->is_repeated()) { - const int field_size = reflection->FieldSize(*message, field_descriptor); + const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor); for (int i = 0; i < field_size; ++i) { - reflection->SetRepeatedString(message, field_descriptor, i, "[redacted]"); + reflection->SetRepeatedString(&(*reflectable_message), field_descriptor, i, + "[redacted]"); } - } else if (reflection->HasField(*message, field_descriptor)) { - reflection->SetString(message, field_descriptor, "[redacted]"); + } else if (reflection->HasField(*reflectable_message, field_descriptor)) { + reflection->SetString(&(*reflectable_message), field_descriptor, "[redacted]"); } } else { - reflection->ClearField(message, field_descriptor); + reflection->ClearField(&(*reflectable_message), field_descriptor); } } } diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 57b2f8678731..2c74d189e7e2 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -170,6 +170,7 @@ class RepeatedPtrUtil { template static std::size_t hash(const Protobuf::RepeatedPtrField& source) { std::string text; +#ifdef ENVOY_ENABLE_FULL_PROTOS { Protobuf::TextFormat::Printer printer; printer.SetExpandAny(true); @@ -182,6 +183,11 @@ class RepeatedPtrUtil { absl::StrAppend(&text, text_message); } } +#else + for (const auto& message : source) { + absl::StrAppend(&text, message.SerializeAsString()); + } +#endif return HashUtil::xxHash64(text); } @@ -198,7 +204,7 @@ class RepeatedPtrUtil { std::transform(repeated_field.begin(), repeated_field.end(), std::back_inserter(ret_container), [](const ProtoType& proto_message) -> std::unique_ptr { Protobuf::Message* clone = proto_message.New(); - clone->MergeFrom(proto_message); + clone->CheckTypeAndMergeFrom(proto_message); return std::unique_ptr(clone); }); return ret_container; @@ -351,6 +357,17 @@ class MessageUtil { return typed_config; } + /** + * Convert from a typed message into a google.protobuf.Any. This should be used + * instead of the inbuilt PackTo, as PackTo is not available with lite protos. + * + * @param any_message destination google.protobuf.Any. + * @param message source to pack from. + * + * @throw EnvoyException if the message does not unpack. + */ + static void packFrom(ProtobufWkt::Any& any_message, const Protobuf::Message& message); + /** * Convert from google.protobuf.Any to a typed message. This should be used * instead of the inbuilt UnpackTo as it performs validation of results. @@ -446,10 +463,12 @@ class MessageUtil { */ static inline std::string getStringField(const Protobuf::Message& message, const std::string& field_name) { - const Protobuf::Descriptor* descriptor = message.GetDescriptor(); + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); + const Protobuf::Descriptor* descriptor = reflectable_message->GetDescriptor(); const Protobuf::FieldDescriptor* name_field = descriptor->FindFieldByName(field_name); - const Protobuf::Reflection* reflection = message.GetReflection(); - return reflection->GetString(message, name_field); + const Protobuf::Reflection* reflection = reflectable_message->GetReflection(); + return reflection->GetString(*reflectable_message, name_field); + return name_field->name(); } #ifdef ENVOY_ENABLE_YAML diff --git a/source/common/protobuf/visitor.cc b/source/common/protobuf/visitor.cc index c5a9b706ada9..38e0231dc7a7 100644 --- a/source/common/protobuf/visitor.cc +++ b/source/common/protobuf/visitor.cc @@ -22,17 +22,17 @@ void traverseMessageWorker(ConstProtoVisitor& visitor, const Protobuf::Message& std::unique_ptr inner_message; absl::string_view target_type_url; - if (message.GetDescriptor()->full_name() == "google.protobuf.Any") { + if (message.GetTypeName() == "google.protobuf.Any") { auto* any_message = Protobuf::DynamicCastToGenerated(&message); inner_message = Helper::typeUrlToMessage(any_message->type_url()); target_type_url = any_message->type_url(); // inner_message must be valid as parsing would have already failed to load if there was an // invalid type_url. MessageUtil::unpackTo(*any_message, *inner_message); - } else if (message.GetDescriptor()->full_name() == "xds.type.v3.TypedStruct") { + } else if (message.GetTypeName() == "xds.type.v3.TypedStruct") { std::tie(inner_message, target_type_url) = Helper::convertTypedStruct(message); - } else if (message.GetDescriptor()->full_name() == "udpa.type.v1.TypedStruct") { + } else if (message.GetTypeName() == "udpa.type.v1.TypedStruct") { std::tie(inner_message, target_type_url) = Helper::convertTypedStruct(message); } @@ -46,9 +46,9 @@ void traverseMessageWorker(ConstProtoVisitor& visitor, const Protobuf::Message& throw EnvoyException(fmt::format("Invalid type_url '{}' during traversal", target_type_url)); } } - - const Protobuf::Descriptor* descriptor = message.GetDescriptor(); - const Protobuf::Reflection* reflection = message.GetReflection(); + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); + const Protobuf::Descriptor* descriptor = reflectable_message->GetDescriptor(); + const Protobuf::Reflection* reflection = reflectable_message->GetReflection(); for (int i = 0; i < descriptor->field_count(); ++i) { const Protobuf::FieldDescriptor* field = descriptor->field(i); visitor.onField(message, *field); @@ -58,14 +58,15 @@ void traverseMessageWorker(ConstProtoVisitor& visitor, const Protobuf::Message& Helper::ScopedMessageParents scoped_parents(parents, message); if (field->is_repeated()) { - const int size = reflection->FieldSize(message, field); + const int size = reflection->FieldSize(*reflectable_message, field); for (int j = 0; j < size; ++j) { - traverseMessageWorker(visitor, reflection->GetRepeatedMessage(message, field, j), parents, - false, recurse_into_any); + traverseMessageWorker(visitor, + reflection->GetRepeatedMessage(*reflectable_message, field, j), + parents, false, recurse_into_any); } - } else if (reflection->HasField(message, field)) { - traverseMessageWorker(visitor, reflection->GetMessage(message, field), parents, false, - recurse_into_any); + } else if (reflection->HasField(*reflectable_message, field)) { + traverseMessageWorker(visitor, reflection->GetMessage(*reflectable_message, field), parents, + false, recurse_into_any); } } } diff --git a/source/common/quic/active_quic_listener.cc b/source/common/quic/active_quic_listener.cc index b59a0ef3fdac..90db9cfa8246 100644 --- a/source/common/quic/active_quic_listener.cc +++ b/source/common/quic/active_quic_listener.cc @@ -33,7 +33,7 @@ ActiveQuicListener::ActiveQuicListener( uint32_t packets_to_read_to_connection_count_ratio, EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory, EnvoyQuicProofSourceFactoryInterface& proof_source_factory, - QuicConnectionIdGeneratorPtr&& cid_generator) + QuicConnectionIdGeneratorPtr&& cid_generator, QuicConnectionIdWorkerSelector worker_selector) : Server::ActiveUdpListenerBase( worker_index, concurrency, parent, *listen_socket, dispatcher.createUdpListener( @@ -44,8 +44,10 @@ ActiveQuicListener::ActiveQuicListener( kernel_worker_routing_(kernel_worker_routing), packets_to_read_to_connection_count_ratio_(packets_to_read_to_connection_count_ratio), crypto_server_stream_factory_(crypto_server_stream_factory), - connection_id_generator_(std::move(cid_generator)) { + connection_id_generator_(std::move(cid_generator)), + select_connection_id_worker_(std::move(worker_selector)) { ASSERT(!GetQuicFlag(quic_header_size_limit_includes_overhead)); + ASSERT(select_connection_id_worker_ != nullptr); enabled_.emplace(Runtime::FeatureFlag(enabled, runtime)); @@ -175,41 +177,9 @@ uint32_t ActiveQuicListener::destination(const Network::UdpRecvData& data) const return worker_index_; } - // This implementation is not as performant as it could be. It will result in most packets being + // Taking this path is not as performant as it could be. It means most packets are being // delivered by the kernel to the wrong worker, and then redirected to the correct worker. - // - // This could possibly be improved by keeping a global table of connection IDs, so that a new - // connection will add its connection ID to the table on the current worker, and so packets should - // be delivered to the correct worker by the kernel unless the client changes address. - - // This is a re-implementation of the same algorithm written in BPF in - // ``ActiveQuicListenerFactory::createActiveUdpListener`` - const uint64_t packet_length = data.buffer_->length(); - if (packet_length < 9) { - return worker_index_; - } - - uint8_t first_octet; - data.buffer_->copyOut(0, sizeof(first_octet), &first_octet); - - uint32_t connection_id_snippet; - if (first_octet & 0x80) { - // IETF QUIC long header. - // The connection id starts from 7th byte. - // Minimum length of a long header packet is 14. - if (packet_length < 14) { - return worker_index_; - } - - data.buffer_->copyOut(6, sizeof(connection_id_snippet), &connection_id_snippet); - } else { - // IETF QUIC short header, or gQUIC. - // The connection id starts from 2nd byte. - data.buffer_->copyOut(1, sizeof(connection_id_snippet), &connection_id_snippet); - } - - connection_id_snippet = htonl(connection_id_snippet); - return connection_id_snippet % concurrency_; + return select_connection_id_worker_(*data.buffer_, worker_index_); } size_t ActiveQuicListener::numPacketsExpectedPerEventLoop() const { @@ -316,6 +286,8 @@ ActiveQuicListenerFactory::ActiveQuicListenerFactory( validation_visitor, context_); } + worker_selector_ = + quic_cid_generator_factory_->getCompatibleConnectionIdWorkerSelector(concurrency_); #if defined(SO_ATTACH_REUSEPORT_CBPF) && defined(__linux__) if (!disable_kernel_bpf_packet_routing_for_test_) { if (concurrency_ > 1) { @@ -383,7 +355,7 @@ ActiveQuicListenerFactory::createActiveQuicListener( runtime, worker_index, concurrency, dispatcher, parent, std::move(listen_socket), listener_config, quic_config, kernel_worker_routing, enabled, quic_stat_names, packets_to_read_to_connection_count_ratio, crypto_server_stream_factory, proof_source_factory, - std::move(cid_generator)); + std::move(cid_generator), worker_selector_); } } // namespace Quic diff --git a/source/common/quic/active_quic_listener.h b/source/common/quic/active_quic_listener.h index 8280061eec4f..5d1adaa6987f 100644 --- a/source/common/quic/active_quic_listener.h +++ b/source/common/quic/active_quic_listener.h @@ -38,7 +38,8 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase, uint32_t packets_to_read_to_connection_count_ratio, EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory, EnvoyQuicProofSourceFactoryInterface& proof_source_factory, - QuicConnectionIdGeneratorPtr&& cid_generator); + QuicConnectionIdGeneratorPtr&& cid_generator, + QuicConnectionIdWorkerSelector worker_selector); ~ActiveQuicListener() override; @@ -88,7 +89,8 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase, uint64_t event_loops_with_buffered_chlo_for_test_{0}; uint32_t packets_to_read_to_connection_count_ratio_; EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory_; - QuicConnectionIdGeneratorPtr connection_id_generator_; + const QuicConnectionIdGeneratorPtr connection_id_generator_; + const QuicConnectionIdWorkerSelector select_connection_id_worker_; // Latches envoy.reloadable_features.quic_reject_all at the beginning of each event loop. bool reject_all_{false}; }; @@ -144,6 +146,7 @@ class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory, QuicStatNames& quic_stat_names_; const uint32_t packets_to_read_to_connection_count_ratio_; const Network::Socket::OptionsSharedPtr options_{std::make_shared()}; + QuicConnectionIdWorkerSelector worker_selector_; bool kernel_worker_routing_{}; ProcessContextOptRef context_; diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index 63439c1c4fa3..c383abc53eb6 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -222,6 +222,10 @@ void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, return; } quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); + if (read_side_closed()) { + return; + } + if (!headers_decompressed() || header_list.empty()) { onStreamError(!http3_options_.override_stream_error_on_invalid_http_message().value(), quic::QUIC_BAD_APPLICATION_PAYLOAD); @@ -494,5 +498,9 @@ void EnvoyQuicClientStream::useCapsuleProtocol() { } #endif +void EnvoyQuicClientStream::OnInvalidHeaders() { + onStreamError(absl::nullopt, quic::QUIC_BAD_APPLICATION_PAYLOAD); +} + } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/envoy_quic_client_stream.h b/source/common/quic/envoy_quic_client_stream.h index 7c40341b422f..e8a6272d07e1 100644 --- a/source/common/quic/envoy_quic_client_stream.h +++ b/source/common/quic/envoy_quic_client_stream.h @@ -64,6 +64,7 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, const quic::QuicHeaderList& header_list) override; void OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) override; + void OnInvalidHeaders() override; // Http::MultiplexedStreamImplBase bool hasPendingData() override; diff --git a/source/common/quic/envoy_quic_connection_id_generator_factory.h b/source/common/quic/envoy_quic_connection_id_generator_factory.h index 686fa363ed17..2fa896d1a7de 100644 --- a/source/common/quic/envoy_quic_connection_id_generator_factory.h +++ b/source/common/quic/envoy_quic_connection_id_generator_factory.h @@ -10,6 +10,10 @@ namespace Envoy { namespace Quic { using QuicConnectionIdGeneratorPtr = std::unique_ptr; +// A function similar to the BPF program from createCompatibleLinuxBpfSocketOption, it takes +// a QUIC packet and returns the appropriate worker_index. +using QuicConnectionIdWorkerSelector = + std::function; /** * A factory interface to provide QUIC connection IDs and compatible BPF code for stable packet @@ -33,6 +37,13 @@ class EnvoyQuicConnectionIdGeneratorFactory { */ virtual Network::Socket::OptionConstSharedPtr createCompatibleLinuxBpfSocketOption(uint32_t concurrency) PURE; + + /** + * Returns a function to retrieve the worker index associated with a QUIC packet; the same + * principle as the BPF program above, but for contexts where BPF is unavailable. + */ + virtual QuicConnectionIdWorkerSelector + getCompatibleConnectionIdWorkerSelector(uint32_t concurrency) PURE; }; using EnvoyQuicConnectionIdGeneratorFactoryPtr = diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index 003ff29c5b77..aa5c8d57c587 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -222,6 +222,10 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, return; } quic::QuicSpdyServerStreamBase::OnInitialHeadersComplete(fin, frame_len, header_list); + if (read_side_closed()) { + return; + } + if (!headers_decompressed() || header_list.empty()) { onStreamError(absl::nullopt); return; @@ -569,5 +573,7 @@ void EnvoyQuicServerStream::useCapsuleProtocol() { } #endif +void EnvoyQuicServerStream::OnInvalidHeaders() { onStreamError(absl::nullopt); } + } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/envoy_quic_server_stream.h b/source/common/quic/envoy_quic_server_stream.h index ea9b4bf07ad8..35cef92dfe22 100644 --- a/source/common/quic/envoy_quic_server_stream.h +++ b/source/common/quic/envoy_quic_server_stream.h @@ -91,6 +91,7 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, void OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) override; void OnHeadersTooLarge() override; + void OnInvalidHeaders() override; // Http::MultiplexedStreamImplBase void onPendingFlushTimer() override; diff --git a/source/common/quic/envoy_quic_utils.cc b/source/common/quic/envoy_quic_utils.cc index 1b0047a6537b..7dc7cb7c16f6 100644 --- a/source/common/quic/envoy_quic_utils.cc +++ b/source/common/quic/envoy_quic_utils.cc @@ -250,6 +250,9 @@ void convertQuicConfig(const envoy::config::core::v3::QuicProtocolOptions& confi quic_config.SetMaxBidirectionalStreamsToSend(max_streams); quic_config.SetMaxUnidirectionalStreamsToSend(max_streams); configQuicInitialFlowControlWindow(config, quic_config); + quic_config.SetConnectionOptionsToSend(quic::ParseQuicTagVector(config.connection_options())); + quic_config.SetClientConnectionOptions( + quic::ParseQuicTagVector(config.client_connection_options())); } void configQuicInitialFlowControlWindow(const envoy::config::core::v3::QuicProtocolOptions& config, diff --git a/source/common/quic/platform/BUILD b/source/common/quic/platform/BUILD index 1ac9f77efee6..f9eb06a247b5 100644 --- a/source/common/quic/platform/BUILD +++ b/source/common/quic/platform/BUILD @@ -58,7 +58,10 @@ envoy_quiche_platform_impl_cc_library( name = "quiche_time_utils_impl_lib", srcs = ["quiche_time_utils_impl.cc"], hdrs = ["quiche_time_utils_impl.h"], - external_deps = ["abseil_base"], + external_deps = [ + "abseil_base", + "abseil_time", + ], ) envoy_quiche_platform_impl_cc_library( diff --git a/source/common/quic/platform/quiche_flags_impl.cc b/source/common/quic/platform/quiche_flags_impl.cc index 38e105c545f0..187ce518720d 100644 --- a/source/common/quic/platform/quiche_flags_impl.cc +++ b/source/common/quic/platform/quiche_flags_impl.cc @@ -38,10 +38,6 @@ template <> constexpr bool maybeOverride(absl::string_view name, bool val) // Do not include 32-byte per-entry overhead while counting header size. return false; } - if (name == "quic_reloadable_flag_quic_act_upon_invalid_header") { - // This flag changes quiche behavior that's incompatible with current Envoy. - return false; - } return val; } diff --git a/source/common/rds/rds_route_config_subscription.cc b/source/common/rds/rds_route_config_subscription.cc index 7a0bb133406b..b551363b7160 100644 --- a/source/common/rds/rds_route_config_subscription.cc +++ b/source/common/rds/rds_route_config_subscription.cc @@ -57,12 +57,13 @@ void RdsRouteConfigSubscription::onConfigUpdate( return; } const auto& route_config = resources[0].get().resource(); - if (route_config.GetDescriptor()->full_name() != + Protobuf::ReflectableMessage reflectable_config = createReflectableMessage(route_config); + if (reflectable_config->GetDescriptor()->full_name() != route_config_provider_manager_.protoTraits().resourceType()) { throw EnvoyException(fmt::format("Unexpected {} configuration type (expecting {}): {}", rds_type_, route_config_provider_manager_.protoTraits().resourceType(), - route_config.GetDescriptor()->full_name())); + reflectable_config->GetDescriptor()->full_name())); } if (resourceName(route_config_provider_manager_.protoTraits(), route_config) != route_config_name_) { diff --git a/source/common/rds/route_config_provider_manager.cc b/source/common/rds/route_config_provider_manager.cc index 9db2b37865e3..9abda6d10e88 100644 --- a/source/common/rds/route_config_provider_manager.cc +++ b/source/common/rds/route_config_provider_manager.cc @@ -46,7 +46,8 @@ RouteConfigProviderManager::dumpRouteConfigs(const Matchers::StringMatcher& name } auto* dynamic_config = config_dump->mutable_dynamic_route_configs()->Add(); dynamic_config->set_version_info(provider->configInfo().value().version_); - dynamic_config->mutable_route_config()->PackFrom(provider->configInfo().value().config_); + MessageUtil::packFrom(*dynamic_config->mutable_route_config(), + provider->configInfo().value().config_); TimestampUtil::systemClockToTimestamp(provider->lastUpdated(), *dynamic_config->mutable_last_updated()); } @@ -58,7 +59,8 @@ RouteConfigProviderManager::dumpRouteConfigs(const Matchers::StringMatcher& name continue; } auto* static_config = config_dump->mutable_static_route_configs()->Add(); - static_config->mutable_route_config()->PackFrom(provider->configInfo().value().config_); + MessageUtil::packFrom(*static_config->mutable_route_config(), + provider->configInfo().value().config_); TimestampUtil::systemClockToTimestamp(provider->lastUpdated(), *static_config->mutable_last_updated()); } diff --git a/source/common/rds/util.cc b/source/common/rds/util.cc index b4c9a627ecc5..51e69b8a68a5 100644 --- a/source/common/rds/util.cc +++ b/source/common/rds/util.cc @@ -5,18 +5,19 @@ namespace Rds { ProtobufTypes::MessagePtr cloneProto(ProtoTraits& proto_traits, const Protobuf::Message& rc) { auto clone = proto_traits.createEmptyProto(); - clone->CopyFrom(rc); + clone->CheckTypeAndMergeFrom(rc); return clone; } std::string resourceName(ProtoTraits& proto_traits, const Protobuf::Message& rc) { - const Protobuf::FieldDescriptor* field = - rc.GetDescriptor()->FindFieldByNumber(proto_traits.resourceNameFieldNumber()); + Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(rc); + const Protobuf::FieldDescriptor* field = reflectable_message->GetDescriptor()->FindFieldByNumber( + proto_traits.resourceNameFieldNumber()); if (!field) { return {}; } - const Protobuf::Reflection* reflection = rc.GetReflection(); - return reflection->GetString(rc, field); + const Protobuf::Reflection* reflection = reflectable_message->GetReflection(); + return reflection->GetString(*reflectable_message, field); } } // namespace Rds diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 6205ebe2ce33..b8fe27c53693 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -141,7 +141,10 @@ class RouteActionValidationVisitor absl::Status performDataInputValidation(const Matcher::DataInputFactory&, absl::string_view type_url) override { static std::string request_header_input_name = TypeUtil::descriptorFullNameToTypeUrl( - envoy::type::matcher::v3::HttpRequestHeaderMatchInput::descriptor()->full_name()); + createReflectableMessage( + envoy::type::matcher::v3::HttpRequestHeaderMatchInput::default_instance()) + ->GetDescriptor() + ->full_name()); if (type_url == request_header_input_name) { return absl::OkStatus(); } @@ -597,8 +600,9 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost, throw EnvoyException("Sum of weights in the weighted_cluster must be greater than 0."); } - weighted_clusters_config_ = std::make_unique( - weighted_clusters, total_weight, route.route().weighted_clusters().header_name()); + weighted_clusters_config_ = + std::make_unique(std::move(weighted_clusters), total_weight, + route.route().weighted_clusters().header_name()); } else if (route.route().cluster_specifier_case() == envoy::config::route::v3::RouteAction::ClusterSpecifierCase:: diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 65cdfb86996d..3b4522a66f07 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -1052,10 +1052,11 @@ class RouteEntryImplBase : public RouteEntryAndRoute, // We keep them in a separate data structure to avoid memory overhead for the routes that do not // use weighted clusters. struct WeightedClustersConfig { - WeightedClustersConfig(const std::vector& weighted_clusters, + WeightedClustersConfig(const std::vector&& weighted_clusters, uint64_t total_cluster_weight, const std::string& random_value_header_name) - : weighted_clusters_(weighted_clusters), total_cluster_weight_(total_cluster_weight), + : weighted_clusters_(std::move(weighted_clusters)), + total_cluster_weight_(total_cluster_weight), random_value_header_name_(random_value_header_name) {} const std::vector weighted_clusters_; const uint64_t total_cluster_weight_; diff --git a/source/common/router/header_parser_utils.cc b/source/common/router/header_parser_utils.cc index 345e9045498e..f92986d1cdc7 100644 --- a/source/common/router/header_parser_utils.cc +++ b/source/common/router/header_parser_utils.cc @@ -39,7 +39,7 @@ std::string HeaderParser::translateMetadataFormat(const std::string& header_valu } new_format = parsed_params->asObjectArray()[0]->asString(); for (size_t i = 1; i < parsed_params->asObjectArray().size(); i++) { - new_format += ":" + parsed_params->asObjectArray()[i]->asString(); + absl::StrAppend(&new_format, ":", parsed_params->asObjectArray()[i]->asString()); } new_format = absl::StrCat("%", matches[1], "_METADATA(", new_format, ")%"); diff --git a/source/common/router/route_config_update_receiver_impl.cc b/source/common/router/route_config_update_receiver_impl.cc index 5cf45e16ea9d..62cafc189531 100644 --- a/source/common/router/route_config_update_receiver_impl.cc +++ b/source/common/router/route_config_update_receiver_impl.cc @@ -26,10 +26,10 @@ void rebuildRouteConfigVirtualHosts( envoy::config::route::v3::RouteConfiguration& route_config) { route_config.clear_virtual_hosts(); for (const auto& vhost : rds_vhosts) { - route_config.mutable_virtual_hosts()->Add()->CopyFrom(vhost.second); + route_config.mutable_virtual_hosts()->Add()->CheckTypeAndMergeFrom(vhost.second); } for (const auto& vhost : vhds_vhosts) { - route_config.mutable_virtual_hosts()->Add()->CopyFrom(vhost.second); + route_config.mutable_virtual_hosts()->Add()->CheckTypeAndMergeFrom(vhost.second); } } @@ -56,7 +56,7 @@ bool RouteConfigUpdateReceiverImpl::onRdsUpdate(const Protobuf::Message& rc, return false; } auto new_route_config = std::make_unique(); - new_route_config->CopyFrom(rc); + new_route_config->CheckTypeAndMergeFrom(rc); const uint64_t new_vhds_config_hash = new_route_config->has_vhds() ? MessageUtil::hash(new_route_config->vhds()) : 0ul; if (new_route_config->has_vhds()) { @@ -102,7 +102,7 @@ bool RouteConfigUpdateReceiverImpl::onVhdsUpdate( auto route_config_after_this_update = std::make_unique(); - route_config_after_this_update->CopyFrom(base_.protobufConfiguration()); + route_config_after_this_update->CheckTypeAndMergeFrom(base_.protobufConfiguration()); rebuildRouteConfigVirtualHosts(*rds_virtual_hosts_, *vhosts_after_this_update, *route_config_after_this_update); diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 82dea8432649..032a5a7de82d 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -1940,7 +1940,7 @@ ProdFilter::createRetryState(const RetryPolicy& policy, Http::RequestHeaderMap& // Since doing retry will make Envoy to buffer the request body, if upstream using HTTP/3 is the // only reason for doing retry, set the retry shadow buffer limit to 0 so that we don't retry or // buffer safe requests with body which is not common. - setRetryShadownBufferLimit(0); + setRetryShadowBufferLimit(0); } return retry_state; } diff --git a/source/common/router/router.h b/source/common/router/router.h index ddbcfad71d0a..82142fe04f2c 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -266,7 +266,8 @@ class FilterConfig : Http::FilterChainFactory { server_factory_ctx, context.initManager(), context.scope()); Http::FilterChainHelper - helper(*filter_config_provider_manager, server_factory_ctx, *upstream_ctx_, prefix); + helper(*filter_config_provider_manager, server_factory_ctx, context.clusterManager(), + *upstream_ctx_, prefix); helper.processFilters(upstream_http_filters, "router upstream http", "router upstream http", upstream_http_filter_factories_); } @@ -565,7 +566,7 @@ class Filter : Logger::Loggable, const FilterStats& stats() { return stats_; } protected: - void setRetryShadownBufferLimit(uint32_t retry_shadow_buffer_limit) { + void setRetryShadowBufferLimit(uint32_t retry_shadow_buffer_limit) { ASSERT(retry_shadow_buffer_limit_ > retry_shadow_buffer_limit); retry_shadow_buffer_limit_ = retry_shadow_buffer_limit; } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 123a64b2cec6..f7a4f40521d7 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -33,6 +33,7 @@ RUNTIME_GUARD(envoy_reloadable_features_allow_absolute_url_with_mixed_scheme); RUNTIME_GUARD(envoy_reloadable_features_allow_compact_maglev); RUNTIME_GUARD(envoy_reloadable_features_append_query_parameters_path_rewriter); RUNTIME_GUARD(envoy_reloadable_features_append_xfh_idempotent); +RUNTIME_GUARD(envoy_reloadable_features_check_mep_on_first_eject); RUNTIME_GUARD(envoy_reloadable_features_conn_pool_delete_when_idle); RUNTIME_GUARD(envoy_reloadable_features_count_unused_mapped_pages_as_free); RUNTIME_GUARD(envoy_reloadable_features_dfp_mixed_scheme); @@ -80,7 +81,6 @@ RUNTIME_GUARD(envoy_reloadable_features_test_feature_true); RUNTIME_GUARD(envoy_reloadable_features_thrift_allow_negative_field_ids); RUNTIME_GUARD(envoy_reloadable_features_thrift_connection_draining); RUNTIME_GUARD(envoy_reloadable_features_uhv_allow_malformed_url_encoding); -RUNTIME_GUARD(envoy_reloadable_features_uhv_translate_backslash_to_slash); RUNTIME_GUARD(envoy_reloadable_features_upstream_allow_connect_with_2xx); RUNTIME_GUARD(envoy_reloadable_features_upstream_wait_for_response_headers_before_disabling_read); RUNTIME_GUARD(envoy_reloadable_features_use_http3_header_normalisation); @@ -119,6 +119,8 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_include_histograms); FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); // TODO(danzh) false deprecate it once QUICHE has its own enable/disable flag. FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); +// TODO(adisuissa): enable by default once this is tested in prod. +FALSE_RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); // Block of non-boolean flags. Use of int flags is deprecated. Do not add more. ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index 81bc35baaf29..2a4deeee8b2e 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -720,7 +720,7 @@ SnapshotImplPtr LoaderImpl::createNewSnapshot() { std::string path = layer.disk_layer().symlink_root() + "/" + layer.disk_layer().subdirectory(); if (layer.disk_layer().append_service_cluster()) { - path += "/" + service_cluster_; + absl::StrAppend(&path, "/", service_cluster_); } if (api_.fileSystem().directoryExists(path)) { TRY_ASSERT_MAIN_THREAD { diff --git a/source/common/stream_info/utility.cc b/source/common/stream_info/utility.cc index 85d2473037a9..ab720c88eda2 100644 --- a/source/common/stream_info/utility.cc +++ b/source/common/stream_info/utility.cc @@ -11,27 +11,36 @@ namespace Envoy { namespace StreamInfo { +const std::string ResponseFlagUtils::toString(const StreamInfo& stream_info) { + return toString(stream_info, true); +} + const std::string ResponseFlagUtils::toShortString(const StreamInfo& stream_info) { + return toString(stream_info, false); +} + +const std::string ResponseFlagUtils::toString(const StreamInfo& stream_info, bool use_long_name) { // We don't expect more than 4 flags are set. Relax to 16 since the vector is allocated on stack // anyway. - absl::InlinedVector flag_strings; - for (const auto& [flag_string, flag] : ALL_RESPONSE_STRING_FLAGS) { + absl::InlinedVector flag_strings_vec; + for (const auto& [flag_strings, flag] : ALL_RESPONSE_STRINGS_FLAGS) { if (stream_info.hasResponseFlag(flag)) { - flag_strings.push_back(flag_string); + flag_strings_vec.push_back(use_long_name ? flag_strings.long_string_ + : flag_strings.short_string_); } } - if (flag_strings.empty()) { + if (flag_strings_vec.empty()) { return std::string(NONE); } - return absl::StrJoin(flag_strings, ","); + return absl::StrJoin(flag_strings_vec, ","); } absl::flat_hash_map ResponseFlagUtils::getFlagMap() { static_assert(ResponseFlag::LastFlag == 0x4000000, - "A flag has been added. Add the new flag to ALL_RESPONSE_STRING_FLAGS."); + "A flag has been added. Add the new flag to ALL_RESPONSE_STRINGS_FLAGS."); absl::flat_hash_map res; - for (auto [str, flag] : ResponseFlagUtils::ALL_RESPONSE_STRING_FLAGS) { - res.emplace(str, flag); + for (auto [flag_strings, flag] : ResponseFlagUtils::ALL_RESPONSE_STRINGS_FLAGS) { + res.emplace(flag_strings.short_string_, flag); } return res; } diff --git a/source/common/stream_info/utility.h b/source/common/stream_info/utility.h index 492129c1f692..5f8c84a1bc36 100644 --- a/source/common/stream_info/utility.h +++ b/source/common/stream_info/utility.h @@ -15,10 +15,16 @@ namespace StreamInfo { */ class ResponseFlagUtils { public: + static const std::string toString(const StreamInfo& stream_info); static const std::string toShortString(const StreamInfo& stream_info); static absl::optional toResponseFlag(absl::string_view response_flag); - using FlagStringAndEnum = std::pair; + struct FlagStrings { + const absl::string_view short_string_; + const absl::string_view long_string_; // PascalCase string + }; + + using FlagStringsAndEnum = std::pair; constexpr static absl::string_view NONE = "-"; constexpr static absl::string_view DOWNSTREAM_CONNECTION_TERMINATION = "DC"; @@ -49,40 +55,91 @@ class ResponseFlagUtils { constexpr static absl::string_view OVERLOAD_MANAGER = "OM"; constexpr static absl::string_view DNS_FAIL = "DF"; - static constexpr std::array ALL_RESPONSE_STRING_FLAGS{ - FlagStringAndEnum{FAILED_LOCAL_HEALTH_CHECK, ResponseFlag::FailedLocalHealthCheck}, - FlagStringAndEnum{NO_HEALTHY_UPSTREAM, ResponseFlag::NoHealthyUpstream}, - FlagStringAndEnum{UPSTREAM_REQUEST_TIMEOUT, ResponseFlag::UpstreamRequestTimeout}, - FlagStringAndEnum{LOCAL_RESET, ResponseFlag::LocalReset}, - FlagStringAndEnum{UPSTREAM_REMOTE_RESET, ResponseFlag::UpstreamRemoteReset}, - FlagStringAndEnum{UPSTREAM_CONNECTION_FAILURE, ResponseFlag::UpstreamConnectionFailure}, - FlagStringAndEnum{UPSTREAM_CONNECTION_TERMINATION, - ResponseFlag::UpstreamConnectionTermination}, - FlagStringAndEnum{UPSTREAM_OVERFLOW, ResponseFlag::UpstreamOverflow}, - FlagStringAndEnum{NO_ROUTE_FOUND, ResponseFlag::NoRouteFound}, - FlagStringAndEnum{DELAY_INJECTED, ResponseFlag::DelayInjected}, - FlagStringAndEnum{FAULT_INJECTED, ResponseFlag::FaultInjected}, - FlagStringAndEnum{RATE_LIMITED, ResponseFlag::RateLimited}, - FlagStringAndEnum{UNAUTHORIZED_EXTERNAL_SERVICE, ResponseFlag::UnauthorizedExternalService}, - FlagStringAndEnum{RATELIMIT_SERVICE_ERROR, ResponseFlag::RateLimitServiceError}, - FlagStringAndEnum{DOWNSTREAM_CONNECTION_TERMINATION, - ResponseFlag::DownstreamConnectionTermination}, - FlagStringAndEnum{UPSTREAM_RETRY_LIMIT_EXCEEDED, ResponseFlag::UpstreamRetryLimitExceeded}, - FlagStringAndEnum{STREAM_IDLE_TIMEOUT, ResponseFlag::StreamIdleTimeout}, - FlagStringAndEnum{INVALID_ENVOY_REQUEST_HEADERS, ResponseFlag::InvalidEnvoyRequestHeaders}, - FlagStringAndEnum{DOWNSTREAM_PROTOCOL_ERROR, ResponseFlag::DownstreamProtocolError}, - FlagStringAndEnum{UPSTREAM_MAX_STREAM_DURATION_REACHED, - ResponseFlag::UpstreamMaxStreamDurationReached}, - FlagStringAndEnum{RESPONSE_FROM_CACHE_FILTER, ResponseFlag::ResponseFromCacheFilter}, - FlagStringAndEnum{NO_FILTER_CONFIG_FOUND, ResponseFlag::NoFilterConfigFound}, - FlagStringAndEnum{DURATION_TIMEOUT, ResponseFlag::DurationTimeout}, - FlagStringAndEnum{UPSTREAM_PROTOCOL_ERROR, ResponseFlag::UpstreamProtocolError}, - FlagStringAndEnum{NO_CLUSTER_FOUND, ResponseFlag::NoClusterFound}, - FlagStringAndEnum{OVERLOAD_MANAGER, ResponseFlag::OverloadManager}, + constexpr static absl::string_view DOWNSTREAM_CONNECTION_TERMINATION_LONG = + "DownstreamConnectionTermination"; + constexpr static absl::string_view FAILED_LOCAL_HEALTH_CHECK_LONG = "FailedLocalHealthCheck"; + constexpr static absl::string_view NO_HEALTHY_UPSTREAM_LONG = "NoHealthyUpstream"; + constexpr static absl::string_view UPSTREAM_REQUEST_TIMEOUT_LONG = "UpstreamRequestTimeout"; + constexpr static absl::string_view LOCAL_RESET_LONG = "LocalReset"; + constexpr static absl::string_view UPSTREAM_REMOTE_RESET_LONG = "UpstreamRemoteReset"; + constexpr static absl::string_view UPSTREAM_CONNECTION_FAILURE_LONG = "UpstreamConnectionFailure"; + constexpr static absl::string_view UPSTREAM_CONNECTION_TERMINATION_LONG = + "UpstreamConnectionTermination"; + constexpr static absl::string_view UPSTREAM_OVERFLOW_LONG = "UpstreamOverflow"; + constexpr static absl::string_view UPSTREAM_RETRY_LIMIT_EXCEEDED_LONG = + "UpstreamRetryLimitExceeded"; + constexpr static absl::string_view NO_ROUTE_FOUND_LONG = "NoRouteFound"; + constexpr static absl::string_view DELAY_INJECTED_LONG = "DelayInjected"; + constexpr static absl::string_view FAULT_INJECTED_LONG = "FaultInjected"; + constexpr static absl::string_view RATE_LIMITED_LONG = "RateLimited"; + constexpr static absl::string_view UNAUTHORIZED_EXTERNAL_SERVICE_LONG = + "UnauthorizedExternalService"; + constexpr static absl::string_view RATELIMIT_SERVICE_ERROR_LONG = "RateLimitServiceError"; + constexpr static absl::string_view STREAM_IDLE_TIMEOUT_LONG = "StreamIdleTimeout"; + constexpr static absl::string_view INVALID_ENVOY_REQUEST_HEADERS_LONG = + "InvalidEnvoyRequestHeaders"; + constexpr static absl::string_view DOWNSTREAM_PROTOCOL_ERROR_LONG = "DownstreamProtocolError"; + constexpr static absl::string_view UPSTREAM_MAX_STREAM_DURATION_REACHED_LONG = + "UpstreamMaxStreamDurationReached"; + constexpr static absl::string_view RESPONSE_FROM_CACHE_FILTER_LONG = "ResponseFromCacheFilter"; + constexpr static absl::string_view NO_FILTER_CONFIG_FOUND_LONG = "NoFilterConfigFound"; + constexpr static absl::string_view DURATION_TIMEOUT_LONG = "DurationTimeout"; + constexpr static absl::string_view UPSTREAM_PROTOCOL_ERROR_LONG = "UpstreamProtocolError"; + constexpr static absl::string_view NO_CLUSTER_FOUND_LONG = "NoClusterFound"; + constexpr static absl::string_view OVERLOAD_MANAGER_LONG = "OverloadManagerTerminated"; + + static constexpr std::array ALL_RESPONSE_STRINGS_FLAGS{ + FlagStringsAndEnum{{FAILED_LOCAL_HEALTH_CHECK, FAILED_LOCAL_HEALTH_CHECK_LONG}, + ResponseFlag::FailedLocalHealthCheck}, + FlagStringsAndEnum{{NO_HEALTHY_UPSTREAM, NO_HEALTHY_UPSTREAM_LONG}, + ResponseFlag::NoHealthyUpstream}, + FlagStringsAndEnum{{UPSTREAM_REQUEST_TIMEOUT, UPSTREAM_REQUEST_TIMEOUT_LONG}, + ResponseFlag::UpstreamRequestTimeout}, + FlagStringsAndEnum{{LOCAL_RESET, LOCAL_RESET_LONG}, ResponseFlag::LocalReset}, + FlagStringsAndEnum{{UPSTREAM_REMOTE_RESET, UPSTREAM_REMOTE_RESET_LONG}, + ResponseFlag::UpstreamRemoteReset}, + FlagStringsAndEnum{{UPSTREAM_CONNECTION_FAILURE, UPSTREAM_CONNECTION_FAILURE_LONG}, + ResponseFlag::UpstreamConnectionFailure}, + FlagStringsAndEnum{{UPSTREAM_CONNECTION_TERMINATION, UPSTREAM_CONNECTION_TERMINATION_LONG}, + ResponseFlag::UpstreamConnectionTermination}, + FlagStringsAndEnum{{UPSTREAM_OVERFLOW, UPSTREAM_OVERFLOW_LONG}, + ResponseFlag::UpstreamOverflow}, + FlagStringsAndEnum{{NO_ROUTE_FOUND, NO_ROUTE_FOUND_LONG}, ResponseFlag::NoRouteFound}, + FlagStringsAndEnum{{DELAY_INJECTED, DELAY_INJECTED_LONG}, ResponseFlag::DelayInjected}, + FlagStringsAndEnum{{FAULT_INJECTED, FAULT_INJECTED_LONG}, ResponseFlag::FaultInjected}, + FlagStringsAndEnum{{RATE_LIMITED, RATE_LIMITED_LONG}, ResponseFlag::RateLimited}, + FlagStringsAndEnum{{UNAUTHORIZED_EXTERNAL_SERVICE, UNAUTHORIZED_EXTERNAL_SERVICE_LONG}, + ResponseFlag::UnauthorizedExternalService}, + FlagStringsAndEnum{{RATELIMIT_SERVICE_ERROR, RATELIMIT_SERVICE_ERROR_LONG}, + ResponseFlag::RateLimitServiceError}, + FlagStringsAndEnum{ + {DOWNSTREAM_CONNECTION_TERMINATION, DOWNSTREAM_CONNECTION_TERMINATION_LONG}, + ResponseFlag::DownstreamConnectionTermination}, + FlagStringsAndEnum{{UPSTREAM_RETRY_LIMIT_EXCEEDED, UPSTREAM_RETRY_LIMIT_EXCEEDED_LONG}, + ResponseFlag::UpstreamRetryLimitExceeded}, + FlagStringsAndEnum{{STREAM_IDLE_TIMEOUT, STREAM_IDLE_TIMEOUT_LONG}, + ResponseFlag::StreamIdleTimeout}, + FlagStringsAndEnum{{INVALID_ENVOY_REQUEST_HEADERS, INVALID_ENVOY_REQUEST_HEADERS_LONG}, + ResponseFlag::InvalidEnvoyRequestHeaders}, + FlagStringsAndEnum{{DOWNSTREAM_PROTOCOL_ERROR, DOWNSTREAM_PROTOCOL_ERROR_LONG}, + ResponseFlag::DownstreamProtocolError}, + FlagStringsAndEnum{ + {UPSTREAM_MAX_STREAM_DURATION_REACHED, UPSTREAM_MAX_STREAM_DURATION_REACHED_LONG}, + ResponseFlag::UpstreamMaxStreamDurationReached}, + FlagStringsAndEnum{{RESPONSE_FROM_CACHE_FILTER, RESPONSE_FROM_CACHE_FILTER_LONG}, + ResponseFlag::ResponseFromCacheFilter}, + FlagStringsAndEnum{{NO_FILTER_CONFIG_FOUND, NO_FILTER_CONFIG_FOUND_LONG}, + ResponseFlag::NoFilterConfigFound}, + FlagStringsAndEnum{{DURATION_TIMEOUT, DURATION_TIMEOUT_LONG}, ResponseFlag::DurationTimeout}, + FlagStringsAndEnum{{UPSTREAM_PROTOCOL_ERROR, UPSTREAM_PROTOCOL_ERROR_LONG}, + ResponseFlag::UpstreamProtocolError}, + FlagStringsAndEnum{{NO_CLUSTER_FOUND, NO_CLUSTER_FOUND_LONG}, ResponseFlag::NoClusterFound}, + FlagStringsAndEnum{{OVERLOAD_MANAGER, OVERLOAD_MANAGER_LONG}, ResponseFlag::OverloadManager}, }; private: ResponseFlagUtils(); + static const std::string toString(const StreamInfo& stream_info, bool use_long_name); static absl::flat_hash_map getFlagMap(); }; diff --git a/source/common/upstream/cluster_discovery_manager.cc b/source/common/upstream/cluster_discovery_manager.cc index ac6a87f3a78c..b9ab3e8d42f4 100644 --- a/source/common/upstream/cluster_discovery_manager.cc +++ b/source/common/upstream/cluster_discovery_manager.cc @@ -9,13 +9,15 @@ namespace Upstream { namespace { -using ClusterAddedCb = std::function; +using ClusterAddedCb = std::function; class ClusterCallbacks : public ClusterUpdateCallbacks { public: ClusterCallbacks(ClusterAddedCb cb) : cb_(std::move(cb)) {} - void onClusterAddOrUpdate(ThreadLocalCluster& cluster) override { cb_(cluster); }; + void onClusterAddOrUpdate(absl::string_view cluster_name, ThreadLocalClusterCommand&) override { + cb_(cluster_name); + }; void onClusterRemoval(const std::string&) override {} @@ -28,12 +30,12 @@ class ClusterCallbacks : public ClusterUpdateCallbacks { ClusterDiscoveryManager::ClusterDiscoveryManager( std::string thread_name, ClusterLifecycleCallbackHandler& lifecycle_callbacks_handler) : thread_name_(std::move(thread_name)) { - callbacks_ = std::make_unique([this](ThreadLocalCluster& cluster) { + callbacks_ = std::make_unique([this](absl::string_view cluster_name) { ENVOY_LOG(trace, "cm cdm: starting processing cluster name {} (status {}) from cluster lifecycle " "callback in {}", - cluster.info()->name(), enumToInt(ClusterDiscoveryStatus::Available), thread_name_); - processClusterName(cluster.info()->name(), ClusterDiscoveryStatus::Available); + cluster_name, enumToInt(ClusterDiscoveryStatus::Available), thread_name_); + processClusterName(cluster_name, ClusterDiscoveryStatus::Available); }); callbacks_handle_ = lifecycle_callbacks_handler.addClusterUpdateCallbacks(*callbacks_); } diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index a26a46a8a213..cdc351ab4263 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -289,6 +289,7 @@ ClusterManagerImpl::ClusterManagerImpl( const Server::Instance& server) : factory_(factory), runtime_(runtime), stats_(stats), tls_(tls), random_(api.randomGenerator()), + deferred_cluster_creation_(bootstrap.cluster_manager().enable_deferred_cluster_creation()), bind_config_(bootstrap.cluster_manager().has_upstream_bind_config() ? absl::make_optional(bootstrap.cluster_manager().upstream_bind_config()) : absl::nullopt), @@ -399,6 +400,8 @@ ClusterManagerImpl::ClusterManagerImpl( Envoy::Config::SubscriptionFactory::RetryInitialDelayMs, Envoy::Config::SubscriptionFactory::RetryMaxDelayMs); + const bool use_eds_cache = + Runtime::runtimeFeatureEnabled("envoy.restart_features.use_eds_cache_for_ads"); if (dyn_resources.ads_config().api_type() == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC) { Config::Utility::checkTransportVersion(dyn_resources.ads_config()); @@ -418,7 +421,7 @@ ClusterManagerImpl::ClusterManagerImpl( ->createUncachedRawAsyncClient(), main_thread_dispatcher, random_, *stats_.rootScope(), dyn_resources.ads_config(), local_info, std::move(custom_config_validators), std::move(backoff_strategy), - makeOptRefFromPtr(xds_config_tracker_.get()), {}); + makeOptRefFromPtr(xds_config_tracker_.get()), {}, use_eds_cache); } else { Config::Utility::checkTransportVersion(dyn_resources.ads_config()); auto xds_delegate_opt_ref = makeOptRefFromPtr(xds_resources_delegate_.get()); @@ -439,7 +442,7 @@ ClusterManagerImpl::ClusterManagerImpl( ->createUncachedRawAsyncClient(), main_thread_dispatcher, random_, *stats_.rootScope(), dyn_resources.ads_config(), local_info, std::move(custom_config_validators), std::move(backoff_strategy), - makeOptRefFromPtr(xds_config_tracker_.get()), xds_delegate_opt_ref); + makeOptRefFromPtr(xds_config_tracker_.get()), xds_delegate_opt_ref, use_eds_cache); } } else { ads_mux_ = std::make_unique(); @@ -528,6 +531,13 @@ ClusterManagerStats ClusterManagerImpl::generateStats(Stats::Scope& scope) { POOL_GAUGE_PREFIX(scope, final_prefix))}; } +ThreadLocalClusterManagerStats +ClusterManagerImpl::ThreadLocalClusterManagerImpl::generateStats(Stats::Scope& scope, + const std::string& thread_name) { + const std::string final_prefix = absl::StrCat("thread_local_cluster_manager.", thread_name); + return {ALL_THREAD_LOCAL_CLUSTER_MANAGER_STATS(POOL_GAUGE_PREFIX(scope, final_prefix))}; +} + void ClusterManagerImpl::onClusterInit(ClusterManagerCluster& cm_cluster) { // This routine is called when a cluster has finished initializing. The cluster has not yet // been setup for cross-thread updates to avoid needless updates during initialization. The order @@ -549,6 +559,8 @@ void ClusterManagerImpl::onClusterInit(ClusterManagerCluster& cm_cluster) { } // Now setup for cross-thread updates. + // This is used by cluster types such as EDS clusters to drain the connection pools of removed + // hosts. cluster_data->second->member_update_cb_ = cluster.prioritySet().addMemberUpdateCb( [&cluster, this](const HostVector&, const HostVector& hosts_removed) -> void { if (cluster.info()->lbConfig().close_connections_on_host_set_change()) { @@ -569,6 +581,8 @@ void ClusterManagerImpl::onClusterInit(ClusterManagerCluster& cm_cluster) { } }); + // This is used by cluster types such as EDS clusters to update the cluster + // without draining the cluster. cluster_data->second->priority_update_cb_ = cluster.prioritySet().addPriorityUpdateCb( [&cm_cluster, this](uint32_t priority, const HostVector& hosts_added, const HostVector& hosts_removed) { @@ -793,7 +807,8 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { ENVOY_LOG(debug, "removing cluster {}", cluster_name); tls_.runOnAllThreads([cluster_name](OptRef cluster_manager) { - ASSERT(cluster_manager->thread_local_clusters_.count(cluster_name) == 1); + ASSERT(cluster_manager->thread_local_clusters_.contains(cluster_name) || + cluster_manager->thread_local_deferred_clusters_.contains(cluster_name)); ENVOY_LOG(debug, "removing TLS cluster {}", cluster_name); for (auto cb_it = cluster_manager->update_callbacks_.begin(); cb_it != cluster_manager->update_callbacks_.end();) { @@ -804,7 +819,11 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { (*curr_cb_it)->onClusterRemoval(cluster_name); } cluster_manager->thread_local_clusters_.erase(cluster_name); + cluster_manager->thread_local_deferred_clusters_.erase(cluster_name); + cluster_manager->local_stats_.clusters_inflated_.set( + cluster_manager->thread_local_clusters_.size()); }); + cluster_initialization_map_.erase(cluster_name); } auto existing_warming_cluster = warming_clusters_.find(cluster_name); @@ -973,7 +992,7 @@ ThreadLocalCluster* ClusterManagerImpl::getThreadLocalCluster(absl::string_view if (entry != cluster_manager.thread_local_clusters_.end()) { return entry->second.get(); } else { - return nullptr; + return cluster_manager.initializeClusterInlineIfExists(cluster); } } @@ -1054,7 +1073,6 @@ void ClusterManagerImpl::drainConnections(const std::string& cluster, DrainConnectionsHostPredicate predicate) { ENVOY_LOG_EVENT(debug, "drain_connections_call", "drainConnections called for cluster {}", cluster); - tls_.runOnAllThreads([cluster, predicate](OptRef cluster_manager) { auto cluster_entry = cluster_manager->thread_local_clusters_.find(cluster); if (cluster_entry != cluster_manager->thread_local_clusters_.end()) { @@ -1067,7 +1085,6 @@ void ClusterManagerImpl::drainConnections(const std::string& cluster, void ClusterManagerImpl::drainConnections(DrainConnectionsHostPredicate predicate) { ENVOY_LOG_EVENT(debug, "drain_connections_call_for_all_clusters", "drainConnections called for all clusters"); - tls_.runOnAllThreads([predicate](OptRef cluster_manager) { for (const auto& cluster_entry : cluster_manager->thread_local_clusters_) { cluster_entry.second->drainConnPools(predicate, @@ -1088,12 +1105,49 @@ void ClusterManagerImpl::checkActiveStaticCluster(const std::string& cluster) { void ClusterManagerImpl::postThreadLocalRemoveHosts(const Cluster& cluster, const HostVector& hosts_removed) { + // Drain the connection pools for the given hosts. For deferred clusters have + // been created. tls_.runOnAllThreads([name = cluster.info()->name(), hosts_removed](OptRef cluster_manager) { cluster_manager->removeHosts(name, hosts_removed); }); } +bool ClusterManagerImpl::deferralIsSupportedForCluster( + const ClusterInfoConstSharedPtr& info) const { + if (!deferred_cluster_creation_) { + return false; + } + + // Certain cluster types are unsupported for deferred initialization. + // We need to check both the `clusterType()` (preferred) falling back to + // the `type()` due to how custom clusters were added leveraging an any + // config. + if (auto custom_cluster_type = info->clusterType(); custom_cluster_type.has_value()) { + // TODO(kbaichoo): make it configurable what custom types are supported? + static const std::array supported_well_known_cluster_types = { + "envoy.clusters.aggregate", "envoy.cluster.eds", "envoy.clusters.redis", + "envoy.cluster.static"}; + if (std::find(supported_well_known_cluster_types.begin(), + supported_well_known_cluster_types.end(), + custom_cluster_type->name()) == supported_well_known_cluster_types.end()) { + return false; + } + + } else { + // Check DiscoveryType instead. + static constexpr std::array + supported_cluster_types = {envoy::config::cluster::v3::Cluster::EDS, + envoy::config::cluster::v3::Cluster::STATIC}; + if (std::find(supported_cluster_types.begin(), supported_cluster_types.end(), info->type()) == + supported_cluster_types.end()) { + return false; + } + } + + return true; +} + void ClusterManagerImpl::postThreadLocalClusterUpdate(ClusterManagerCluster& cm_cluster, ThreadLocalClusterUpdateParams&& params) { bool add_or_update_cluster = false; @@ -1119,42 +1173,237 @@ void ClusterManagerImpl::postThreadLocalClusterUpdate(ClusterManagerCluster& cm_ HostMapConstSharedPtr host_map = cm_cluster.cluster().prioritySet().crossPriorityHostMap(); pending_cluster_creations_.erase(cm_cluster.cluster().info()->name()); - tls_.runOnAllThreads([info = cm_cluster.cluster().info(), params = std::move(params), - add_or_update_cluster, load_balancer_factory, map = std::move(host_map)]( - OptRef cluster_manager) { - ThreadLocalClusterManagerImpl::ClusterEntry* new_cluster = nullptr; - if (add_or_update_cluster) { - if (cluster_manager->thread_local_clusters_.contains(info->name())) { - ENVOY_LOG(debug, "updating TLS cluster {}", info->name()); - } else { - ENVOY_LOG(debug, "adding TLS cluster {}", info->name()); - } - new_cluster = new ThreadLocalClusterManagerImpl::ClusterEntry(*cluster_manager, info, - load_balancer_factory); - cluster_manager->thread_local_clusters_[info->name()].reset(new_cluster); - } + // Populate the cluster initialization object based on this update. + ClusterInitializationObjectConstSharedPtr cluster_initialization_object = + addOrUpdateClusterInitializationObjectIfSupported(params, cm_cluster.cluster().info(), + load_balancer_factory, host_map); - for (const auto& per_priority : params.per_priority_update_params_) { - cluster_manager->updateClusterMembership( - info->name(), per_priority.priority_, per_priority.update_hosts_params_, - per_priority.locality_weights_, per_priority.hosts_added_, per_priority.hosts_removed_, - per_priority.weighted_priority_health_, per_priority.overprovisioning_factor_, map); - } + tls_.runOnAllThreads([info = cm_cluster.cluster().info(), params = std::move(params), + add_or_update_cluster, load_balancer_factory, map = std::move(host_map), + cluster_initialization_object = std::move(cluster_initialization_object)]( + OptRef cluster_manager) { + ASSERT(cluster_manager.has_value(), + "Expected the ThreadLocalClusterManager to be set during ClusterManagerImpl creation."); + + // Cluster Manager here provided by the particular thread, it will provide + // this allowing to make the relevant change. + if (const bool defer_unused_clusters = + cluster_initialization_object != nullptr && + !cluster_manager->thread_local_clusters_.contains(info->name()) && + !Envoy::Thread::MainThread::isMainThread(); + defer_unused_clusters) { + // Save the cluster initialization object. + ENVOY_LOG(debug, "Deferring add or update for TLS cluster {}", info->name()); + cluster_manager->thread_local_deferred_clusters_[info->name()] = + cluster_initialization_object; + + // Invoke similar logic of onClusterAddOrUpdate. + ThreadLocalClusterCommand command = [&cluster_manager, + cluster_name = info->name()]() -> ThreadLocalCluster& { + // If we have multiple callbacks only the first one needs to use the + // command to initialize the cluster. + auto existing_cluster_entry = cluster_manager->thread_local_clusters_.find(cluster_name); + if (existing_cluster_entry != cluster_manager->thread_local_clusters_.end()) { + return *existing_cluster_entry->second; + } - if (new_cluster != nullptr) { + auto* cluster_entry = cluster_manager->initializeClusterInlineIfExists(cluster_name); + ASSERT(cluster_entry != nullptr, "Deferred clusters initiailization should not fail."); + return *cluster_entry; + }; for (auto cb_it = cluster_manager->update_callbacks_.begin(); cb_it != cluster_manager->update_callbacks_.end();) { // The current callback may remove itself from the list, so a handle for // the next item is fetched before calling the callback. auto curr_cb_it = cb_it; ++cb_it; - (*curr_cb_it)->onClusterAddOrUpdate(*new_cluster); + (*curr_cb_it)->onClusterAddOrUpdate(info->name(), command); + } + + } else { + // Broadcast + ThreadLocalClusterManagerImpl::ClusterEntry* new_cluster = nullptr; + if (add_or_update_cluster) { + if (cluster_manager->thread_local_clusters_.contains(info->name())) { + ENVOY_LOG(debug, "updating TLS cluster {}", info->name()); + } else { + ENVOY_LOG(debug, "adding TLS cluster {}", info->name()); + } + + new_cluster = new ThreadLocalClusterManagerImpl::ClusterEntry(*cluster_manager, info, + load_balancer_factory); + cluster_manager->thread_local_clusters_[info->name()].reset(new_cluster); + cluster_manager->local_stats_.clusters_inflated_.set( + cluster_manager->thread_local_clusters_.size()); + } + + for (const auto& per_priority : params.per_priority_update_params_) { + cluster_manager->updateClusterMembership( + info->name(), per_priority.priority_, per_priority.update_hosts_params_, + per_priority.locality_weights_, per_priority.hosts_added_, per_priority.hosts_removed_, + per_priority.weighted_priority_health_, per_priority.overprovisioning_factor_, map); + } + + if (new_cluster != nullptr) { + ThreadLocalClusterCommand command = [&new_cluster]() -> ThreadLocalCluster& { + return *new_cluster; + }; + for (auto cb_it = cluster_manager->update_callbacks_.begin(); + cb_it != cluster_manager->update_callbacks_.end();) { + // The current callback may remove itself from the list, so a handle for + // the next item is fetched before calling the callback. + auto curr_cb_it = cb_it; + ++cb_it; + (*curr_cb_it)->onClusterAddOrUpdate(info->name(), command); + } } } }); } +ClusterManagerImpl::ClusterInitializationObjectConstSharedPtr +ClusterManagerImpl::addOrUpdateClusterInitializationObjectIfSupported( + const ThreadLocalClusterUpdateParams& params, ClusterInfoConstSharedPtr cluster_info, + LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map) { + if (!deferralIsSupportedForCluster(cluster_info)) { + return nullptr; + } + + const std::string& cluster_name = cluster_info->name(); + auto entry = cluster_initialization_map_.find(cluster_name); + // TODO(kbaichoo): if EDS can be configured via cluster_type() then modify the + // merging logic below. + // We should only merge if the cluster type is the same as before and this is + // an EDS cluster. This is due to the fact that EDS clusters get + // ClusterLoadAssignment from the configuration server but pass per priority + // deltas in updates to the ClusterManager. In the future we may decide to + // change how the updates propagate among those components. + const bool should_merge_with_prior_cluster = + entry != cluster_initialization_map_.end() && + entry->second->cluster_info_->type() == cluster_info->type() && + cluster_info->type() == envoy::config::cluster::v3::Cluster::EDS; + + if (should_merge_with_prior_cluster) { + // We need to copy from an existing Cluster Initialization Object. In + // particular, only update the params with changed priority. + auto new_initialization_object = std::make_shared( + entry->second->per_priority_state_, params, std::move(cluster_info), load_balancer_factory, + map); + cluster_initialization_map_[cluster_name] = new_initialization_object; + return new_initialization_object; + } else { + // We need to create a fresh Cluster Initialization Object. + auto new_initialization_object = std::make_shared( + params, std::move(cluster_info), load_balancer_factory, map); + cluster_initialization_map_[cluster_name] = new_initialization_object; + return new_initialization_object; + } +} + +ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry* +ClusterManagerImpl::ThreadLocalClusterManagerImpl::initializeClusterInlineIfExists( + absl::string_view cluster) { + auto entry = thread_local_deferred_clusters_.find(cluster); + if (entry == thread_local_deferred_clusters_.end()) { + // Unknown cluster. + return nullptr; + } + + // Create the cluster inline. + const ClusterInitializationObjectConstSharedPtr& initialization_object = entry->second; + ENVOY_LOG(debug, "initializing TLS cluster {} inline", cluster); + auto cluster_entry = std::make_unique( + *this, initialization_object->cluster_info_, initialization_object->load_balancer_factory_); + ClusterEntry* cluster_entry_ptr = cluster_entry.get(); + + thread_local_clusters_[cluster] = std::move(cluster_entry); + local_stats_.clusters_inflated_.set(thread_local_clusters_.size()); + + for (const auto& [_, per_priority] : initialization_object->per_priority_state_) { + updateClusterMembership(initialization_object->cluster_info_->name(), per_priority.priority_, + per_priority.update_hosts_params_, per_priority.locality_weights_, + per_priority.hosts_added_, per_priority.hosts_removed_, + per_priority.weighted_priority_health_, + per_priority.overprovisioning_factor_, + initialization_object->cross_priority_host_map_); + } + + // Remove the CIO as we've initialized the cluster. + thread_local_deferred_clusters_.erase(entry); + + return cluster_entry_ptr; +} + +ClusterManagerImpl::ClusterInitializationObject::ClusterInitializationObject( + const ThreadLocalClusterUpdateParams& params, ClusterInfoConstSharedPtr cluster_info, + LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map) + : cluster_info_(std::move(cluster_info)), load_balancer_factory_(load_balancer_factory), + cross_priority_host_map_(map) { + // Copy the update since the map is empty. + for (const auto& update : params.per_priority_update_params_) { + per_priority_state_.emplace(update.priority_, update); + } +} + +ClusterManagerImpl::ClusterInitializationObject::ClusterInitializationObject( + const absl::flat_hash_map& per_priority_state, + const ThreadLocalClusterUpdateParams& update_params, ClusterInfoConstSharedPtr cluster_info, + LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map) + : per_priority_state_(per_priority_state), cluster_info_(std::move(cluster_info)), + load_balancer_factory_(load_balancer_factory), cross_priority_host_map_(map) { + + ASSERT(cluster_info_->type() == envoy::config::cluster::v3::Cluster::EDS, + fmt::format("Using merge constructor on possible non-mergable cluster of type {}", + cluster_info_->type())); + // Because EDS Clusters receive the entire ClusterLoadAssignment but only + // provides the delta we must process the hosts_added and hosts_removed and + // not simply overwrite with hosts added. + for (const auto& update : update_params.per_priority_update_params_) { + auto it = per_priority_state_.find(update.priority_); + if (it != per_priority_state_.end()) { + auto& priority_state = it->second; + // Merge the two per_priorities. + priority_state.update_hosts_params_ = update.update_hosts_params_; + priority_state.locality_weights_ = update.locality_weights_; + priority_state.weighted_priority_health_ = update.weighted_priority_health_; + priority_state.overprovisioning_factor_ = update.overprovisioning_factor_; + + // Merge the hosts vectors to just have hosts added. + // Assumes that the old host_added_ is exclusive to new hosts_added_ and + // new hosts_removed_ only refers to the old hosts_added_. + ASSERT(priority_state.hosts_removed_.empty(), + "Cluster Initialization Object should apply hosts " + "removed updates to hosts_added vector!"); + + // TODO(kbaichoo): replace with a more efficient algorithm. For example + // if the EDS cluster exposed the LoadAssignment we could just merge by + // overwriting hosts_added. + if (!update.hosts_removed_.empty()) { + // Remove all hosts to be removed from the old host_added. + auto& host_added = priority_state.hosts_added_; + auto removed_section = std::remove_if( + host_added.begin(), host_added.end(), + [hosts_removed = std::cref(update.hosts_removed_)](const HostSharedPtr& ptr) { + return std::find(hosts_removed.get().begin(), hosts_removed.get().end(), ptr) != + hosts_removed.get().end(); + }); + priority_state.hosts_added_.erase(removed_section, priority_state.hosts_added_.end()); + } + + // Add updated host_added. + priority_state.hosts_added_.reserve(priority_state.hosts_added_.size() + + update.hosts_added_.size()); + std::copy(update.hosts_added_.begin(), update.hosts_added_.end(), + std::back_inserter(priority_state.hosts_added_)); + + } else { + // Just copy the new priority. + per_priority_state_.emplace(update.priority_, update); + } + } +} + void ClusterManagerImpl::postThreadLocalHealthFailure(const HostSharedPtr& host) { tls_.runOnAllThreads([host](OptRef cluster_manager) { cluster_manager->onHostHealthFailure(host); @@ -1266,8 +1515,8 @@ ClusterManagerImpl::allocateOdCdsApi(const envoy::config::core::v3::ConfigSource OptRef odcds_resources_locator, ProtobufMessage::ValidationVisitor& validation_visitor) { // TODO(krnowak): Instead of creating a new handle every time, store the handles internally and - // return an already existing one if the config or locator matches. Note that this may need a way - // to clean up the unused handles, so we can close the unnecessary connections. + // return an already existing one if the config or locator matches. Note that this may need a + // way to clean up the unused handles, so we can close the unnecessary connections. auto odcds = OdCdsApiImpl::create(odcds_config, odcds_resources_locator, *this, *this, *stats_.rootScope(), validation_visitor); return OdCdsApiHandleImpl::create(*this, std::move(odcds)); @@ -1281,28 +1530,28 @@ ClusterManagerImpl::requestOnDemandClusterDiscovery(OdCdsApiSharedPtr odcds, std auto [handle, discovery_in_progress, invoker] = cluster_manager.cdm_.addCallback(name, std::move(callback)); - // This check will catch requests for discoveries from this thread only. If other thread requested - // the same discovery, we will detect it in the main thread later. + // This check will catch requests for discoveries from this thread only. If other thread + // requested the same discovery, we will detect it in the main thread later. if (discovery_in_progress) { ENVOY_LOG(debug, "cm odcds: on-demand discovery for cluster {} is already in progress, something else " "in thread {} has already requested it", name, cluster_manager.thread_local_dispatcher_.name()); - // This worker thread has already requested a discovery of a cluster with this name, so nothing - // more left to do here. + // This worker thread has already requested a discovery of a cluster with this name, so + // nothing more left to do here. // // We can't "just" return handle here, because handle is a part of the structured binding done // above. So it's not really a ClusterDiscoveryCallbackHandlePtr, but more like - // ClusterDiscoveryCallbackHandlePtr&, so named return value optimization does not apply here - - // it needs to be moved. + // ClusterDiscoveryCallbackHandlePtr&, so named return value optimization does not apply here + // - it needs to be moved. return std::move(handle); } ENVOY_LOG( debug, "cm odcds: forwarding the on-demand discovery request for cluster {} to the main thread", name); - // This seems to be the first request for discovery of this cluster in this worker thread. Rest of - // the process may only happen in the main thread. + // This seems to be the first request for discovery of this cluster in this worker thread. Rest + // of the process may only happen in the main thread. dispatcher_.post([this, odcds = std::move(odcds), timeout, name = std::move(name), invoker = std::move(invoker), &thread_local_dispatcher = cluster_manager.thread_local_dispatcher_] { @@ -1321,8 +1570,8 @@ ClusterManagerImpl::requestOnDemandClusterDiscovery(OdCdsApiSharedPtr odcds, std if (auto it = pending_cluster_creations_.find(name); it != pending_cluster_creations_.end()) { ENVOY_LOG(debug, "cm odcds: on-demand discovery for cluster {} is already in progress", name); - // We already began the discovery process for this cluster, nothing to do. If we got here, it - // means that it was other worker thread that requested the discovery. + // We already began the discovery process for this cluster, nothing to do. If we got here, + // it means that it was other worker thread that requested the discovery. return; } // Start the discovery. If the cluster gets discovered, cluster manager will warm it up and @@ -1338,8 +1587,8 @@ ClusterManagerImpl::requestOnDemandClusterDiscovery(OdCdsApiSharedPtr odcds, std // We can't "just" return handle here, because handle is a part of the structured binding done // above. So it's not really a ClusterDiscoveryCallbackHandlePtr, but more like - // ClusterDiscoveryCallbackHandlePtr&, so named return value optimization does not apply here - it - // needs to be moved. + // ClusterDiscoveryCallbackHandlePtr&, so named return value optimization does not apply here - + // it needs to be moved. return std::move(handle); } @@ -1373,6 +1622,14 @@ void ClusterManagerImpl::notifyClusterDiscoveryStatus(absl::string_view name, }); } +Config::EdsResourcesCacheOptRef ClusterManagerImpl::edsResourcesCache() { + // EDS caching is only supported for ADS. + if (ads_mux_) { + return ads_mux_->edsResourcesCache(); + } + return {}; +} + ClusterDiscoveryManager ClusterManagerImpl::createAndSwapClusterDiscoveryManager(std::string thread_name) { ThreadLocalClusterManagerImpl& cluster_manager = *tls_; @@ -1424,7 +1681,8 @@ ClusterManagerImpl::dumpClusterConfigs(const Matchers::StringMatcher& name_match ClusterManagerImpl::ThreadLocalClusterManagerImpl::ThreadLocalClusterManagerImpl( ClusterManagerImpl& parent, Event::Dispatcher& dispatcher, const absl::optional& local_cluster_params) - : parent_(parent), thread_local_dispatcher_(dispatcher), cdm_(dispatcher.name(), *this) { + : parent_(parent), thread_local_dispatcher_(dispatcher), cdm_(dispatcher.name(), *this), + local_stats_(generateStats(*parent.stats_.rootScope(), dispatcher.name())) { // If local cluster is defined then we need to initialize it first. if (local_cluster_params.has_value()) { const auto& local_cluster_name = local_cluster_params->info_->name(); @@ -1432,6 +1690,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ThreadLocalClusterManagerImpl thread_local_clusters_[local_cluster_name] = std::make_unique( *this, local_cluster_params->info_, local_cluster_params->load_balancer_factory_); local_priority_set_ = &thread_local_clusters_[local_cluster_name]->prioritySet(); + local_stats_.clusters_inflated_.set(thread_local_clusters_.size()); } } @@ -1472,8 +1731,18 @@ void ClusterManagerImpl::ThreadLocalClusterManagerImpl::removeTcpConn( void ClusterManagerImpl::ThreadLocalClusterManagerImpl::removeHosts( const std::string& name, const HostVector& hosts_removed) { - ASSERT(thread_local_clusters_.find(name) != thread_local_clusters_.end()); - const auto& cluster_entry = thread_local_clusters_[name]; + auto entry = thread_local_clusters_.find(name); + // The if should only be possible if deferred cluster creation is enabled. + if (entry == thread_local_clusters_.end()) { + ASSERT( + parent_.deferred_cluster_creation_, + fmt::format("Cannot find ThreadLocalCluster {}, but deferred cluster creation is disabled.", + name)); + ASSERT(thread_local_deferred_clusters_.find(name) != thread_local_deferred_clusters_.end(), + "Cluster with removed host is neither deferred or inflated!"); + return; + } + const auto& cluster_entry = entry->second; ENVOY_LOG(debug, "removing hosts for TLS cluster {} removed {}", name, hosts_removed.size()); // We need to go through and purge any connection pools for hosts that got deleted. @@ -1510,9 +1779,9 @@ void ClusterManagerImpl::ThreadLocalClusterManagerImpl::onHostHealthFailure( // Network::ConnectionCallbacks. The last removed tcp conn will remove the TcpConnectionsMap // from host_tcp_conn_map_, so do not cache it between iterations. // - // TODO(ggreenway) PERF: If there are a large number of connections, this could take a long time - // and halt other useful work. Consider breaking up this work. Note that this behavior is noted - // in the configuration documentation in cluster setting + // TODO(ggreenway) PERF: If there are a large number of connections, this could take a long + // time and halt other useful work. Consider breaking up this work. Note that this behavior is + // noted in the configuration documentation in cluster setting // "close_connections_on_host_health_failure". Update the docs if this if this changes. while (true) { const auto& it = host_tcp_conn_map_.find(host); @@ -1654,8 +1923,8 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::~ClusterEntry() // cluster. // // TODO(mattklein123): Optimally, we would just fire member changed callbacks and remove all of - // the hosts inside of the HostImpl destructor. That is a change with wide implications, so we are - // going with a more targeted approach for now. + // the hosts inside of the HostImpl destructor. That is a change with wide implications, so we + // are going with a more targeted approach for now. drainConnPools(); } diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 84ea8b79dbd4..294af652d761 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -222,6 +222,18 @@ struct ClusterManagerStats { ALL_CLUSTER_MANAGER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) }; +/** + * All thread local cluster manager stats. @see stats_macros.h + */ +#define ALL_THREAD_LOCAL_CLUSTER_MANAGER_STATS(GAUGE) GAUGE(clusters_inflated, NeverImport) + +/** + * Struct definition for all cluster manager stats. @see stats_macros.h + */ +struct ThreadLocalClusterManagerStats { + ALL_THREAD_LOCAL_CLUSTER_MANAGER_STATS(GENERATE_GAUGE_STRUCT) +}; + /** * Implementation of ClusterManager that reads from a proto configuration, maintains a central * cluster list, as well as thread local caches of each cluster and associated connection pools. @@ -362,6 +374,8 @@ class ClusterManagerImpl : public ClusterManager, return common_lb_config_pool_->getObject(common_lb_config); } + Config::EdsResourcesCacheOptRef edsResourcesCache() override; + protected: virtual void postThreadLocalRemoveHosts(const Cluster& cluster, const HostVector& hosts_removed); @@ -370,8 +384,10 @@ class ClusterManagerImpl : public ClusterManager, struct PerPriority { PerPriority(uint32_t priority, const HostVector& hosts_added, const HostVector& hosts_removed) : hosts_added_(hosts_added), hosts_removed_(hosts_removed), priority_(priority) {} - - const HostVector hosts_added_; + // TODO(kbaichoo): make the hosts_added_ vector const and have the + // cluster initialization object have a stripped down version of this + // struct. + HostVector hosts_added_; const HostVector hosts_removed_; PrioritySet::UpdateHostsParams update_hosts_params_; LocalityWeightsConstSharedPtr locality_weights_; @@ -389,6 +405,34 @@ class ClusterManagerImpl : public ClusterManager, std::vector per_priority_update_params_; }; + /** + * A cluster initialization object (CIO) encapsulates the relevant information + * to create a cluster inline when there is traffic to it. We can thus use the + * CIO to deferred instantiating clusters on workers until they are used. + */ + struct ClusterInitializationObject { + ClusterInitializationObject(const ThreadLocalClusterUpdateParams& params, + ClusterInfoConstSharedPtr cluster_info, + LoadBalancerFactorySharedPtr load_balancer_factory, + HostMapConstSharedPtr map); + + ClusterInitializationObject( + const absl::flat_hash_map& + per_priority_state, + const ThreadLocalClusterUpdateParams& update_params, ClusterInfoConstSharedPtr cluster_info, + LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map); + + absl::flat_hash_map per_priority_state_; + const ClusterInfoConstSharedPtr cluster_info_; + const LoadBalancerFactorySharedPtr load_balancer_factory_; + const HostMapConstSharedPtr cross_priority_host_map_; + }; + + using ClusterInitializationObjectConstSharedPtr = + std::shared_ptr; + using ClusterInitializationMap = + absl::flat_hash_map; + /** * An implementation of an on-demand CDS handle. It forwards the discovery request to the cluster * manager that created the handle. @@ -635,9 +679,22 @@ class ClusterManagerImpl : public ClusterManager, // Upstream::ClusterLifecycleCallbackHandler ClusterUpdateCallbacksHandlePtr addClusterUpdateCallbacks(ClusterUpdateCallbacks& cb) override; + /** + * Transparently initialize the given thread local cluster if possible using + * the Cluster Initialization object. + * + * @return The ClusterEntry of the newly initialized cluster or nullptr if there + * is no cluster deferred cluster with that name. + */ + ClusterEntry* initializeClusterInlineIfExists(absl::string_view cluster); + ClusterManagerImpl& parent_; Event::Dispatcher& thread_local_dispatcher_; + // Known clusters will exclusively exist in either `thread_local_clusters_` + // or `thread_local_deferred_clusters_`. absl::flat_hash_map thread_local_clusters_; + // Maps from a given cluster name to the CIO for that cluster. + ClusterInitializationMap thread_local_deferred_clusters_; ClusterConnectivityState cluster_manager_state_; @@ -651,6 +708,11 @@ class ClusterManagerImpl : public ClusterManager, const PrioritySet* local_priority_set_{}; bool destroying_{}; ClusterDiscoveryManager cdm_; + ThreadLocalClusterManagerStats local_stats_; + + private: + static ThreadLocalClusterManagerStats generateStats(Stats::Scope& scope, + const std::string& thread_name); }; struct ClusterData : public ClusterManagerCluster { @@ -783,6 +845,18 @@ class ClusterManagerImpl : public ClusterManager, void notifyClusterDiscoveryStatus(absl::string_view name, ClusterDiscoveryStatus status); private: + /** + * Builds the cluster initialization object for this given cluster. + * @return a ClusterInitializationObjectSharedPtr that can be used to create + * this cluster or nullptr if deferred cluster creation is off or the cluster + * type is not supported. + */ + ClusterInitializationObjectConstSharedPtr addOrUpdateClusterInitializationObjectIfSupported( + const ThreadLocalClusterUpdateParams& params, ClusterInfoConstSharedPtr cluster_info, + LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map); + + bool deferralIsSupportedForCluster(const ClusterInfoConstSharedPtr& info) const; + ClusterManagerFactory& factory_; Runtime::Loader& runtime_; Stats::Store& stats_; @@ -796,6 +870,8 @@ class ClusterManagerImpl : public ClusterManager, private: ClusterMap warming_clusters_; + const bool deferred_cluster_creation_; + ClusterInitializationMap cluster_initialization_map_; absl::optional bind_config_; Outlier::EventLoggerSharedPtr outlier_event_logger_; const LocalInfo::LocalInfo& local_info_; diff --git a/source/common/upstream/cluster_update_tracker.cc b/source/common/upstream/cluster_update_tracker.cc index 15eefc0d2ca5..cf4e2274d185 100644 --- a/source/common/upstream/cluster_update_tracker.cc +++ b/source/common/upstream/cluster_update_tracker.cc @@ -12,11 +12,12 @@ ClusterUpdateTracker::ClusterUpdateTracker(ClusterManager& cm, const std::string } } -void ClusterUpdateTracker::onClusterAddOrUpdate(ThreadLocalCluster& cluster) { - if (cluster.info()->name() != cluster_name_) { +void ClusterUpdateTracker::onClusterAddOrUpdate(absl::string_view cluster_name, + ThreadLocalClusterCommand& get_cluster) { + if (cluster_name != cluster_name_) { return; } - thread_local_cluster_ = cluster; + thread_local_cluster_ = get_cluster(); } void ClusterUpdateTracker::onClusterRemoval(const std::string& cluster) { diff --git a/source/common/upstream/cluster_update_tracker.h b/source/common/upstream/cluster_update_tracker.h index 4660faf3c6d9..cfef30ae7abf 100644 --- a/source/common/upstream/cluster_update_tracker.h +++ b/source/common/upstream/cluster_update_tracker.h @@ -17,7 +17,8 @@ class ClusterUpdateTracker : public ClusterUpdateCallbacks { ThreadLocalClusterOptRef threadLocalCluster() { return thread_local_cluster_; }; // ClusterUpdateCallbacks - void onClusterAddOrUpdate(ThreadLocalCluster& cluster) override; + void onClusterAddOrUpdate(absl::string_view cluster_name, + ThreadLocalClusterCommand& get_cluster) override; void onClusterRemoval(const std::string& cluster) override; private: diff --git a/source/common/upstream/outlier_detection_impl.cc b/source/common/upstream/outlier_detection_impl.cc index 3c51d3a5e790..11937559ee0a 100644 --- a/source/common/upstream/outlier_detection_impl.cc +++ b/source/common/upstream/outlier_detection_impl.cc @@ -480,8 +480,11 @@ void DetectorImpl::ejectHost(HostSharedPtr host, double ejected_percent = 100.0 * (ejections_active_helper_.value() + 1) / host_monitors_.size(); // Note this is not currently checked per-priority level, so it is possible // for outlier detection to eject all hosts at any given priority level. - // Note: at-least one host is ejected, we ignore max ejection percentage when ejecting first host. - if ((ejections_active_helper_.value() == 0) || (ejected_percent <= max_ejection_percent)) { + bool should_eject = (ejected_percent <= max_ejection_percent); + if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.check_mep_on_first_eject")) { + should_eject = (ejections_active_helper_.value() == 0) || should_eject; + } + if (should_eject) { if (type == envoy::data::cluster::v3::CONSECUTIVE_5XX || type == envoy::data::cluster::v3::SUCCESS_RATE) { // Deprecated counter, preserving old behaviour until it's removed. @@ -594,7 +597,7 @@ void DetectorImpl::onConsecutiveErrorWorker(HostSharedPtr host, case envoy::data::cluster::v3::FAILURE_PERCENTAGE: FALLTHRU; case envoy::data::cluster::v3::FAILURE_PERCENTAGE_LOCAL_ORIGIN: - IS_ENVOY_BUG("unexpected non-consecutive errorr"); + IS_ENVOY_BUG("unexpected non-consecutive error"); return; case envoy::data::cluster::v3::CONSECUTIVE_5XX: stats_.ejections_consecutive_5xx_.inc(); // Deprecated diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index cccb4f9e33ca..03c0e8c1e4a5 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -1256,7 +1256,7 @@ ClusterInfoImpl::ClusterInfoImpl( Http::FilterChainHelper helper(*filter_config_provider_manager, upstream_context_.getServerFactoryContext(), - upstream_context_, prefix); + factory_context.clusterManager(), upstream_context_, prefix); helper.processFilters(http_filters, "upstream http", "upstream http", http_filter_factories_); } } diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 05a097d349db..349f01cbc79a 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -495,9 +495,23 @@ class HostsPerLocalityImpl : public HostsPerLocality { HostsPerLocalityImpl() : HostsPerLocalityImpl(std::vector(), false) {} // Single locality constructor + // + // Parameter requirements: + // 1. All entries in hosts must have the same locality. + // 2. If has_local_locality is true, then the locality of all entries in hosts + // must be equal to the current envoy's locality. HostsPerLocalityImpl(const HostVector& hosts, bool has_local_locality = false) : HostsPerLocalityImpl(std::vector({hosts}), has_local_locality) {} + // Multiple localities constructor + // + // locality_hosts must adhere to the following ordering constraints: + // 1. All hosts within a single HostVector bucket must have the same locality + // 2. No hosts in different HostVector buckets can have the same locality + // 3. If has_local_locality is true, then the locality of all hosts in the first HostVector bucket + // must be equal to the current envoy's locality. + // 4. All non-local HostVector buckets must be sorted in ascending order by the LocalityLess + // comparator HostsPerLocalityImpl(std::vector&& locality_hosts, bool has_local_locality) : local_(has_local_locality), hosts_per_locality_(std::move(locality_hosts)) { ASSERT(!has_local_locality || !hosts_per_locality_.empty()); diff --git a/source/exe/process_wide.cc b/source/exe/process_wide.cc index 212f80c53c15..4a32d11ca377 100644 --- a/source/exe/process_wide.cc +++ b/source/exe/process_wide.cc @@ -31,9 +31,13 @@ ProcessWide::ProcessWide(bool validate_proto_descriptors) { // TODO(mattklein123): Audit the following as not all of these have to be re-initialized in the // edge case where something does init/destroy/init/destroy. Event::Libevent::Global::initialize(); +#ifdef ENVOY_ENABLE_FULL_PROTOS if (validate_proto_descriptors) { Envoy::Server::validateProtoDescriptors(); } +#else + UNREFERENCED_PARAMETER(validate_proto_descriptors); +#endif Http::Http2::initializeNghttp2Logging(); // We do not initialize Google gRPC here -- we instead instantiate diff --git a/source/extensions/access_loggers/common/BUILD b/source/extensions/access_loggers/common/BUILD index 8b8060d4c74a..802ecba54dd8 100644 --- a/source/extensions/access_loggers/common/BUILD +++ b/source/extensions/access_loggers/common/BUILD @@ -27,6 +27,7 @@ envoy_cc_library( srcs = ["grpc_access_logger_utils.cc"], hdrs = ["grpc_access_logger_utils.h"], deps = [ + "//envoy/common:base_includes", "@envoy_api//envoy/extensions/access_loggers/grpc/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/clusters/aggregate/cluster.cc b/source/extensions/clusters/aggregate/cluster.cc index ca7d40cca6e5..f7d780c546cf 100644 --- a/source/extensions/clusters/aggregate/cluster.cc +++ b/source/extensions/clusters/aggregate/cluster.cc @@ -113,10 +113,12 @@ void AggregateClusterLoadBalancer::refresh(OptRef excluded_cl priority_context_ = std::move(priority_context); } -void AggregateClusterLoadBalancer::onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) { - if (std::find(clusters_->begin(), clusters_->end(), cluster.info()->name()) != clusters_->end()) { - ENVOY_LOG(debug, "adding or updating cluster '{}' for aggregate cluster '{}'", - cluster.info()->name(), parent_info_->name()); +void AggregateClusterLoadBalancer::onClusterAddOrUpdate( + absl::string_view cluster_name, Upstream::ThreadLocalClusterCommand& get_cluster) { + if (std::find(clusters_->begin(), clusters_->end(), cluster_name) != clusters_->end()) { + ENVOY_LOG(debug, "adding or updating cluster '{}' for aggregate cluster '{}'", cluster_name, + parent_info_->name()); + auto& cluster = get_cluster(); refresh(); addMemberUpdateCallbackForCluster(cluster); } diff --git a/source/extensions/clusters/aggregate/cluster.h b/source/extensions/clusters/aggregate/cluster.h index 6990b07e92e2..3e2a6ae4f334 100644 --- a/source/extensions/clusters/aggregate/cluster.h +++ b/source/extensions/clusters/aggregate/cluster.h @@ -70,7 +70,8 @@ class AggregateClusterLoadBalancer : public Upstream::LoadBalancer, const ClusterSetConstSharedPtr& clusters); // Upstream::ClusterUpdateCallbacks - void onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) override; + void onClusterAddOrUpdate(absl::string_view cluster_name, + Upstream::ThreadLocalClusterCommand& get_cluster) override; void onClusterRemoval(const std::string& cluster_name) override; // Upstream::LoadBalancer diff --git a/source/extensions/clusters/eds/eds.cc b/source/extensions/clusters/eds/eds.cc index 4aaea4dfba38..fbf1761c7a87 100644 --- a/source/extensions/clusters/eds/eds.cc +++ b/source/extensions/clusters/eds/eds.cc @@ -18,7 +18,11 @@ EdsClusterImpl::EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluste : BaseDynamicClusterImpl(cluster, cluster_context), Envoy::Config::SubscriptionBase( cluster_context.messageValidationVisitor(), "cluster_name"), - local_info_(cluster_context.serverFactoryContext().localInfo()) { + local_info_(cluster_context.serverFactoryContext().localInfo()), + eds_resources_cache_( + Runtime::runtimeFeatureEnabled("envoy.restart_features.use_eds_cache_for_ads") + ? cluster_context.clusterManager().edsResourcesCache() + : absl::nullopt) { Event::Dispatcher& dispatcher = cluster_context.serverFactoryContext().mainThreadDispatcher(); assignment_timeout_ = dispatcher.createTimer([this]() -> void { onAssignmentTimeout(); }); const auto& eds_config = cluster.eds_cluster_config().eds_config(); @@ -35,6 +39,13 @@ EdsClusterImpl::EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluste resource_decoder_, {}); } +EdsClusterImpl::~EdsClusterImpl() { + if (using_cached_resource_) { + // Clear the callback as the subscription is no longer valid. + eds_resources_cache_->removeCallback(edsServiceName(), this); + } +} + void EdsClusterImpl::startPreInit() { subscription_->start({edsServiceName()}); } void EdsClusterImpl::BatchUpdateHelper::batchUpdate(PrioritySet::HostUpdateCb& host_update_cb) { @@ -176,6 +187,9 @@ void EdsClusterImpl::onConfigUpdate(const std::vectorenabled()) { assignment_timeout_->disableTimer(); + if (eds_resources_cache_.has_value()) { + eds_resources_cache_->disableExpiryTimer(edsServiceName()); + } } // Check if endpoint_stale_after is set. const uint64_t stale_after_ms = @@ -184,6 +198,10 @@ void EdsClusterImpl::onConfigUpdate(const std::vectorconfigUpdateStats().assignment_timeout_received_.inc(); assignment_timeout_->enableTimer(std::chrono::milliseconds(stale_after_ms)); + if (eds_resources_cache_.has_value()) { + eds_resources_cache_->setExpiryTimer(edsServiceName(), + std::chrono::milliseconds(stale_after_ms)); + } } // Pause LEDS messages until the EDS config is finished processing. @@ -193,6 +211,17 @@ void EdsClusterImpl::onConfigUpdate(const std::vectorclusterManager().adsMux()->pause(type_url); } + update(cluster_load_assignment); + // If previously used a cached version, remove the subscription from the cache's + // callbacks. + if (using_cached_resource_) { + eds_resources_cache_->removeCallback(edsServiceName(), this); + using_cached_resource_ = false; + } +} + +void EdsClusterImpl::update( + const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment) { // Compare the current set of LEDS localities (localities using LEDS) to the one received in the // update. A LEDS locality can either be added, removed, or kept. If it is added we add a // subscription to it, and if it is removed we delete the subscription. @@ -213,14 +242,14 @@ void EdsClusterImpl::onConfigUpdate(const std::vector( std::move(cluster_load_assignment)); used_load_assignment = cluster_load_assignment_.get(); + } else { + cluster_load_assignment_ = nullptr; + used_load_assignment = &cluster_load_assignment; } // Add all the LEDS localities that are new. @@ -284,16 +313,28 @@ void EdsClusterImpl::onAssignmentTimeout() { // TODO(snowp): This should probably just use xDS TTLs? envoy::config::endpoint::v3::ClusterLoadAssignment resource; resource.set_cluster_name(edsServiceName()); - ProtobufWkt::Any any_resource; - any_resource.PackFrom(resource); - auto decoded_resource = - Config::DecodedResourceImpl::fromResource(*resource_decoder_, any_resource, ""); - std::vector resource_refs = {*decoded_resource}; - onConfigUpdate(resource_refs, ""); + update(resource); + + if (eds_resources_cache_.has_value()) { + // Clear the resource so it won't be used, and its watchers will be notified. + eds_resources_cache_->removeResource(edsServiceName()); + } // Stat to track how often we end up with stale assignments. info_->configUpdateStats().assignment_stale_.inc(); } +void EdsClusterImpl::onCachedResourceRemoved(absl::string_view resource_name) { + ASSERT(resource_name == edsServiceName()); + // Disable the timer if previously started. + if (assignment_timeout_->enabled()) { + assignment_timeout_->disableTimer(); + eds_resources_cache_->disableExpiryTimer(edsServiceName()); + } + envoy::config::endpoint::v3::ClusterLoadAssignment resource; + resource.set_cluster_name(edsServiceName()); + update(resource); +} + void EdsClusterImpl::reloadHealthyHostsHelper(const HostSharedPtr& host) { // Here we will see if we have a host that has been marked for deletion by service discovery // but has been stabilized due to passing active health checking. If such a host is now @@ -378,6 +419,27 @@ bool EdsClusterImpl::updateHostsPerLocality( void EdsClusterImpl::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException*) { ASSERT(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure != reason); + // Config failure may happen if Envoy times out waiting for the EDS resource. + // If it is a timeout, the eds resources cache is enabled, + // and there is a cached ClusterLoadAssignment, then the cached assignment should be used. + if (reason == Envoy::Config::ConfigUpdateFailureReason::FetchTimedout && + eds_resources_cache_.has_value()) { + ENVOY_LOG(trace, "onConfigUpdateFailed due to timeout for {}, looking for cached resources", + edsServiceName()); + auto cached_resource = eds_resources_cache_->getResource(edsServiceName(), this); + if (cached_resource.has_value()) { + ENVOY_LOG( + debug, + "Did not receive EDS response on time, using cached ClusterLoadAssignment for cluster {}", + edsServiceName()); + envoy::config::endpoint::v3::ClusterLoadAssignment cached_load_assignment = + dynamic_cast(*cached_resource); + info_->configUpdateStats().assignment_use_cached_.inc(); + using_cached_resource_ = true; + update(cached_load_assignment); + return; + } + } // We need to allow server startup to continue, even if we have a bad config. onPreInitComplete(); } @@ -385,6 +447,9 @@ void EdsClusterImpl::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReas std::pair EdsClusterFactory::createClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& context) { + // TODO(kbaichoo): EDS cluster should be able to support loading it's + // configuration from the CustomClusterType protobuf. Currently it does not. + // See: https://github.com/envoyproxy/envoy/issues/28752 if (!cluster.has_eds_cluster_config()) { throw EnvoyException("cannot create an EDS cluster without an EDS config"); } diff --git a/source/extensions/clusters/eds/eds.h b/source/extensions/clusters/eds/eds.h index 3d4cfbd8de55..70b5f0903867 100644 --- a/source/extensions/clusters/eds/eds.h +++ b/source/extensions/clusters/eds/eds.h @@ -5,6 +5,7 @@ #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/core/v3/config_source.pb.h" +#include "envoy/config/eds_resources_cache.h" #include "envoy/config/endpoint/v3/endpoint.pb.h" #include "envoy/config/endpoint/v3/endpoint.pb.validate.h" #include "envoy/config/subscription.h" @@ -29,10 +30,12 @@ namespace Upstream { */ class EdsClusterImpl : public BaseDynamicClusterImpl, - Envoy::Config::SubscriptionBase { + Envoy::Config::SubscriptionBase, + private Config::EdsResourceRemovalCallback { public: EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& cluster_context); + virtual ~EdsClusterImpl(); // Upstream::Cluster InitializePhase initializePhase() const override { return initialize_phase_; } @@ -61,6 +64,12 @@ class EdsClusterImpl return !name.empty() ? name : info_->name(); } + // Updates the internal data structures with a given cluster load assignment. + void update(const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment); + + // EdsResourceRemovalCallback + void onCachedResourceRemoved(absl::string_view resource_name) override; + // ClusterImplBase void reloadHealthyHostsHelper(const HostSharedPtr& host) override; void startPreInit() override; @@ -106,6 +115,13 @@ class EdsClusterImpl // relevant parts of the config for each locality. Note that this field must // be set when LEDS is used. std::unique_ptr cluster_load_assignment_; + + // An optional cache for the EDS resources. + // Upon a (warming) timeout, a cached resource will be used. + Config::EdsResourcesCacheOptRef eds_resources_cache_; + + // Tracks whether a cached resource is used as the current EDS resource. + bool using_cached_resource_{false}; }; using EdsClusterImplSharedPtr = std::shared_ptr; diff --git a/source/extensions/clusters/original_dst/original_dst_cluster.h b/source/extensions/clusters/original_dst/original_dst_cluster.h index 33d5a2f38f01..7a7cfc330eb3 100644 --- a/source/extensions/clusters/original_dst/original_dst_cluster.h +++ b/source/extensions/clusters/original_dst/original_dst_cluster.h @@ -190,5 +190,7 @@ class OriginalDstClusterFactory : public ClusterFactoryImplBase { ClusterFactoryContext& context) override; }; +DECLARE_FACTORY(OriginalDstClusterFactory); + } // namespace Upstream } // namespace Envoy diff --git a/source/extensions/clusters/redis/BUILD b/source/extensions/clusters/redis/BUILD index 464da0ef8bfb..195c9d02e595 100644 --- a/source/extensions/clusters/redis/BUILD +++ b/source/extensions/clusters/redis/BUILD @@ -15,6 +15,7 @@ envoy_cc_library( "crc16.cc", "crc16.h", ], + deps = ["@com_google_absl//absl/strings"], ) envoy_cc_library( diff --git a/source/extensions/clusters/strict_dns/strict_dns_cluster.h b/source/extensions/clusters/strict_dns/strict_dns_cluster.h index c635e0dd9052..f683bba4c4b7 100644 --- a/source/extensions/clusters/strict_dns/strict_dns_cluster.h +++ b/source/extensions/clusters/strict_dns/strict_dns_cluster.h @@ -84,5 +84,7 @@ class StrictDnsClusterFactory : public ClusterFactoryImplBase { ClusterFactoryContext& context) override; }; +DECLARE_FACTORY(StrictDnsClusterFactory); + } // namespace Upstream } // namespace Envoy diff --git a/source/extensions/common/redis/BUILD b/source/extensions/common/redis/BUILD index 3e9ca8cad3d0..d88f1333e684 100644 --- a/source/extensions/common/redis/BUILD +++ b/source/extensions/common/redis/BUILD @@ -16,6 +16,7 @@ envoy_cc_library( name = "cluster_refresh_manager_interface", hdrs = ["cluster_refresh_manager.h"], deps = [ + "//envoy/common:base_includes", ], ) diff --git a/source/extensions/config_subscription/grpc/BUILD b/source/extensions/config_subscription/grpc/BUILD index a3e3fce0d05e..4cbf37e0b9cd 100644 --- a/source/extensions/config_subscription/grpc/BUILD +++ b/source/extensions/config_subscription/grpc/BUILD @@ -9,11 +9,26 @@ licenses(["notice"]) # Apache 2 envoy_extension_package() +envoy_cc_library( + name = "grpc_mux_context_lib", + hdrs = ["grpc_mux_context.h"], + deps = [ + "//envoy/config:custom_config_validators_interface", + "//envoy/config:eds_resources_cache_interface", + "//envoy/config:xds_config_tracker_interface", + "//envoy/config:xds_resources_delegate_interface", + "//envoy/upstream:cluster_manager_interface", + "//source/common/config:utility_lib", + ], +) + envoy_cc_library( name = "grpc_mux_lib", srcs = ["grpc_mux_impl.cc"], hdrs = ["grpc_mux_impl.h"], deps = [ + ":eds_resources_cache_lib", + ":grpc_mux_context_lib", ":grpc_stream_lib", ":xds_source_id_lib", "//envoy/config:custom_config_validators_interface", @@ -34,6 +49,7 @@ envoy_cc_library( "//source/common/memory:utils_lib", "//source/common/protobuf", "@com_google_absl//absl/container:btree", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], ) @@ -44,6 +60,8 @@ envoy_cc_library( hdrs = ["new_grpc_mux_impl.h"], deps = [ ":delta_subscription_state_lib", + ":eds_resources_cache_lib", + ":grpc_mux_context_lib", ":grpc_stream_lib", ":pausable_ack_queue_lib", ":watch_map_lib", @@ -54,6 +72,7 @@ envoy_cc_library( "//source/common/config:xds_context_params_lib", "//source/common/config:xds_resource_lib", "//source/common/memory:utils_lib", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], ) @@ -63,6 +82,7 @@ envoy_cc_library( srcs = ["grpc_subscription_impl.cc"], hdrs = ["grpc_subscription_impl.h"], deps = [ + ":eds_resources_cache_lib", ":grpc_mux_lib", ":new_grpc_mux_lib", "//envoy/config:subscription_interface", @@ -210,6 +230,7 @@ envoy_cc_library( "//source/common/common:minimal_logger_lib", "//source/common/common:utility_lib", "//source/common/config:decoded_resource_lib", + "//source/common/config:resource_name_lib", "//source/common/config:utility_lib", "//source/common/config:xds_resource_lib", "//source/common/protobuf", diff --git a/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc b/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc index 987524f0c204..bfd2c706070c 100644 --- a/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc +++ b/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc @@ -22,16 +22,24 @@ SubscriptionPtr DeltaGrpcCollectionConfigSubscriptionFactory::create( api_config_source, data.api_.randomGenerator(), SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/Config::Utility::factoryForGrpcApiConfigSource( + data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true) + ->createUncachedRawAsyncClient(), + /*dispatcher_=*/data.dispatcher_, + /*service_method_=*/deltaGrpcMethod(data.type_url_), + /*local_info_=*/data.local_info_, + /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*scope_=*/data.scope_, + /*config_validators_=*/std::move(custom_config_validators), + /*xds_resources_delegate_=*/{}, + /*xds_config_tracker_=*/data.xds_config_tracker_, + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr // No EDS resources cache needed from collections. + }; return std::make_unique( - data.collection_locator_.value(), - std::make_shared( - Config::Utility::factoryForGrpcApiConfigSource(data.cm_.grpcAsyncClientManager(), - api_config_source, data.scope_, true) - ->createUncachedRawAsyncClient(), - data.dispatcher_, deltaGrpcMethod(data.type_url_), data.scope_, - Utility::parseRateLimitSettings(api_config_source), data.local_info_, - std::move(custom_config_validators), std::move(backoff_strategy), - data.xds_config_tracker_), + data.collection_locator_.value(), std::make_shared(grpc_mux_context), data.callbacks_, data.resource_decoder_, data.stats_, data.dispatcher_, Utility::configSourceInitialFetchTimeout(data.config_), /*is_aggregated=*/false, data.options_); diff --git a/source/extensions/config_subscription/grpc/grpc_mux_context.h b/source/extensions/config_subscription/grpc/grpc_mux_context.h new file mode 100644 index 000000000000..254cb18b9534 --- /dev/null +++ b/source/extensions/config_subscription/grpc/grpc_mux_context.h @@ -0,0 +1,36 @@ +#pragma once + +#include "envoy/common/backoff_strategy.h" +#include "envoy/config/custom_config_validators.h" +#include "envoy/config/eds_resources_cache.h" +#include "envoy/config/xds_config_tracker.h" +#include "envoy/config/xds_resources_delegate.h" +#include "envoy/event/dispatcher.h" +#include "envoy/grpc/async_client.h" +#include "envoy/local_info/local_info.h" +#include "envoy/stats/scope.h" + +#include "source/common/config/utility.h" + +namespace Envoy { +namespace Config { + +// Context (data) needed for creating a GrpcMux object. +// These are parameters needed for the creation of all GrpcMux objects. +struct GrpcMuxContext { + Grpc::RawAsyncClientPtr async_client_; + Event::Dispatcher& dispatcher_; + const Protobuf::MethodDescriptor& service_method_; + const LocalInfo::LocalInfo& local_info_; + const RateLimitSettings& rate_limit_settings_; + Stats::Scope& scope_; + CustomConfigValidatorsPtr config_validators_; + XdsResourcesDelegateOptRef xds_resources_delegate_; + XdsConfigTrackerOptRef xds_config_tracker_; + BackOffStrategyPtr backoff_strategy_; + const std::string& target_xds_authority_; + EdsResourcesCachePtr eds_resources_cache_; +}; + +} // namespace Config +} // namespace Envoy diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc index 924596483a2c..640609f5af04 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc @@ -6,6 +6,7 @@ #include "source/common/config/utility.h" #include "source/common/memory/utils.h" #include "source/common/protobuf/protobuf.h" +#include "source/extensions/config_subscription/grpc/eds_resources_cache_impl.h" #include "source/extensions/config_subscription/grpc/xds_source_id.h" #include "absl/container/btree_map.h" @@ -56,26 +57,24 @@ std::string convertToWildcard(const std::string& resource_name) { } } // namespace -GrpcMuxImpl::GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, - Grpc::RawAsyncClientPtr async_client, Event::Dispatcher& dispatcher, - const Protobuf::MethodDescriptor& service_method, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings, bool skip_subsequent_node, - CustomConfigValidatorsPtr&& config_validators, - BackOffStrategyPtr backoff_strategy, - XdsConfigTrackerOptRef xds_config_tracker, - XdsResourcesDelegateOptRef xds_resources_delegate, - const std::string& target_xds_authority) - : grpc_stream_(this, std::move(async_client), service_method, dispatcher, scope, - std::move(backoff_strategy), rate_limit_settings), - local_info_(local_info), skip_subsequent_node_(skip_subsequent_node), - config_validators_(std::move(config_validators)), xds_config_tracker_(xds_config_tracker), - xds_resources_delegate_(xds_resources_delegate), target_xds_authority_(target_xds_authority), - first_stream_request_(true), dispatcher_(dispatcher), - dynamic_update_callback_handle_(local_info.contextProvider().addDynamicContextUpdateCallback( - [this](absl::string_view resource_type_url) { - onDynamicContextUpdate(resource_type_url); - })) { - Config::Utility::checkLocalInfo("ads", local_info); +GrpcMuxImpl::GrpcMuxImpl(GrpcMuxContext& grpc_mux_context, bool skip_subsequent_node) + : grpc_stream_(this, std::move(grpc_mux_context.async_client_), + grpc_mux_context.service_method_, grpc_mux_context.dispatcher_, + grpc_mux_context.scope_, std::move(grpc_mux_context.backoff_strategy_), + grpc_mux_context.rate_limit_settings_), + local_info_(grpc_mux_context.local_info_), skip_subsequent_node_(skip_subsequent_node), + config_validators_(std::move(grpc_mux_context.config_validators_)), + xds_config_tracker_(grpc_mux_context.xds_config_tracker_), + xds_resources_delegate_(grpc_mux_context.xds_resources_delegate_), + eds_resources_cache_(std::move(grpc_mux_context.eds_resources_cache_)), + target_xds_authority_(grpc_mux_context.target_xds_authority_), first_stream_request_(true), + dispatcher_(grpc_mux_context.dispatcher_), + dynamic_update_callback_handle_( + grpc_mux_context.local_info_.contextProvider().addDynamicContextUpdateCallback( + [this](absl::string_view resource_type_url) { + onDynamicContextUpdate(resource_type_url); + })) { + Config::Utility::checkLocalInfo("ads", local_info_); AllMuxes::get().insert(this); } @@ -187,8 +186,14 @@ GrpcMuxWatchPtr GrpcMuxImpl::addWatch(const std::string& type_url, SubscriptionCallbacks& callbacks, OpaqueResourceDecoderSharedPtr resource_decoder, const SubscriptionOptions& options) { + // Resource cache is only used for EDS resources. + EdsResourcesCacheOptRef resources_cache{absl::nullopt}; + if (eds_resources_cache_ && + (type_url == Config::getTypeUrl())) { + resources_cache = makeOptRefFromPtr(eds_resources_cache_.get()); + } auto watch = std::make_unique(resources, callbacks, resource_decoder, type_url, - *this, options, local_info_); + *this, options, local_info_, resources_cache); ENVOY_LOG(debug, "gRPC mux addWatch for " + type_url); // Lazily kick off the requests based on first subscription. This has the @@ -412,6 +417,19 @@ void GrpcMuxImpl::processDiscoveryResources(const std::vectorcallbacks_.onConfigUpdate(found_resources, version_info); + // Resource cache is only used for EDS resources. + if (eds_resources_cache_ && + (type_url == Config::getTypeUrl())) { + for (const auto& resource : found_resources) { + const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment = + dynamic_cast( + resource.get().resource()); + eds_resources_cache_->setResource(resource.get().name(), cluster_load_assignment); + } + // No need to remove resources from the cache, as currently only non-collection + // subscriptions are supported, and these resources are removed in the call + // to updateWatchInterest(). + } } } @@ -532,15 +550,28 @@ class GrpcMuxFactory : public MuxFactory { const envoy::config::core::v3::ApiConfigSource& ads_config, const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, - XdsResourcesDelegateOptRef xds_resources_delegate) override { - return std::make_shared( - local_info, std::move(async_client), dispatcher, + XdsResourcesDelegateOptRef xds_resources_delegate, bool use_eds_resources_cache) override { + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::move(async_client), + /*dispatcher_=*/dispatcher, + /*service_method_=*/ *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - scope, Utility::parseRateLimitSettings(ads_config), - ads_config.set_node_on_first_message_only(), std::move(config_validators), - std::move(backoff_strategy), xds_config_tracker, xds_resources_delegate, - Config::Utility::getGrpcControlPlane(ads_config).value_or("")); + /*local_info_=*/local_info, + /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*scope_=*/scope, + /*config_validators_=*/std::move(config_validators), + /*xds_resources_delegate_=*/xds_resources_delegate, + /*xds_config_tracker_=*/xds_config_tracker, + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/Config::Utility::getGrpcControlPlane(ads_config).value_or(""), + /*eds_resources_cache_=*/ + (use_eds_resources_cache && + Runtime::runtimeFeatureEnabled("envoy.restart_features.use_eds_cache_for_ads")) + ? std::make_unique(dispatcher) + : nullptr}; + return std::make_shared(grpc_mux_context, + ads_config.set_node_on_first_message_only()); } }; diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.h b/source/extensions/config_subscription/grpc/grpc_mux_impl.h index 83ac9c558c7e..92a557ef9604 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.h +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.h @@ -7,6 +7,7 @@ #include "envoy/common/random_generator.h" #include "envoy/common/time.h" #include "envoy/config/custom_config_validators.h" +#include "envoy/config/endpoint/v3/endpoint.pb.h" #include "envoy/config/grpc_mux.h" #include "envoy/config/subscription.h" #include "envoy/config/xds_config_tracker.h" @@ -20,10 +21,12 @@ #include "source/common/common/logger.h" #include "source/common/common/utility.h" #include "source/common/config/api_version.h" +#include "source/common/config/resource_name.h" #include "source/common/config/ttl.h" #include "source/common/config/utility.h" #include "source/common/config/xds_context_params.h" #include "source/common/config/xds_resource.h" +#include "source/extensions/config_subscription/grpc/grpc_mux_context.h" #include "source/extensions/config_subscription/grpc/grpc_stream.h" #include "absl/container/node_hash_map.h" @@ -38,13 +41,7 @@ class GrpcMuxImpl : public GrpcMux, public GrpcStreamCallbacks, public Logger::Loggable { public: - GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, Grpc::RawAsyncClientPtr async_client, - Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, - Stats::Scope& scope, const RateLimitSettings& rate_limit_settings, - bool skip_subsequent_node, CustomConfigValidatorsPtr&& config_validators, - BackOffStrategyPtr backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, - XdsResourcesDelegateOptRef xds_resources_delegate, - const std::string& target_xds_authority); + GrpcMuxImpl(GrpcMuxContext& grpc_mux_context, bool skip_subsequent_node); ~GrpcMuxImpl() override; @@ -72,6 +69,10 @@ class GrpcMuxImpl : public GrpcMux, void requestOnDemandUpdate(const std::string&, const absl::flat_hash_set&) override { } + EdsResourcesCacheOptRef edsResourcesCache() override { + return makeOptRefFromPtr(eds_resources_cache_.get()); + } + void handleDiscoveryResponse( std::unique_ptr&& message); @@ -99,17 +100,26 @@ class GrpcMuxImpl : public GrpcMux, SubscriptionCallbacks& callbacks, OpaqueResourceDecoderSharedPtr resource_decoder, const std::string& type_url, GrpcMuxImpl& parent, const SubscriptionOptions& options, - const LocalInfo::LocalInfo& local_info) + const LocalInfo::LocalInfo& local_info, + EdsResourcesCacheOptRef eds_resources_cache) : callbacks_(callbacks), resource_decoder_(resource_decoder), type_url_(type_url), parent_(parent), subscription_options_(options), local_info_(local_info), - watches_(parent.apiStateFor(type_url).watches_) { + watches_(parent.apiStateFor(type_url).watches_), + eds_resources_cache_(eds_resources_cache) { updateResources(resources); + // If eds resources cache is provided, then the type must be ClusterLoadAssignment. + ASSERT( + !eds_resources_cache_.has_value() || + (type_url == Config::getTypeUrl())); } ~GrpcMuxWatchImpl() override { watches_.erase(iter_); if (!resources_.empty()) { parent_.queueDiscoveryRequest(type_url_); + if (eds_resources_cache_.has_value()) { + removeResourcesFromCache(resources_); + } } } @@ -131,7 +141,12 @@ class GrpcMuxImpl : public GrpcMux, private: void updateResources(const absl::flat_hash_set& resources) { - resources_.clear(); + // Finding the list of removed resources by keeping the current resources + // set until the end the function and computing the diff. + // Temporarily keep the resources prior to the update to find which ones + // were removed. + std::set previous_resources; + previous_resources.swap(resources_); std::transform( resources.begin(), resources.end(), std::inserter(resources_, resources_.begin()), [this](const std::string& resource_name) -> std::string { @@ -148,15 +163,49 @@ class GrpcMuxImpl : public GrpcMux, } return resource_name; }); + if (eds_resources_cache_.has_value()) { + // Compute the removed resources and remove them from the cache. + std::set removed_resources; + std::set_difference(previous_resources.begin(), previous_resources.end(), + resources_.begin(), resources_.end(), + std::inserter(removed_resources, removed_resources.begin())); + removeResourcesFromCache(removed_resources); + } // move this watch to the beginning of the list iter_ = watches_.emplace(watches_.begin(), this); } + void removeResourcesFromCache(const std::set& resources_to_remove) { + ASSERT(eds_resources_cache_.has_value()); + // Iterate over the resources to remove, and if no other watcher + // registered for that resource, remove it from the cache. + for (const auto& resource_name : resources_to_remove) { + // Counts the number of watchers that watch the resource. + uint32_t resource_watchers_count = 0; + for (const auto& watch : watches_) { + // Skip the current watcher as it is intending to remove the resource. + if (watch == this) { + continue; + } + if (watch->resources_.find(resource_name) != watch->resources_.end()) { + resource_watchers_count++; + } + } + // Other than "this" watcher, the resource is not watched by any other + // watcher, so it can be removed. + if (resource_watchers_count == 0) { + eds_resources_cache_->removeResource(resource_name); + } + } + } + using WatchList = std::list; const SubscriptionOptions& subscription_options_; const LocalInfo::LocalInfo& local_info_; WatchList& watches_; WatchList::iterator iter_; + // Optional cache for the specific ClusterLoadAssignments of this watch. + EdsResourcesCacheOptRef eds_resources_cache_; }; // Per muxed API state. @@ -212,6 +261,7 @@ class GrpcMuxImpl : public GrpcMux, CustomConfigValidatorsPtr config_validators_; XdsConfigTrackerOptRef xds_config_tracker_; XdsResourcesDelegateOptRef xds_resources_delegate_; + EdsResourcesCachePtr eds_resources_cache_; const std::string target_xds_authority_; bool first_stream_request_; diff --git a/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc b/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc index c7296942e9e7..722bdf5b689e 100644 --- a/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc +++ b/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc @@ -2,6 +2,7 @@ #include "source/common/config/custom_config_validators_impl.h" #include "source/common/config/type_to_endpoint.h" +#include "source/extensions/config_subscription/grpc/grpc_mux_context.h" #include "source/extensions/config_subscription/grpc/grpc_mux_impl.h" #include "source/extensions/config_subscription/grpc/grpc_subscription_impl.h" #include "source/extensions/config_subscription/grpc/new_grpc_mux_impl.h" @@ -24,27 +25,29 @@ GrpcConfigSubscriptionFactory::create(ConfigSubscriptionFactory::SubscriptionDat api_config_source, data.api_.randomGenerator(), SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/Utility::factoryForGrpcApiConfigSource(data.cm_.grpcAsyncClientManager(), + api_config_source, data.scope_, true) + ->createUncachedRawAsyncClient(), + /*dispatcher_=*/data.dispatcher_, + /*service_method_=*/sotwGrpcMethod(data.type_url_), + /*local_info_=*/data.local_info_, + /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*scope_=*/data.scope_, + /*config_validators_=*/std::move(custom_config_validators), + /*xds_resources_delegate_=*/data.xds_resources_delegate_, + /*xds_config_tracker_=*/data.xds_config_tracker_, + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/control_plane_id, + /*eds_resources_cache_=*/nullptr // EDS cache is only used for ADS. + }; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.unified_mux")) { mux = std::make_shared( - Utility::factoryForGrpcApiConfigSource(data.cm_.grpcAsyncClientManager(), api_config_source, - data.scope_, true) - ->createUncachedRawAsyncClient(), - data.dispatcher_, sotwGrpcMethod(data.type_url_), data.scope_, - Utility::parseRateLimitSettings(api_config_source), data.local_info_, - api_config_source.set_node_on_first_message_only(), std::move(custom_config_validators), - std::move(backoff_strategy), data.xds_config_tracker_, data.xds_resources_delegate_, - control_plane_id); + grpc_mux_context, api_config_source.set_node_on_first_message_only()); } else { - mux = std::make_shared( - data.local_info_, - Utility::factoryForGrpcApiConfigSource(data.cm_.grpcAsyncClientManager(), api_config_source, - data.scope_, true) - ->createUncachedRawAsyncClient(), - data.dispatcher_, sotwGrpcMethod(data.type_url_), data.scope_, - Utility::parseRateLimitSettings(api_config_source), - api_config_source.set_node_on_first_message_only(), std::move(custom_config_validators), - std::move(backoff_strategy), data.xds_config_tracker_, data.xds_resources_delegate_, - control_plane_id); + mux = std::make_shared(grpc_mux_context, + api_config_source.set_node_on_first_message_only()); } return std::make_unique( std::move(mux), data.callbacks_, data.resource_decoder_, data.stats_, data.type_url_, @@ -65,23 +68,28 @@ DeltaGrpcConfigSubscriptionFactory::create(ConfigSubscriptionFactory::Subscripti api_config_source, data.api_.randomGenerator(), SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/Utility::factoryForGrpcApiConfigSource(data.cm_.grpcAsyncClientManager(), + api_config_source, data.scope_, true) + ->createUncachedRawAsyncClient(), + /*dispatcher_=*/data.dispatcher_, + /*service_method_=*/deltaGrpcMethod(data.type_url_), + /*local_info_=*/data.local_info_, + /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*scope_=*/data.scope_, + /*config_validators_=*/std::move(custom_config_validators), + /*xds_resources_delegate_=*/{}, + /*xds_config_tracker_=*/data.xds_config_tracker_, + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr // EDS cache is only used for ADS. + }; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.unified_mux")) { mux = std::make_shared( - Utility::factoryForGrpcApiConfigSource(data.cm_.grpcAsyncClientManager(), api_config_source, - data.scope_, true) - ->createUncachedRawAsyncClient(), - data.dispatcher_, deltaGrpcMethod(data.type_url_), data.scope_, - Utility::parseRateLimitSettings(api_config_source), data.local_info_, - api_config_source.set_node_on_first_message_only(), std::move(custom_config_validators), - std::move(backoff_strategy), data.xds_config_tracker_); + grpc_mux_context, api_config_source.set_node_on_first_message_only()); } else { - mux = std::make_shared( - Config::Utility::factoryForGrpcApiConfigSource(data.cm_.grpcAsyncClientManager(), - api_config_source, data.scope_, true) - ->createUncachedRawAsyncClient(), - data.dispatcher_, deltaGrpcMethod(data.type_url_), data.scope_, - Utility::parseRateLimitSettings(api_config_source), data.local_info_, - std::move(custom_config_validators), std::move(backoff_strategy), data.xds_config_tracker_); + mux = std::make_shared(grpc_mux_context); } return std::make_unique( std::move(mux), data.callbacks_, data.resource_decoder_, data.stats_, data.type_url_, diff --git a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc index 2bf989b4c4b1..5a45f97abd8f 100644 --- a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc @@ -11,6 +11,7 @@ #include "source/common/memory/utils.h" #include "source/common/protobuf/protobuf.h" #include "source/common/protobuf/utility.h" +#include "source/extensions/config_subscription/grpc/eds_resources_cache_impl.h" namespace Envoy { namespace Config { @@ -34,22 +35,21 @@ class AllMuxesState { using AllMuxes = ThreadSafeSingleton; } // namespace -NewGrpcMuxImpl::NewGrpcMuxImpl(Grpc::RawAsyncClientPtr&& async_client, - Event::Dispatcher& dispatcher, - const Protobuf::MethodDescriptor& service_method, - Stats::Scope& scope, const RateLimitSettings& rate_limit_settings, - const LocalInfo::LocalInfo& local_info, - CustomConfigValidatorsPtr&& config_validators, - BackOffStrategyPtr backoff_strategy, - XdsConfigTrackerOptRef xds_config_tracker) - : grpc_stream_(this, std::move(async_client), service_method, dispatcher, scope, - std::move(backoff_strategy), rate_limit_settings), - local_info_(local_info), config_validators_(std::move(config_validators)), - dynamic_update_callback_handle_(local_info.contextProvider().addDynamicContextUpdateCallback( - [this](absl::string_view resource_type_url) { - onDynamicContextUpdate(resource_type_url); - })), - dispatcher_(dispatcher), xds_config_tracker_(xds_config_tracker) { +NewGrpcMuxImpl::NewGrpcMuxImpl(GrpcMuxContext& grpc_mux_context) + : grpc_stream_(this, std::move(grpc_mux_context.async_client_), + grpc_mux_context.service_method_, grpc_mux_context.dispatcher_, + grpc_mux_context.scope_, std::move(grpc_mux_context.backoff_strategy_), + grpc_mux_context.rate_limit_settings_), + local_info_(grpc_mux_context.local_info_), + config_validators_(std::move(grpc_mux_context.config_validators_)), + dynamic_update_callback_handle_( + grpc_mux_context.local_info_.contextProvider().addDynamicContextUpdateCallback( + [this](absl::string_view resource_type_url) { + onDynamicContextUpdate(resource_type_url); + })), + dispatcher_(grpc_mux_context.dispatcher_), + xds_config_tracker_(grpc_mux_context.xds_config_tracker_), + eds_resources_cache_(std::move(grpc_mux_context.eds_resources_cache_)) { AllMuxes::get().insert(this); } @@ -242,9 +242,16 @@ void NewGrpcMuxImpl::removeWatch(const std::string& type_url, Watch* watch) { void NewGrpcMuxImpl::addSubscription(const std::string& type_url, const bool use_namespace_matching) { - subscriptions_.emplace(type_url, std::make_unique( - type_url, local_info_, use_namespace_matching, dispatcher_, - *config_validators_.get(), xds_config_tracker_)); + // Resource cache is only used for EDS resources. + EdsResourcesCacheOptRef resources_cache{absl::nullopt}; + if (eds_resources_cache_ && + (type_url == Config::getTypeUrl())) { + resources_cache = makeOptRefFromPtr(eds_resources_cache_.get()); + } + subscriptions_.emplace( + type_url, std::make_unique(type_url, local_info_, use_namespace_matching, + dispatcher_, *config_validators_.get(), + xds_config_tracker_, resources_cache)); subscription_ordering_.emplace_back(type_url); } @@ -340,13 +347,27 @@ class NewGrpcMuxFactory : public MuxFactory { const envoy::config::core::v3::ApiConfigSource& ads_config, const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, - OptRef) override { - return std::make_shared( - std::move(async_client), dispatcher, + OptRef, bool use_eds_resources_cache) override { + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::move(async_client), + /*dispatcher_=*/dispatcher, + /*service_method_=*/ *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.DeltaAggregatedResources"), - scope, Utility::parseRateLimitSettings(ads_config), local_info, - std::move(config_validators), std::move(backoff_strategy), xds_config_tracker); + /*local_info_=*/local_info, + /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*scope_=*/scope, + /*config_validators_=*/std::move(config_validators), + /*xds_resources_delegate_=*/absl::nullopt, + /*xds_config_tracker_=*/xds_config_tracker, + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/ + (use_eds_resources_cache && + Runtime::runtimeFeatureEnabled("envoy.restart_features.use_eds_cache_for_ads")) + ? std::make_unique(dispatcher) + : nullptr}; + return std::make_shared(grpc_mux_context); } }; diff --git a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h index 39a27eeed84b..421cbd9ae05b 100644 --- a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h +++ b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h @@ -4,6 +4,7 @@ #include "envoy/common/random_generator.h" #include "envoy/common/token_bucket.h" +#include "envoy/config/endpoint/v3/endpoint.pb.h" #include "envoy/config/grpc_mux.h" #include "envoy/config/subscription.h" #include "envoy/config/xds_config_tracker.h" @@ -11,9 +12,11 @@ #include "source/common/common/logger.h" #include "source/common/config/api_version.h" +#include "source/common/config/resource_name.h" #include "source/common/grpc/common.h" #include "source/common/runtime/runtime_features.h" #include "source/extensions/config_subscription/grpc/delta_subscription_state.h" +#include "source/extensions/config_subscription/grpc/grpc_mux_context.h" #include "source/extensions/config_subscription/grpc/grpc_stream.h" #include "source/extensions/config_subscription/grpc/pausable_ack_queue.h" #include "source/extensions/config_subscription/grpc/watch_map.h" @@ -31,12 +34,7 @@ class NewGrpcMuxImpl public GrpcStreamCallbacks, Logger::Loggable { public: - NewGrpcMuxImpl(Grpc::RawAsyncClientPtr&& async_client, Event::Dispatcher& dispatcher, - const Protobuf::MethodDescriptor& service_method, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings, - const LocalInfo::LocalInfo& local_info, - CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr backoff_strategy, - XdsConfigTrackerOptRef xds_config_tracker); + NewGrpcMuxImpl(GrpcMuxContext& grpc_mux_context); ~NewGrpcMuxImpl() override; @@ -58,6 +56,10 @@ class NewGrpcMuxImpl void requestOnDemandUpdate(const std::string& type_url, const absl::flat_hash_set& for_update) override; + EdsResourcesCacheOptRef edsResourcesCache() override { + return makeOptRefFromPtr(eds_resources_cache_.get()); + } + ScopedResume pause(const std::string& type_url) override; ScopedResume pause(const std::vector type_urls) override; @@ -86,9 +88,15 @@ class NewGrpcMuxImpl SubscriptionStuff(const std::string& type_url, const LocalInfo::LocalInfo& local_info, const bool use_namespace_matching, Event::Dispatcher& dispatcher, CustomConfigValidators& config_validators, - XdsConfigTrackerOptRef xds_config_tracker) - : watch_map_(use_namespace_matching, type_url, config_validators), - sub_state_(type_url, watch_map_, local_info, dispatcher, xds_config_tracker) {} + XdsConfigTrackerOptRef xds_config_tracker, + EdsResourcesCacheOptRef eds_resources_cache) + : watch_map_(use_namespace_matching, type_url, config_validators, eds_resources_cache), + sub_state_(type_url, watch_map_, local_info, dispatcher, xds_config_tracker) { + // If eds resources cache is provided, then the type must be ClusterLoadAssignment. + ASSERT( + !eds_resources_cache.has_value() || + (type_url == Config::getTypeUrl())); + } WatchMap watch_map_; DeltaSubscriptionState sub_state_; @@ -182,6 +190,7 @@ class NewGrpcMuxImpl Common::CallbackHandlePtr dynamic_update_callback_handle_; Event::Dispatcher& dispatcher_; XdsConfigTrackerOptRef xds_config_tracker_; + EdsResourcesCachePtr eds_resources_cache_; // True iff Envoy is shutting down; no messages should be sent on the `grpc_stream_` when this is // true because it may contain dangling pointers. diff --git a/source/extensions/config_subscription/grpc/watch_map.cc b/source/extensions/config_subscription/grpc/watch_map.cc index b9a1907fd3d3..9c16a4182e45 100644 --- a/source/extensions/config_subscription/grpc/watch_map.cc +++ b/source/extensions/config_subscription/grpc/watch_map.cc @@ -64,8 +64,20 @@ WatchMap::updateWatchInterest(Watch* watch, watch->resource_names_ = update_to_these_names; - return AddedRemoved(findAdditions(newly_added_to_watch, watch), - findRemovals(newly_removed_from_watch, watch)); + // First resources are added and only then removed, so a watch won't be removed + // if its interest has been replaced (rather than completely removed). + absl::flat_hash_set added_resources = findAdditions(newly_added_to_watch, watch); + absl::flat_hash_set removed_resources = + findRemovals(newly_removed_from_watch, watch); + // Remove cached resource that are no longer relevant. + if (eds_resources_cache_.has_value()) { + for (const auto& resource_name : removed_resources) { + // This may pass a resource_name that is not in the cache, for example + // if the resource contents has never arrived. + eds_resources_cache_->removeResource(resource_name); + } + } + return AddedRemoved(std::move(added_resources), std::move(removed_resources)); } absl::flat_hash_set WatchMap::watchesInterestedIn(const std::string& resource_name) { @@ -121,6 +133,10 @@ void WatchMap::onConfigUpdate(const std::vector& resources, ASSERT(deferred_removed_during_update_ == nullptr); deferred_removed_during_update_ = std::make_unique>(); Cleanup cleanup([this] { removeDeferredWatches(); }); + // The xDS server may send a resource that Envoy isn't interested in. This bit array + // will hold an "interesting" bit for each of the resources sent in the update. + std::vector interesting_resources; + interesting_resources.reserve(resources.size()); // Build a map from watches, to the set of updated resources that each watch cares about. Each // entry in the map is then a nice little bundle that can be fed directly into the individual // onConfigUpdate()s. @@ -130,6 +146,9 @@ void WatchMap::onConfigUpdate(const std::vector& resources, for (const auto& interested_watch : interested_in_r) { per_watch_updates[interested_watch].emplace_back(*r); } + // Set the corresponding interested_resources entry to true iff there is a + // watch interested in the resource. + interesting_resources.emplace_back(!interested_in_r.empty()); } // Execute external config validators. @@ -159,6 +178,23 @@ void WatchMap::onConfigUpdate(const std::vector& resources, watch->callbacks_.onConfigUpdate(this_watch_updates->second, version_info); } } + + if (eds_resources_cache_.has_value()) { + // Add/update the watched resources to/in the cache. + // Only resources that have a watcher should be updated. + for (uint32_t resource_idx = 0; resource_idx < resources.size(); ++resource_idx) { + if (interesting_resources[resource_idx]) { + const auto& resource = resources[resource_idx]; + const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment = + dynamic_cast( + resource.get()->resource()); + eds_resources_cache_->setResource(resource.get()->name(), cluster_load_assignment); + } + } + // Note: No need to remove resources from the cache, as currently only non-collection + // subscriptions are supported, and these resources are removed in the call + // to updateWatchInterest(). + } } void WatchMap::onConfigUpdate(const Protobuf::RepeatedPtrField& resources, @@ -242,6 +278,19 @@ void WatchMap::onConfigUpdate( cur_watch->callbacks_.onConfigUpdate({}, {}, system_version_info); } } + + if (eds_resources_cache_.has_value()) { + // Add/update the watched resources to/in the cache. + for (const auto& resource : decoded_resources) { + const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment = + dynamic_cast( + resource.get()->resource()); + eds_resources_cache_->setResource(resource.get()->name(), cluster_load_assignment); + } + // No need to remove resources from the cache, as currently only non-collection + // subscriptions are supported, and these resources are removed in the call + // to updateWatchInterest(). + } } void WatchMap::onConfigUpdateFailed(ConfigUpdateFailureReason reason, const EnvoyException* e) { diff --git a/source/extensions/config_subscription/grpc/watch_map.h b/source/extensions/config_subscription/grpc/watch_map.h index cd5c1973fd02..47cd43b4581f 100644 --- a/source/extensions/config_subscription/grpc/watch_map.h +++ b/source/extensions/config_subscription/grpc/watch_map.h @@ -5,11 +5,13 @@ #include #include "envoy/config/custom_config_validators.h" +#include "envoy/config/eds_resources_cache.h" #include "envoy/config/subscription.h" #include "envoy/service/discovery/v3/discovery.pb.h" #include "source/common/common/assert.h" #include "source/common/common/logger.h" +#include "source/common/config/resource_name.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" @@ -59,12 +61,25 @@ struct Watch { // update the subscription accordingly. // // A WatchMap is assumed to be dedicated to a single type_url type of resource (EDS, CDS, etc). +// +// The WatchMap can also store the fetched resources in a cache, and allow others to fetch +// resources directly from the cache. This is done for EDS in the following case: +// Assume an active EDS cluster exists with some load-assignment that is kept in the cache. +// If the cluster is updated, and no load-assignment is sent from the xDS server, the +// cached version will be used. +// The WatchMap is responsible to update the cache with the resource contents, and it is +// up to the specific xDS type subscription handler (i.e., EdsClusterImpl), to fetch +// the resource from the cache. class WatchMap : public UntypedConfigUpdateCallbacks, public Logger::Loggable { public: WatchMap(const bool use_namespace_matching, const std::string& type_url, - CustomConfigValidators& config_validators) + CustomConfigValidators& config_validators, EdsResourcesCacheOptRef eds_resources_cache) : use_namespace_matching_(use_namespace_matching), type_url_(type_url), - config_validators_(config_validators) {} + config_validators_(config_validators), eds_resources_cache_(eds_resources_cache) { + // If eds resources cache is provided, then the type must be ClusterLoadAssignment. + ASSERT(!eds_resources_cache_.has_value() || + (type_url == Config::getTypeUrl())); + } // Adds 'callbacks' to the WatchMap, with every possible resource being watched. // (Use updateWatchInterest() to narrow it down to some specific names). @@ -133,6 +148,7 @@ class WatchMap : public UntypedConfigUpdateCallbacks, public Logger::Loggable; } // namespace template -GrpcMuxImpl::GrpcMuxImpl( - std::unique_ptr subscription_state_factory, bool skip_subsequent_node, - const LocalInfo::LocalInfo& local_info, Grpc::RawAsyncClientPtr&& async_client, - Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, - Stats::Scope& scope, const RateLimitSettings& rate_limit_settings, - CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr backoff_strategy, - XdsConfigTrackerOptRef xds_config_tracker, XdsResourcesDelegateOptRef xds_resources_delegate, - const std::string& target_xds_authority) - : grpc_stream_(this, std::move(async_client), service_method, dispatcher, scope, - std::move(backoff_strategy), rate_limit_settings), +GrpcMuxImpl::GrpcMuxImpl(std::unique_ptr subscription_state_factory, + GrpcMuxContext& grpc_mux_content, bool skip_subsequent_node) + : grpc_stream_(this, std::move(grpc_mux_content.async_client_), + grpc_mux_content.service_method_, grpc_mux_content.dispatcher_, + grpc_mux_content.scope_, std::move(grpc_mux_content.backoff_strategy_), + grpc_mux_content.rate_limit_settings_), subscription_state_factory_(std::move(subscription_state_factory)), - skip_subsequent_node_(skip_subsequent_node), local_info_(local_info), - dynamic_update_callback_handle_(local_info.contextProvider().addDynamicContextUpdateCallback( - [this](absl::string_view resource_type_url) { - onDynamicContextUpdate(resource_type_url); - })), - config_validators_(std::move(config_validators)), xds_config_tracker_(xds_config_tracker), - xds_resources_delegate_(xds_resources_delegate), target_xds_authority_(target_xds_authority) { - Config::Utility::checkLocalInfo("ads", local_info); + skip_subsequent_node_(skip_subsequent_node), local_info_(grpc_mux_content.local_info_), + dynamic_update_callback_handle_( + grpc_mux_content.local_info_.contextProvider().addDynamicContextUpdateCallback( + [this](absl::string_view resource_type_url) { + onDynamicContextUpdate(resource_type_url); + })), + config_validators_(std::move(grpc_mux_content.config_validators_)), + xds_config_tracker_(grpc_mux_content.xds_config_tracker_), + xds_resources_delegate_(grpc_mux_content.xds_resources_delegate_), + eds_resources_cache_(std::move(grpc_mux_content.eds_resources_cache_)), + target_xds_authority_(grpc_mux_content.target_xds_authority_) { + Config::Utility::checkLocalInfo("ads", grpc_mux_content.local_info_); AllMuxes::get().insert(this); } @@ -84,12 +86,19 @@ Config::GrpcMuxWatchPtr GrpcMuxImpl::addWatch( const SubscriptionOptions& options) { auto watch_map = watch_maps_.find(type_url); if (watch_map == watch_maps_.end()) { + // Resource cache is only used for EDS resources. + EdsResourcesCacheOptRef resources_cache{absl::nullopt}; + if (eds_resources_cache_ && + (type_url == Config::getTypeUrl())) { + resources_cache = makeOptRefFromPtr(eds_resources_cache_.get()); + } + // We don't yet have a subscription for type_url! Make one! - watch_map = - watch_maps_ - .emplace(type_url, std::make_unique(options.use_namespace_matching_, type_url, - *config_validators_.get())) - .first; + watch_map = watch_maps_ + .emplace(type_url, + std::make_unique(options.use_namespace_matching_, type_url, + *config_validators_.get(), resources_cache)) + .first; subscriptions_.emplace(type_url, subscription_state_factory_->makeSubscriptionState( type_url, *watch_maps_[type_url], resource_decoder, xds_config_tracker_, xds_resources_delegate_, @@ -361,17 +370,9 @@ template class GrpcMuxImpl; // Delta- and SotW-specific concrete subclasses: -GrpcMuxDelta::GrpcMuxDelta(Grpc::RawAsyncClientPtr&& async_client, Event::Dispatcher& dispatcher, - const Protobuf::MethodDescriptor& service_method, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings, - const LocalInfo::LocalInfo& local_info, bool skip_subsequent_node, - CustomConfigValidatorsPtr&& config_validators, - BackOffStrategyPtr backoff_strategy, - XdsConfigTrackerOptRef xds_config_tracker) - : GrpcMuxImpl(std::make_unique(dispatcher), skip_subsequent_node, - local_info, std::move(async_client), dispatcher, service_method, scope, - rate_limit_settings, std::move(config_validators), std::move(backoff_strategy), - xds_config_tracker) {} +GrpcMuxDelta::GrpcMuxDelta(GrpcMuxContext& grpc_mux_context, bool skip_subsequent_node) + : GrpcMuxImpl(std::make_unique(grpc_mux_context.dispatcher_), + grpc_mux_context, skip_subsequent_node) {} // GrpcStreamCallbacks for GrpcMuxDelta void GrpcMuxDelta::requestOnDemandUpdate(const std::string& type_url, @@ -384,19 +385,9 @@ void GrpcMuxDelta::requestOnDemandUpdate(const std::string& type_url, } } -GrpcMuxSotw::GrpcMuxSotw(Grpc::RawAsyncClientPtr&& async_client, Event::Dispatcher& dispatcher, - const Protobuf::MethodDescriptor& service_method, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings, - const LocalInfo::LocalInfo& local_info, bool skip_subsequent_node, - CustomConfigValidatorsPtr&& config_validators, - BackOffStrategyPtr backoff_strategy, - XdsConfigTrackerOptRef xds_config_tracker, - XdsResourcesDelegateOptRef xds_resources_delegate, - const std::string& target_xds_authority) - : GrpcMuxImpl(std::make_unique(dispatcher), skip_subsequent_node, - local_info, std::move(async_client), dispatcher, service_method, scope, - rate_limit_settings, std::move(config_validators), std::move(backoff_strategy), - xds_config_tracker, xds_resources_delegate, target_xds_authority) {} +GrpcMuxSotw::GrpcMuxSotw(GrpcMuxContext& grpc_mux_context, bool skip_subsequent_node) + : GrpcMuxImpl(std::make_unique(grpc_mux_context.dispatcher_), + grpc_mux_context, skip_subsequent_node) {} Config::GrpcMuxWatchPtr NullGrpcMuxImpl::addWatch(const std::string&, const absl::flat_hash_set&, @@ -416,15 +407,28 @@ class DeltaGrpcMuxFactory : public MuxFactory { const envoy::config::core::v3::ApiConfigSource& ads_config, const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, - XdsResourcesDelegateOptRef) override { - return std::make_shared( - std::move(async_client), dispatcher, + XdsResourcesDelegateOptRef, bool use_eds_resources_cache) override { + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::move(async_client), + /*dispatcher_=*/dispatcher, + /*service_method_=*/ *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v3.AggregatedDiscoveryService." - "DeltaAggregatedResources"), - scope, Envoy::Config::Utility::parseRateLimitSettings(ads_config), local_info, - ads_config.set_node_on_first_message_only(), std::move(config_validators), - std::move(backoff_strategy), xds_config_tracker); + "envoy.service.discovery.v3.AggregatedDiscoveryService.DeltaAggregatedResources"), + /*local_info_=*/local_info, + /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*scope_=*/scope, + /*config_validators_=*/std::move(config_validators), + /*xds_resources_delegate_=*/absl::nullopt, + /*xds_config_tracker_=*/xds_config_tracker, + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/ + (use_eds_resources_cache && + Runtime::runtimeFeatureEnabled("envoy.restart_features.use_eds_cache_for_ads")) + ? std::make_unique(dispatcher) + : nullptr}; + return std::make_shared(grpc_mux_context, + ads_config.set_node_on_first_message_only()); } }; @@ -438,15 +442,28 @@ class SotwGrpcMuxFactory : public MuxFactory { const envoy::config::core::v3::ApiConfigSource& ads_config, const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, - XdsResourcesDelegateOptRef) override { - return std::make_shared( - std::move(async_client), dispatcher, + XdsResourcesDelegateOptRef, bool use_eds_resources_cache) override { + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::move(async_client), + /*dispatcher_=*/dispatcher, + /*service_method_=*/ *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v3.AggregatedDiscoveryService." - "StreamAggregatedResources"), - scope, Envoy::Config::Utility::parseRateLimitSettings(ads_config), local_info, - ads_config.set_node_on_first_message_only(), std::move(config_validators), - std::move(backoff_strategy), xds_config_tracker); + "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), + /*local_info_=*/local_info, + /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*scope_=*/scope, + /*config_validators_=*/std::move(config_validators), + /*xds_resources_delegate_=*/absl::nullopt, + /*xds_config_tracker_=*/xds_config_tracker, + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/ + (use_eds_resources_cache && + Runtime::runtimeFeatureEnabled("envoy.restart_features.use_eds_cache_for_ads")) + ? std::make_unique(dispatcher) + : nullptr}; + return std::make_shared(grpc_mux_context, + ads_config.set_node_on_first_message_only()); } }; diff --git a/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.h b/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.h index 7669ca73cffd..f7063dc76f7c 100644 --- a/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.h +++ b/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.h @@ -21,6 +21,7 @@ #include "source/common/common/utility.h" #include "source/common/config/api_version.h" #include "source/common/grpc/common.h" +#include "source/extensions/config_subscription/grpc/grpc_mux_context.h" #include "source/extensions/config_subscription/grpc/grpc_stream.h" #include "source/extensions/config_subscription/grpc/pausable_ack_queue.h" #include "source/extensions/config_subscription/grpc/watch_map.h" @@ -59,14 +60,8 @@ class GrpcMuxImpl : public GrpcStreamCallbacks, public ShutdownableMux, Logger::Loggable { public: - GrpcMuxImpl(std::unique_ptr subscription_state_factory, bool skip_subsequent_node, - const LocalInfo::LocalInfo& local_info, Grpc::RawAsyncClientPtr&& async_client, - Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, - Stats::Scope& scope, const RateLimitSettings& rate_limit_settings, - CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr backoff_strategy, - XdsConfigTrackerOptRef xds_config_tracker, - XdsResourcesDelegateOptRef xds_resources_delegate = absl::nullopt, - const std::string& target_xds_authority = ""); + GrpcMuxImpl(std::unique_ptr subscription_state_factory, GrpcMuxContext& grpc_mux_context, + bool skip_subsequent_node); ~GrpcMuxImpl() override; @@ -108,6 +103,10 @@ class GrpcMuxImpl : public GrpcStreamCallbacks, genericHandleResponse(message->type_url(), *message, control_plane_stats); } + EdsResourcesCacheOptRef edsResourcesCache() override { + return makeOptRefFromPtr(eds_resources_cache_.get()); + } + GrpcStream& grpcStreamForTest() { return grpc_stream_; } protected: @@ -209,6 +208,7 @@ class GrpcMuxImpl : public GrpcStreamCallbacks, CustomConfigValidatorsPtr config_validators_; XdsConfigTrackerOptRef xds_config_tracker_; XdsResourcesDelegateOptRef xds_resources_delegate_; + EdsResourcesCachePtr eds_resources_cache_; const std::string target_xds_authority_; // True iff Envoy is shutting down; no messages should be sent on the `grpc_stream_` when this is @@ -220,11 +220,7 @@ class GrpcMuxDelta : public GrpcMuxImpl { public: - GrpcMuxDelta(Grpc::RawAsyncClientPtr&& async_client, Event::Dispatcher& dispatcher, - const Protobuf::MethodDescriptor& service_method, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings, const LocalInfo::LocalInfo& local_info, - bool skip_subsequent_node, CustomConfigValidatorsPtr&& config_validators, - BackOffStrategyPtr backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker); + GrpcMuxDelta(GrpcMuxContext& grpc_mux_context, bool skip_subsequent_node); // GrpcStreamCallbacks void requestOnDemandUpdate(const std::string& type_url, @@ -235,13 +231,7 @@ class GrpcMuxSotw : public GrpcMuxImpl { public: - GrpcMuxSotw(Grpc::RawAsyncClientPtr&& async_client, Event::Dispatcher& dispatcher, - const Protobuf::MethodDescriptor& service_method, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings, const LocalInfo::LocalInfo& local_info, - bool skip_subsequent_node, CustomConfigValidatorsPtr&& config_validators, - BackOffStrategyPtr backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, - XdsResourcesDelegateOptRef xds_resources_delegate = absl::nullopt, - const std::string& target_xds_authority = ""); + GrpcMuxSotw(GrpcMuxContext& grpc_mux_context, bool skip_subsequent_node); // GrpcStreamCallbacks void requestOnDemandUpdate(const std::string&, const absl::flat_hash_set&) override { @@ -267,6 +257,8 @@ class NullGrpcMuxImpl : public GrpcMux { void requestOnDemandUpdate(const std::string&, const absl::flat_hash_set&) override { ENVOY_BUG(false, "unexpected request for on demand update"); } + + EdsResourcesCacheOptRef edsResourcesCache() override { return {}; } }; } // namespace XdsMux diff --git a/source/extensions/early_data/default_early_data_policy.h b/source/extensions/early_data/default_early_data_policy.h index 50d9aa5924d8..81c3865972bf 100644 --- a/source/extensions/early_data/default_early_data_policy.h +++ b/source/extensions/early_data/default_early_data_policy.h @@ -1,3 +1,5 @@ +#pragma once + #include #include "envoy/extensions/early_data/v3/default_early_data_policy.pb.h" diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 7385b590a13a..d6164dacef35 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -150,6 +150,7 @@ EXTENSIONS = { "envoy.filters.http.header_to_metadata": "//source/extensions/filters/http/header_to_metadata:config", "envoy.filters.http.health_check": "//source/extensions/filters/http/health_check:config", "envoy.filters.http.ip_tagging": "//source/extensions/filters/http/ip_tagging:config", + "envoy.filters.http.json_to_metadata": "//source/extensions/filters/http/json_to_metadata:config", "envoy.filters.http.jwt_authn": "//source/extensions/filters/http/jwt_authn:config", "envoy.filters.http.rate_limit_quota": "//source/extensions/filters/http/rate_limit_quota:config", # Disabled by default diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index f1d743fd6e66..20f020339680 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -406,6 +406,13 @@ envoy.filters.http.ip_tagging: status: stable type_urls: - envoy.extensions.filters.http.ip_tagging.v3.IPTagging +envoy.filters.http.json_to_metadata: + categories: + - envoy.filters.http + security_posture: robust_to_untrusted_downstream + status: alpha + type_urls: + - envoy.extensions.filters.http.json_to_metadata.v3.JsonToMetadata envoy.filters.http.jwt_authn: categories: - envoy.filters.http diff --git a/source/extensions/filters/http/admission_control/evaluators/BUILD b/source/extensions/filters/http/admission_control/evaluators/BUILD index 2b7b1b562cf9..3aaf39cdfcd2 100644 --- a/source/extensions/filters/http/admission_control/evaluators/BUILD +++ b/source/extensions/filters/http/admission_control/evaluators/BUILD @@ -20,6 +20,7 @@ envoy_cc_library( deps = [ "//envoy/grpc:status", "//source/common/common:enum_to_int", + "//source/common/common:fmt_lib", "@envoy_api//envoy/extensions/filters/http/admission_control/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/http/cache/BUILD b/source/extensions/filters/http/cache/BUILD index 8918ec59e2bf..fbe778851c59 100644 --- a/source/extensions/filters/http/cache/BUILD +++ b/source/extensions/filters/http/cache/BUILD @@ -21,6 +21,7 @@ envoy_cc_library( ":cache_entry_utils_lib", ":cache_filter_logging_info_lib", ":cache_headers_utils_lib", + ":cache_insert_queue_lib", ":cacheability_utils_lib", ":http_cache_lib", "//source/common/common:enum_to_int", @@ -57,6 +58,16 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "cache_insert_queue_lib", + srcs = ["cache_insert_queue.cc"], + hdrs = ["cache_insert_queue.h"], + deps = [ + ":http_cache_lib", + "//source/common/buffer:buffer_lib", + ], +) + envoy_cc_library( name = "cache_policy_lib", hdrs = ["cache_policy.h"], diff --git a/source/extensions/filters/http/cache/cache_filter.cc b/source/extensions/filters/http/cache/cache_filter.cc index 5ec72e1af84a..5b69a3825c0d 100644 --- a/source/extensions/filters/http/cache/cache_filter.cc +++ b/source/extensions/filters/http/cache/cache_filter.cc @@ -39,11 +39,19 @@ CacheFilter::CacheFilter(const envoy::extensions::filters::http::cache::v3::Cach void CacheFilter::onDestroy() { filter_state_ = FilterState::Destroyed; - if (lookup_) { + if (lookup_ != nullptr) { lookup_->onDestroy(); } - if (insert_) { - insert_->onDestroy(); + if (insert_queue_ != nullptr) { + // The filter can complete and be destroyed while there is still data being + // written to the cache. In this case the filter hands ownership of the + // queue to itself, which cancels all the callbacks to the filter, but allows + // the queue to complete any write operations before deleting itself. + // + // In the case that the queue is already empty, or in a state which cannot + // complete, setSelfOwned will provoke the queue to abort the write operation. + insert_queue_->setSelfOwned(std::move(insert_queue_)); + insert_queue_.reset(); } } @@ -134,15 +142,22 @@ Http::FilterHeadersStatus CacheFilter::encodeHeaders(Http::ResponseHeaderMap& he if (request_allows_inserts_ && !is_head_request_ && CacheabilityUtils::isCacheableResponse(headers, vary_allow_list_)) { ENVOY_STREAM_LOG(debug, "CacheFilter::encodeHeaders inserting headers", *encoder_callbacks_); - insert_ = cache_->makeInsertContext(std::move(lookup_), *encoder_callbacks_); - // Add metadata associated with the cached response. Right now this is only response_time; - const ResponseMetadata metadata = {time_source_.systemTime()}; - // TODO(capoferro): Note that there is currently no way to communicate back to the CacheFilter - // that an insert has failed. If an insert fails partway, it's better not to send additional - // chunks to the cache if we're already in a failure state and should abort, but we can only do - // that if we can communicate failures back to the filter, so we should fix this. - insert_->insertHeaders( - headers, metadata, [](bool) {}, end_stream); + auto insert_context = cache_->makeInsertContext(std::move(lookup_), *encoder_callbacks_); + if (insert_context != nullptr) { + // The callbacks passed to CacheInsertQueue are all called through the dispatcher, + // so they're thread-safe. During CacheFilter::onDestroy the queue is given ownership + // of itself and all the callbacks are cancelled, so they are also filter-destruction-safe. + insert_queue_ = + std::make_unique(*encoder_callbacks_, std::move(insert_context), + // Cache aborted callback. + [this]() { + insert_queue_ = nullptr; + insert_status_ = InsertStatus::InsertAbortedByCache; + }); + // Add metadata associated with the cached response. Right now this is only response_time; + const ResponseMetadata metadata = {time_source_.systemTime()}; + insert_queue_->insertHeaders(headers, metadata, end_stream); + } if (end_stream) { insert_status_ = InsertStatus::InsertSucceeded; } @@ -165,16 +180,15 @@ Http::FilterDataStatus CacheFilter::encodeData(Buffer::Instance& data, bool end_ // Stop the encoding stream until the cached response is fetched & added to the encoding stream. return Http::FilterDataStatus::StopIterationAndBuffer; } - if (insert_) { + if (insert_queue_ != nullptr) { ENVOY_STREAM_LOG(debug, "CacheFilter::encodeData inserting body", *encoder_callbacks_); - // TODO(toddmgreer): Wait for the cache if necessary. - insert_->insertBody( - data, [](bool) {}, end_stream); + insert_queue_->insertBody(data, end_stream); if (end_stream) { + // We don't actually know if the insert succeeded, but as far as the + // filter is concerned it has been fully handed off to the cache + // implementation. insert_status_ = InsertStatus::InsertSucceeded; } - // insert_status_ remains absl::nullopt if end_stream == false, as we have not completed the - // insertion yet. } return Http::FilterDataStatus::Continue; } @@ -190,9 +204,9 @@ Http::FilterTrailersStatus CacheFilter::encodeTrailers(Http::ResponseTrailerMap& return Http::FilterTrailersStatus::StopIteration; } response_has_trailers_ = !trailers.empty(); - if (insert_) { + if (insert_queue_ != nullptr) { ENVOY_STREAM_LOG(debug, "CacheFilter::encodeTrailers inserting trailers", *encoder_callbacks_); - insert_->insertTrailers(trailers, [](bool) {}); + insert_queue_->insertTrailers(trailers); } insert_status_ = InsertStatus::InsertSucceeded; @@ -671,7 +685,7 @@ LookupStatus CacheFilter::lookupStatus() const { } InsertStatus CacheFilter::insertStatus() const { - return insert_status_.value_or((insert_ == nullptr) + return insert_status_.value_or((insert_queue_ == nullptr) ? InsertStatus::NoInsertRequestIncomplete : InsertStatus::InsertAbortedResponseIncomplete); } diff --git a/source/extensions/filters/http/cache/cache_filter.h b/source/extensions/filters/http/cache/cache_filter.h index 5b4977d1bbfc..b547275c64e4 100644 --- a/source/extensions/filters/http/cache/cache_filter.h +++ b/source/extensions/filters/http/cache/cache_filter.h @@ -10,6 +10,7 @@ #include "source/common/common/logger.h" #include "source/extensions/filters/http/cache/cache_filter_logging_info.h" #include "source/extensions/filters/http/cache/cache_headers_utils.h" +#include "source/extensions/filters/http/cache/cache_insert_queue.h" #include "source/extensions/filters/http/cache/http_cache.h" #include "source/extensions/filters/http/common/pass_through_filter.h" @@ -126,10 +127,13 @@ class CacheFilter : public Http::PassThroughFilter, // being cancelled. InsertStatus insertStatus() const; + // insert_queue_ ownership may be passed to the queue itself during + // CacheFilter::onDestroy, allowing the insert queue to outlive the filter + // while the necessary cache write operations complete. + std::unique_ptr insert_queue_; TimeSource& time_source_; OptRef cache_; LookupContextPtr lookup_; - InsertContextPtr insert_; LookupResultPtr lookup_result_; // Tracks what body bytes still need to be read from the cache. This is diff --git a/source/extensions/filters/http/cache/cache_insert_queue.cc b/source/extensions/filters/http/cache/cache_insert_queue.cc new file mode 100644 index 000000000000..143417e596e0 --- /dev/null +++ b/source/extensions/filters/http/cache/cache_insert_queue.cc @@ -0,0 +1,211 @@ +#include "source/extensions/filters/http/cache/cache_insert_queue.h" + +#include "source/common/buffer/buffer_impl.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Cache { + +// Representation of a piece of data to be sent to a cache for writing. +class CacheInsertFragment { +public: + // Sends a fragment to the cache. + // on_complete is called when the cache completes the operation. + virtual void + send(InsertContext& context, + std::function on_complete) PURE; + + virtual ~CacheInsertFragment() = default; +}; + +// A CacheInsertFragment containing some amount of http response body data. +// The size of a fragment is equal to the size of the buffer arriving at +// CacheFilter::encodeData. +class CacheInsertFragmentBody : public CacheInsertFragment { +public: + CacheInsertFragmentBody(const Buffer::Instance& buffer, bool end_stream) + : buffer_(buffer), end_stream_(end_stream) {} + + void + send(InsertContext& context, + std::function on_complete) override { + size_t sz = buffer_.length(); + context.insertBody( + std::move(buffer_), + [on_complete, end_stream = end_stream_, sz](bool cache_success) { + on_complete(cache_success, end_stream, sz); + }, + end_stream_); + } + +private: + Buffer::OwnedImpl buffer_; + const bool end_stream_; +}; + +// A CacheInsertFragment containing the full trailers of the response. +class CacheInsertFragmentTrailers : public CacheInsertFragment { +public: + explicit CacheInsertFragmentTrailers(const Http::ResponseTrailerMap& trailers) + : trailers_(Http::ResponseTrailerMapImpl::create()) { + Http::ResponseTrailerMapImpl::copyFrom(*trailers_, trailers); + } + + void + send(InsertContext& context, + std::function on_complete) override { + // While zero isn't technically true for the size of trailers, it doesn't + // matter at this point because watermarks after the stream is complete + // aren't useful. + context.insertTrailers( + *trailers_, [on_complete](bool cache_success) { on_complete(cache_success, true, 0); }); + } + +private: + std::unique_ptr trailers_; +}; + +CacheInsertQueue::CacheInsertQueue(Http::StreamEncoderFilterCallbacks& encoder_callbacks, + InsertContextPtr insert_context, AbortInsertCallback abort) + : dispatcher_(encoder_callbacks.dispatcher()), insert_context_(std::move(insert_context)), + low_watermark_bytes_(encoder_callbacks.encoderBufferLimit() / 2), + high_watermark_bytes_(encoder_callbacks.encoderBufferLimit()), + encoder_callbacks_(encoder_callbacks), abort_callback_(abort) {} + +void CacheInsertQueue::insertHeaders(const Http::ResponseHeaderMap& response_headers, + const ResponseMetadata& metadata, bool end_stream) { + end_stream_queued_ = end_stream; + // While zero isn't technically true for the size of headers, headers are + // typically excluded from the stream buffer limit. + insert_context_->insertHeaders( + response_headers, metadata, + [this, end_stream](bool cache_success) { onFragmentComplete(cache_success, end_stream, 0); }, + end_stream); + fragment_in_flight_ = true; +} + +void CacheInsertQueue::insertBody(const Buffer::Instance& fragment, bool end_stream) { + if (end_stream) { + end_stream_queued_ = true; + } + if (fragment_in_flight_) { + size_t sz = fragment.length(); + queue_size_bytes_ += sz; + fragments_.push_back(std::make_unique(fragment, end_stream)); + if (!watermarked_ && queue_size_bytes_ > high_watermark_bytes_) { + if (encoder_callbacks_.has_value()) { + encoder_callbacks_.value().get().onEncoderFilterAboveWriteBufferHighWatermark(); + } + watermarked_ = true; + } + } else { + insert_context_->insertBody( + Buffer::OwnedImpl(fragment), + [this, end_stream](bool cache_success) { + onFragmentComplete(cache_success, end_stream, 0); + }, + end_stream); + fragment_in_flight_ = true; + } +} + +void CacheInsertQueue::insertTrailers(const Http::ResponseTrailerMap& trailers) { + end_stream_queued_ = true; + if (fragment_in_flight_) { + fragments_.push_back(std::make_unique(trailers)); + } else { + insert_context_->insertTrailers( + trailers, [this](bool cache_success) { onFragmentComplete(cache_success, true, 0); }); + fragment_in_flight_ = true; + } +} + +void CacheInsertQueue::onFragmentComplete(bool cache_success, bool end_stream, size_t sz) { + // If the cache implementation is asynchronous, this may be called from whatever + // thread that cache implementation runs on. Therefore, we post it to the + // dispatcher to be certain any callbacks and updates are called on the filter's + // thread (and therefore we don't have to mutex-guard anything). + dispatcher_.post([this, cache_success, end_stream, sz]() { + fragment_in_flight_ = false; + if (aborting_) { + // Parent filter was destroyed, so we can quit this operation. + fragments_.clear(); + self_ownership_.reset(); + return; + } + ASSERT(queue_size_bytes_ >= sz, "queue can't be emptied by more than its size"); + queue_size_bytes_ -= sz; + if (watermarked_ && queue_size_bytes_ <= low_watermark_bytes_) { + if (encoder_callbacks_.has_value()) { + encoder_callbacks_.value().get().onEncoderFilterBelowWriteBufferLowWatermark(); + } + watermarked_ = false; + } + if (!cache_success) { + // canceled by cache; unwatermark if necessary, inform the filter if + // it's still around, and delete the queue. + if (watermarked_) { + if (encoder_callbacks_.has_value()) { + encoder_callbacks_.value().get().onEncoderFilterBelowWriteBufferLowWatermark(); + } + watermarked_ = false; + } + fragments_.clear(); + // Clearing self-ownership might provoke the destructor, so take a copy of the + // abort callback to avoid reading from 'this' after it may be deleted. + auto abort_callback = abort_callback_; + self_ownership_.reset(); + abort_callback(); + return; + } + if (end_stream) { + ASSERT(fragments_.empty(), "ending a stream with the queue not empty is a bug"); + ASSERT(!watermarked_, "being over the high watermark when the queue is empty makes no sense"); + self_ownership_.reset(); + return; + } + if (!fragments_.empty()) { + // If there's more in the queue, push the next fragment to the cache. + auto fragment = std::move(fragments_.front()); + fragments_.pop_front(); + fragment_in_flight_ = true; + fragment->send(*insert_context_, [this](bool cache_success, bool end_stream, size_t sz) { + onFragmentComplete(cache_success, end_stream, sz); + }); + } + }); +} + +void CacheInsertQueue::setSelfOwned(std::unique_ptr self) { + // If we sent a high watermark event, this is our last chance to unset it on the + // stream, so we'd better do so. + if (watermarked_) { + encoder_callbacks_->onEncoderFilterBelowWriteBufferLowWatermark(); + watermarked_ = false; + } + // Disable all the callbacks, they're going to have nowhere to go. + abort_callback_ = []() {}; + encoder_callbacks_.reset(); + if (fragments_.empty() && !fragment_in_flight_) { + // If the queue is already empty we can just let it be destroyed immediately. + return; + } + if (!end_stream_queued_) { + // If the queue can't be completed we can abort early but we need to wait for + // any callback-in-flight to complete before destroying the queue. + aborting_ = true; + } + self_ownership_ = std::move(self); +} + +CacheInsertQueue::~CacheInsertQueue() { + ASSERT(!watermarked_, "should not have a watermarked status when the queue is destroyed"); + ASSERT(fragments_.empty(), "queue should be empty by the time the destructor is run"); + insert_context_->onDestroy(); +} + +} // namespace Cache +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/cache/cache_insert_queue.h b/source/extensions/filters/http/cache/cache_insert_queue.h new file mode 100644 index 000000000000..81cf577eb7a3 --- /dev/null +++ b/source/extensions/filters/http/cache/cache_insert_queue.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include "source/extensions/filters/http/cache/http_cache.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Cache { + +using OverHighWatermarkCallback = std::function; +using UnderLowWatermarkCallback = std::function; +using AbortInsertCallback = std::function; +class CacheInsertFragment; + +// This queue acts as an intermediary between CacheFilter and the cache +// implementation extension. Having a queue allows CacheFilter to stream at its +// normal rate, while allowing a cache implementation to run asynchronously and +// potentially at a slower rate, without having to implement its own buffer. +// +// If the queue contains more than the "high watermark" for the buffer +// (encoder_callbacks.encoderBufferLimit()), then a high watermark event is +// sent to the encoder, which may cause the filter to slow down, to allow the +// cache implementation time to catch up and avoid buffering significantly +// more data in memory than the configuration intends to allow. When this happens, +// the queue must drain to half the encoderBufferLimit before a low watermark +// event is sent to resume normal flow. +// +// From the cache implementation's perspective, the queue ensures that the cache +// receives data one piece at a time - no more data will be delivered until the +// cache implementation calls the provided callback indicating that it is ready +// to receive more data. +class CacheInsertQueue { +public: + CacheInsertQueue(Http::StreamEncoderFilterCallbacks& encoder_callbacks, + InsertContextPtr insert_context, AbortInsertCallback abort); + void insertHeaders(const Http::ResponseHeaderMap& response_headers, + const ResponseMetadata& metadata, bool end_stream); + void insertBody(const Buffer::Instance& fragment, bool end_stream); + void insertTrailers(const Http::ResponseTrailerMap& trailers); + void setSelfOwned(std::unique_ptr self); + ~CacheInsertQueue(); + +private: + void onFragmentComplete(bool cache_success, bool end_stream, size_t sz); + + Event::Dispatcher& dispatcher_; + const InsertContextPtr insert_context_; + const size_t low_watermark_bytes_, high_watermark_bytes_; + OptRef encoder_callbacks_; + AbortInsertCallback abort_callback_; + std::deque> fragments_; + // Size of the data currently in the queue (including any fragment in flight). + size_t queue_size_bytes_ = 0; + // True when the high watermark has been exceeded and the low watermark + // threshold has not been crossed since. + bool watermarked_ = false; + // True when the queue has sent a fragment to the cache implementation and has + // not yet received a response. + bool fragment_in_flight_ = false; + // True if end_stream has been queued. If the queue gets handed ownership + // of itself before the end is in sight then it might as well abort since + // it's not going to get a complete entry. + bool end_stream_queued_ = false; + // If the filter was deleted while !end_stream_queued_, aborting_ is set to + // true; when the next fragment completes (or cancels), the queue is destroyed. + bool aborting_ = false; + // When the filter is destroyed, it passes ownership of CacheInsertQueue + // to itself, because CacheInsertQueue can outlive the filter. The queue + // will remove its self-ownership (thereby deleting itself) upon + // completion of its work. + std::unique_ptr self_ownership_; +}; + +} // namespace Cache +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/cache/http_cache.h b/source/extensions/filters/http/cache/http_cache.h index 2a2e9c6e30c3..f5b670c25199 100644 --- a/source/extensions/filters/http/cache/http_cache.h +++ b/source/extensions/filters/http/cache/http_cache.h @@ -134,24 +134,34 @@ class InsertContext { public: // Accepts response_headers for caching. Only called once. // - // Implementations must call insert_complete(true) on success, or - // insert_complete(false) to attempt to abort the insertion. + // Implementations MUST call insert_complete(true) on success, or + // insert_complete(false) to attempt to abort the insertion. This + // call may be made asynchronously, but any async operation that can + // potentially silently fail must include a timeout, to avoid memory leaks. virtual void insertHeaders(const Http::ResponseHeaderMap& response_headers, const ResponseMetadata& metadata, InsertCallback insert_complete, bool end_stream) PURE; - // The insertion is streamed into the cache in chunks whose size is determined + // The insertion is streamed into the cache in fragments whose size is determined // by the client, but with a pace determined by the cache. To avoid streaming // data into cache too fast for the cache to handle, clients should wait for - // the cache to call readyForNextChunk() before streaming the next chunk. + // the cache to call ready_for_next_fragment before sending the next fragment. // // The client can abort the streaming insertion by dropping the // InsertContextPtr. A cache can abort the insertion by passing 'false' into - // ready_for_next_chunk. - virtual void insertBody(const Buffer::Instance& chunk, InsertCallback ready_for_next_chunk, + // ready_for_next_fragment. + // + // The cache implementation MUST call ready_for_next_fragment. This call may be + // made asynchronously, but any async operation that can potentially silently + // fail must include a timeout, to avoid memory leaks. + virtual void insertBody(const Buffer::Instance& fragment, InsertCallback ready_for_next_fragment, bool end_stream) PURE; // Inserts trailers into the cache. + // + // The cache implementation MUST call insert_complete. This call may be + // made asynchronously, but any async operation that can potentially silently + // fail must include a timeout, to avoid memory leaks. virtual void insertTrailers(const Http::ResponseTrailerMap& trailers, InsertCallback insert_complete) PURE; @@ -169,7 +179,7 @@ class InsertContext { // --> RPCInsertContext's destructor and onRpcDone cause a data race in RpcInsertContext. // onDestroy() should cancel any outstanding async operations and, if necessary, // it should block on that cancellation to avoid data races. InsertContext must not invoke any - // callbacks to the CacheFilter after having onDestroy() invoked. + // callbacks to the CacheFilter after returning from onDestroy(). virtual void onDestroy() PURE; virtual ~InsertContext() = default; @@ -185,7 +195,7 @@ class LookupContext { // twice. virtual void getHeaders(LookupHeadersCallback&& cb) PURE; - // Reads the next chunk from the cache, calling cb when the chunk is ready. + // Reads the next fragment from the cache, calling cb when the fragment is ready. // The Buffer::InstancePtr passed to cb must not be null. // // The cache must call cb with a range of bytes starting at range.start() and @@ -194,10 +204,10 @@ class LookupContext { // can report an error, and cause the response to be aborted, by calling cb // with nullptr. // - // If a cache happens to load data in chunks of a set size, it may be + // If a cache happens to load data in fragments of a set size, it may be // efficient to respond with fewer than the requested number of bytes. For // example, assuming a 23 byte full-bodied response from a cache that reads in - // absurdly small 10 byte chunks: + // absurdly small 10 byte fragments: // // getBody requests bytes 0-23 .......... callback with bytes 0-9 // getBody requests bytes 10-23 .......... callback with bytes 10-19 diff --git a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc index 3ab49ca0bf26..c1314d18c90d 100644 --- a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc +++ b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc @@ -111,8 +111,8 @@ ProxyFilterConfig::ThreadLocalClusterInfo::~ThreadLocalClusterInfo() { } } -void ProxyFilterConfig::onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) { - const std::string& cluster_name = cluster.info()->name(); +void ProxyFilterConfig::onClusterAddOrUpdate(absl::string_view cluster_name, + Upstream::ThreadLocalClusterCommand&) { ENVOY_LOG(debug, "thread local cluster {} added or updated", cluster_name); ThreadLocalClusterInfo& tls_cluster_info = *tls_slot_; auto it = tls_cluster_info.pending_clusters_.find(cluster_name); diff --git a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.h b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.h index db9a417a2ad9..e63b96107db8 100644 --- a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.h +++ b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.h @@ -59,7 +59,8 @@ class ProxyFilterConfig : public Upstream::ClusterUpdateCallbacks, Upstream::ClusterUpdateCallbacksHandlePtr addThreadLocalClusterUpdateCallbacks(); // Upstream::ClusterUpdateCallbacks - void onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) override; + void onClusterAddOrUpdate(absl::string_view cluster_name, + Upstream::ThreadLocalClusterCommand&) override; void onClusterRemoval(const std::string&) override; private: diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index d8d387c65514..2dbedef31749 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -142,14 +142,12 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { if (buffer_data_ && !skip_check_) { - const bool buffer_is_full = isBufferFull(); + const bool buffer_is_full = isBufferFull(data.length()); if (end_stream || buffer_is_full) { ENVOY_STREAM_LOG(debug, "ext_authz filter finished buffering the request since {}", *decoder_callbacks_, buffer_is_full ? "buffer is full" : "stream is ended"); - if (!buffer_is_full) { - // Make sure data is available in initiateCall. - decoder_callbacks_->addDecodedData(data, true); - } + // Make sure data is available in initiateCall. + decoder_callbacks_->addDecodedData(data, true); initiateCall(*request_headers_); return filter_return_ == FilterReturn::StopDecoding ? Http::FilterDataStatus::StopIterationAndWatermark @@ -444,12 +442,18 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { } } -bool Filter::isBufferFull() const { +bool Filter::isBufferFull(uint64_t num_bytes_processing) const { + if (!config_->allowPartialMessage()) { + return false; + } + + uint64_t num_bytes_buffered = num_bytes_processing; const auto* buffer = decoder_callbacks_->decodingBuffer(); - if (config_->allowPartialMessage() && buffer != nullptr) { - return buffer->length() >= config_->maxRequestBytes(); + if (buffer != nullptr) { + num_bytes_buffered += buffer->length(); } - return false; + + return num_bytes_buffered >= config_->maxRequestBytes(); } void Filter::continueDecoding() { diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 90aab7bb8294..91482a93eca7 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -319,7 +319,7 @@ class Filter : public Logger::Loggable, void addResponseHeaders(Http::HeaderMap& header_map, const Http::HeaderVector& headers); void initiateCall(const Http::RequestHeaderMap& headers); void continueDecoding(); - bool isBufferFull() const; + bool isBufferFull(uint64_t num_bytes_processing) const; // This holds a set of flags defined in per-route configuration. struct PerRouteFlags { diff --git a/source/extensions/filters/http/ext_proc/client.h b/source/extensions/filters/http/ext_proc/client.h index 1cf1bb605308..e4c7dba2fef3 100644 --- a/source/extensions/filters/http/ext_proc/client.h +++ b/source/extensions/filters/http/ext_proc/client.h @@ -32,6 +32,7 @@ class ExternalProcessorCallbacks { std::unique_ptr&& response) PURE; virtual void onGrpcError(Grpc::Status::GrpcStatus error) PURE; virtual void onGrpcClose() PURE; + virtual void logGrpcStreamInfo() PURE; }; class ExternalProcessorClient { diff --git a/source/extensions/filters/http/ext_proc/client_impl.cc b/source/extensions/filters/http/ext_proc/client_impl.cc index b3d9d32c563e..22d970929b2d 100644 --- a/source/extensions/filters/http/ext_proc/client_impl.cc +++ b/source/extensions/filters/http/ext_proc/client_impl.cc @@ -75,6 +75,7 @@ void ExternalProcessorStreamImpl::onReceiveTrailingMetadata(Http::ResponseTraile void ExternalProcessorStreamImpl::onRemoteClose(Grpc::Status::GrpcStatus status, const std::string& message) { ENVOY_LOG(debug, "gRPC stream closed remotely with status {}: {}", status, message); + callbacks_.logGrpcStreamInfo(); stream_closed_ = true; if (status == Grpc::Status::Ok) { callbacks_.onGrpcClose(); diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 7f876bfb6fbd..245d88d6160f 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -171,7 +171,7 @@ Filter::StreamOpenState Filter::openStream() { stats_.streams_started_.inc(); // For custom access logging purposes. Applicable only for Envoy gRPC as Google gRPC does not // have a proper implementation of streamInfo. - if (grpc_service_.has_envoy_grpc()) { + if (grpc_service_.has_envoy_grpc() && logging_info_ != nullptr) { logging_info_->setClusterInfo(stream_->streamInfo().upstreamClusterInfo()); } } @@ -180,11 +180,6 @@ Filter::StreamOpenState Filter::openStream() { void Filter::closeStream() { if (stream_) { - if (grpc_service_.has_envoy_grpc()) { - logging_info_->setBytesSent(stream_->streamInfo().bytesSent()); - logging_info_->setBytesReceived(stream_->streamInfo().bytesReceived()); - logging_info_->setUpstreamHost(stream_->streamInfo().upstreamInfo()->upstreamHost()); - } ENVOY_LOG(debug, "Calling close on stream"); if (stream_->close()) { stats_.streams_closed_.inc(); @@ -463,7 +458,7 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& return FilterTrailersStatus::StopIteration; } - if (!body_delivered && state.bodyMode() == ProcessingMode::BUFFERED) { + if (!body_delivered && state.bufferedData() && state.bodyMode() == ProcessingMode::BUFFERED) { // If no gRPC stream yet, opens it before sending data. switch (openStream()) { case StreamOpenState::Error: @@ -477,7 +472,8 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& // We would like to process the body in a buffered way, but until now the complete // body has not arrived. With the arrival of trailers, we now know that the body // has arrived. - sendBufferedData(state, ProcessorState::CallbackState::BufferedBodyCallback, true); + sendBodyChunk(state, *state.bufferedData(), ProcessorState::CallbackState::BufferedBodyCallback, + false); state.setPaused(true); return FilterTrailersStatus::StopIteration; } @@ -544,7 +540,7 @@ FilterTrailersStatus Filter::encodeTrailers(ResponseTrailerMap& trailers) { void Filter::sendBodyChunk(ProcessorState& state, const Buffer::Instance& data, ProcessorState::CallbackState new_state, bool end_stream) { - ENVOY_LOG(debug, "Sending a body chunk of {} bytes", data.length()); + ENVOY_LOG(debug, "Sending a body chunk of {} bytes, end_stram {}", data.length(), end_stream); state.onStartProcessorCall(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout(), new_state); ProcessingRequest req; @@ -555,17 +551,6 @@ void Filter::sendBodyChunk(ProcessorState& state, const Buffer::Instance& data, stats_.stream_msgs_sent_.inc(); } -void Filter::sendBufferedData(ProcessorState& state, ProcessorState::CallbackState new_state, - bool end_stream) { - if (state.hasBufferedData()) { - sendBodyChunk(state, *state.bufferedData(), new_state, end_stream); - } else { - // If there is no buffered data, sends an empty body. - Buffer::OwnedImpl data(""); - sendBodyChunk(state, data, new_state, end_stream); - } -} - void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers) { ProcessingRequest req; auto* trailers_req = state.mutableTrailers(req); @@ -578,6 +563,20 @@ void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers stats_.stream_msgs_sent_.inc(); } +void Filter::logGrpcStreamInfo() { + if (stream_ != nullptr && logging_info_ != nullptr && grpc_service_.has_envoy_grpc()) { + const auto& upstream_meter = stream_->streamInfo().getUpstreamBytesMeter(); + if (upstream_meter != nullptr) { + logging_info_->setBytesSent(upstream_meter->wireBytesSent()); + logging_info_->setBytesReceived(upstream_meter->wireBytesReceived()); + } + // Only set upstream host in logging info once. + if (logging_info_->upstreamHost() == nullptr) { + logging_info_->setUpstreamHost(stream_->streamInfo().upstreamInfo()->upstreamHost()); + } + } +} + void Filter::onNewTimeout(const ProtobufWkt::Duration& override_message_timeout) { const auto result = DurationUtil::durationToMillisecondsNoThrow(override_message_timeout); if (!result.ok()) { @@ -657,6 +656,11 @@ void Filter::onReceiveMessage(std::unique_ptr&& r) { // We won't be sending anything more to the stream after we // receive this message. ENVOY_LOG(debug, "Sending immediate response"); + // TODO(tyxia) For immediate response case here and below, logging is needed because + // `onFinishProcessorCalls` is called after `closeStream` below. + // Investigate to see if we can switch the order of those two so that the logging here can be + // avoided. + logGrpcStreamInfo(); processing_complete_ = true; closeStream(); onFinishProcessorCalls(Grpc::Status::Ok); @@ -691,6 +695,7 @@ void Filter::onReceiveMessage(std::unique_ptr&& r) { ENVOY_LOG(debug, "Sending immediate response: {}", processing_status.message()); stats_.stream_msgs_received_.inc(); processing_complete_ = true; + logGrpcStreamInfo(); closeStream(); onFinishProcessorCalls(processing_status.raw_code()); ImmediateResponse invalid_mutation_response; @@ -728,6 +733,7 @@ void Filter::onGrpcError(Grpc::Status::GrpcStatus status) { void Filter::onGrpcClose() { ENVOY_LOG(debug, "Received gRPC stream close"); + processing_complete_ = true; stats_.streams_closed_.inc(); // Successful close. We can ignore the stream for the rest of our request @@ -738,6 +744,7 @@ void Filter::onGrpcClose() { void Filter::onMessageTimeout() { ENVOY_LOG(debug, "message timeout reached"); + logGrpcStreamInfo(); stats_.message_timeouts_.inc(); if (config_->failureModeAllow()) { // The user would like a timeout to not cause message processing to fail. diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 5c8e347a06a6..49e04c69e94b 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -247,7 +247,7 @@ class Filter : public Logger::Loggable, const FilterConfig& config() const { return *config_; } ExtProcFilterStats& stats() { return stats_; } - ExtProcLoggingInfo& loggingInfo() { return *logging_info_; } + ExtProcLoggingInfo* loggingInfo() { return logging_info_; } void onDestroy() override; void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override; @@ -264,20 +264,15 @@ class Filter : public Logger::Loggable, Http::FilterTrailersStatus encodeTrailers(Http::ResponseTrailerMap& trailers) override; // ExternalProcessorCallbacks - void onReceiveMessage( std::unique_ptr&& response) override; - void onGrpcError(Grpc::Status::GrpcStatus error) override; - void onGrpcClose() override; + void logGrpcStreamInfo() override; void onMessageTimeout(); void onNewTimeout(const ProtobufWkt::Duration& override_message_timeout); - void sendBufferedData(ProcessorState& state, ProcessorState::CallbackState new_state, - bool end_stream); - void sendBodyChunk(ProcessorState& state, const Buffer::Instance& data, ProcessorState::CallbackState new_state, bool end_stream); diff --git a/source/extensions/filters/http/ext_proc/processor_state.cc b/source/extensions/filters/http/ext_proc/processor_state.cc index 4e1ea746ac59..a4de619e1b50 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.cc +++ b/source/extensions/filters/http/ext_proc/processor_state.cc @@ -34,13 +34,17 @@ void ProcessorState::onStartProcessorCall(Event::TimerCb cb, std::chrono::millis void ProcessorState::onFinishProcessorCall(Grpc::Status::GrpcStatus call_status, CallbackState next_state) { + filter_.logGrpcStreamInfo(); + stopMessageTimer(); if (call_start_time_.has_value()) { std::chrono::microseconds duration = std::chrono::duration_cast( filter_callbacks_->dispatcher().timeSource().monotonicTime() - call_start_time_.value()); - filter_.loggingInfo().recordGrpcCall(duration, call_status, callback_state_, - trafficDirection()); + ExtProcLoggingInfo* logging_info = filter_.loggingInfo(); + if (logging_info != nullptr) { + logging_info->recordGrpcCall(duration, call_status, callback_state_, trafficDirection()); + } call_start_time_ = absl::nullopt; } callback_state_ = next_state; @@ -126,13 +130,18 @@ absl::Status ProcessorState::handleHeadersResponse(const HeadersResponse& respon } else if (complete_body_available_ && body_mode_ != ProcessingMode::NONE) { // If we get here, then all the body data came in before the header message // was complete, and the server wants the body. It doesn't matter whether the - // processing mode is buffered, streamed, or partially streamed -- if we get - // here then the whole body is in the buffer and we can proceed as if the - // "buffered" processing mode was set. - ENVOY_LOG(debug, "Sending buffered request body message"); - filter_.sendBufferedData(*this, ProcessorState::CallbackState::BufferedBodyCallback, true); - clearWatermark(); - return absl::OkStatus(); + // processing mode is buffered, streamed, or partially buffered. + if (bufferedData()) { + // Get here, no_body_ = false, and complete_body_available_ = true, the end_stream + // flag of decodeData() can be determined by whether the trailers are received. + // Also, bufferedData() is not nullptr means decodeData() is called, even though + // the data can be an empty chunk. + filter_.sendBodyChunk(*this, *bufferedData(), + ProcessorState::CallbackState::BufferedBodyCallback, + !trailers_available_); + clearWatermark(); + return absl::OkStatus(); + } } else if (body_mode_ == ProcessingMode::BUFFERED) { // Here, we're not ready to continue processing because then // we won't be able to modify the headers any more, so do nothing and diff --git a/source/extensions/filters/http/json_to_metadata/BUILD b/source/extensions/filters/http/json_to_metadata/BUILD new file mode 100644 index 000000000000..e91b66d4c558 --- /dev/null +++ b/source/extensions/filters/http/json_to_metadata/BUILD @@ -0,0 +1,35 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "json_to_metadata_lib", + srcs = ["filter.cc"], + hdrs = ["filter.h"], + deps = [ + "//envoy/server:filter_config_interface", + "//source/common/http:header_utility_lib", + "//source/common/json:json_loader_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "@envoy_api//envoy/extensions/filters/http/json_to_metadata/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":json_to_metadata_lib", + "//envoy/registry", + "//source/extensions/filters/http/common:factory_base_lib", + "@envoy_api//envoy/extensions/filters/http/json_to_metadata/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/filters/http/json_to_metadata/config.cc b/source/extensions/filters/http/json_to_metadata/config.cc new file mode 100644 index 000000000000..3b9af089b073 --- /dev/null +++ b/source/extensions/filters/http/json_to_metadata/config.cc @@ -0,0 +1,35 @@ +#include "source/extensions/filters/http/json_to_metadata/config.h" + +#include "envoy/http/header_map.h" +#include "envoy/registry/registry.h" + +#include "source/common/protobuf/utility.h" +#include "source/extensions/filters/http/json_to_metadata/filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace JsonToMetadata { + +JsonToMetadataConfig::JsonToMetadataConfig() : FactoryBase("envoy.filters.http.json_to_metadata") {} + +Http::FilterFactoryCb JsonToMetadataConfig::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata& proto_config, + const std::string&, Server::Configuration::FactoryContext& context) { + std::shared_ptr config = + std::make_shared(proto_config, context.scope()); + + return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared(config)); + }; +} + +/** + * Static registration for this filter. @see RegisterFactory. + */ +REGISTER_FACTORY(JsonToMetadataConfig, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace JsonToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/json_to_metadata/config.h b/source/extensions/filters/http/json_to_metadata/config.h new file mode 100644 index 000000000000..0e83f8778fd9 --- /dev/null +++ b/source/extensions/filters/http/json_to_metadata/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "envoy/extensions/filters/http/json_to_metadata/v3/json_to_metadata.pb.h" +#include "envoy/extensions/filters/http/json_to_metadata/v3/json_to_metadata.pb.validate.h" + +#include "source/extensions/filters/http/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace JsonToMetadata { + +class JsonToMetadataConfig + : public Extensions::HttpFilters::Common::FactoryBase< + envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata> { +public: + JsonToMetadataConfig(); + +private: + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata&, + const std::string&, Server::Configuration::FactoryContext&) override; +}; + +} // namespace JsonToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/json_to_metadata/filter.cc b/source/extensions/filters/http/json_to_metadata/filter.cc new file mode 100644 index 000000000000..966d295a98a3 --- /dev/null +++ b/source/extensions/filters/http/json_to_metadata/filter.cc @@ -0,0 +1,389 @@ +#include "source/extensions/filters/http/json_to_metadata/filter.h" + +#include "source/common/http/header_map_impl.h" +#include "source/common/http/utility.h" +#include "source/common/json/json_loader.h" + +#include "absl/strings/str_cat.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace JsonToMetadata { + +namespace { + +struct JsonValueToStringConverter { + std::string operator()(bool&& val) { return std::to_string(val); } + std::string operator()(int64_t&& val) { return std::to_string(val); } + std::string operator()(double&& val) { return std::to_string(val); } + std::string operator()(std::string&& val) { return std::move(val); } +}; + +struct JsonValueToDoubleConverter { + absl::StatusOr operator()(bool&& val) { return static_cast(val); } + absl::StatusOr operator()(int64_t&& val) { return static_cast(val); } + absl::StatusOr operator()(double&& val) { return val; } + absl::StatusOr operator()(std::string&& val) { + double dval; + if (absl::SimpleAtod(StringUtil::trim(val), &dval)) { + return dval; + } + return absl::InternalError(fmt::format("value {} to number conversion failed", val)); + } +}; + +struct JsonValueToProtobufValueConverter { + absl::StatusOr operator()(bool&& val) { + ProtobufWkt::Value protobuf_value; + protobuf_value.set_bool_value(val); + return protobuf_value; + } + absl::StatusOr operator()(int64_t&& val) { + ProtobufWkt::Value protobuf_value; + protobuf_value.set_number_value(val); + return protobuf_value; + } + absl::StatusOr operator()(double&& val) { + ProtobufWkt::Value protobuf_value; + protobuf_value.set_number_value(val); + return protobuf_value; + } + absl::StatusOr operator()(std::string&& val) { + if (val.size() > MAX_PAYLOAD_VALUE_LEN) { + return absl::InternalError( + fmt::format("metadata value is too long. value.length: {}", val.size())); + } + ProtobufWkt::Value protobuf_value; + protobuf_value.set_string_value(std::move(val)); + return protobuf_value; + } +}; + +} // anonymous namespace + +Rule::Rule(const ProtoRule& rule) : rule_(rule) { + if (!rule_.has_on_present() && !rule_.has_on_missing()) { + throw EnvoyException("json to metadata filter: neither `on_present` nor `on_missing` set"); + } + + if (rule_.has_on_missing() && !rule_.on_missing().has_value()) { + throw EnvoyException( + "json to metadata filter: cannot specify on_missing rule with empty value"); + } + + if (rule_.has_on_error() && !rule_.on_error().has_value()) { + throw EnvoyException("json to metadata filter: cannot specify on_error rule with empty value"); + } + + // Support key selectors only. + for (const auto& selector : rule.selectors()) { + keys_.push_back(selector.key()); + } +} + +FilterConfig::FilterConfig( + const envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata& proto_config, + Stats::Scope& scope) + : stats_{ALL_JSON_TO_METADATA_FILTER_STATS(POOL_COUNTER_PREFIX(scope, "json_to_metadata."))}, + request_rules_(generateRequestRules(proto_config)), + request_allow_content_types_(generateRequestAllowContentTypes(proto_config)), + request_allow_empty_content_type_(proto_config.request_rules().allow_empty_content_type()) {} + +Rules FilterConfig::generateRequestRules( + const envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata& proto_config) + const { + Rules rules; + for (const auto& rule : proto_config.request_rules().rules()) { + rules.emplace_back(rule); + } + return rules; +} + +absl::flat_hash_set FilterConfig::generateRequestAllowContentTypes( + const envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata& proto_config) + const { + if (proto_config.request_rules().allow_content_types().empty()) { + return {Http::Headers::get().ContentTypeValues.Json}; + } + + absl::flat_hash_set allow_content_types; + for (const auto& request_allowed_content_type : + proto_config.request_rules().allow_content_types()) { + allow_content_types.insert(request_allowed_content_type); + } + return allow_content_types; +} + +bool FilterConfig::requestContentTypeAllowed(absl::string_view content_type) const { + if (content_type.empty()) { + return request_allow_empty_content_type_; + } + + return request_allow_content_types_.contains(content_type); +} + +void Filter::applyKeyValue(const std::string& value, const KeyValuePair& keyval, + StructMap& struct_map) { + ASSERT(!value.empty()); + + ProtobufWkt::Value val; + val.set_string_value(value); + applyKeyValue(std::move(val), keyval, struct_map); +} + +void Filter::applyKeyValue(double value, const KeyValuePair& keyval, StructMap& struct_map) { + ProtobufWkt::Value val; + val.set_number_value(value); + applyKeyValue(std::move(val), keyval, struct_map); +} + +void Filter::applyKeyValue(ProtobufWkt::Value value, const KeyValuePair& keyval, + StructMap& struct_map) { + const auto& nspace = decideNamespace(keyval.metadata_namespace()); + addMetadata(nspace, keyval.key(), std::move(value), keyval.preserve_existing_metadata_value(), + struct_map); +} + +const std::string& Filter::decideNamespace(const std::string& nspace) const { + static const std::string& jsonToMetadata = "envoy.filters.http.json_to_metadata"; + return nspace.empty() ? jsonToMetadata : nspace; +} + +bool Filter::addMetadata(const std::string& meta_namespace, const std::string& key, + ProtobufWkt::Value val, const bool preserve_existing_metadata_value, + StructMap& struct_map) { + + if (preserve_existing_metadata_value) { + // TODO(kuochunghsu): support encoding + auto& filter_metadata = decoder_callbacks_->streamInfo().dynamicMetadata().filter_metadata(); + const auto entry_it = filter_metadata.find(meta_namespace); + + if (entry_it != filter_metadata.end()) { + const auto& metadata = entry_it->second; + if (metadata.fields().contains(key)) { + ENVOY_LOG(trace, "Found key {} in namespace {}. Preserve the existing metadata value.", key, + meta_namespace); + return false; + } + } + } + + ENVOY_LOG(trace, "add metadata ns:{} key:{}", meta_namespace, key); + auto& keyval = struct_map[meta_namespace]; + (*keyval.mutable_fields())[key] = std::move(val); + + return true; +} + +void Filter::finalizeDynamicMetadata(Http::StreamFilterCallbacks& filter_callback, + const StructMap& struct_map, bool& processing_finished_flag) { + ASSERT(!processing_finished_flag); + processing_finished_flag = true; + if (!struct_map.empty()) { + for (auto const& entry : struct_map) { + filter_callback.streamInfo().setDynamicMetadata(entry.first, entry.second); + } + } +} + +void Filter::handleAllOnMissing(const Rules& rules, bool& processing_finished_flag) { + StructMap struct_map; + for (const auto& rule : rules) { + if (rule.rule_.has_on_missing()) { + applyKeyValue(rule.rule_.on_missing().value(), rule.rule_.on_missing(), struct_map); + } + } + + finalizeDynamicMetadata(*decoder_callbacks_, struct_map, processing_finished_flag); +} + +void Filter::handleOnMissing(const Rule& rule, StructMap& struct_map) { + if (rule.rule_.has_on_missing()) { + applyKeyValue(rule.rule_.on_missing().value(), rule.rule_.on_missing(), struct_map); + } +} + +void Filter::handleAllOnError(const Rules& rules, bool& processing_finished_flag) { + StructMap struct_map; + for (const auto& rule : rules) { + if (rule.rule_.has_on_error()) { + applyKeyValue(rule.rule_.on_error().value(), rule.rule_.on_error(), struct_map); + } + } + finalizeDynamicMetadata(*decoder_callbacks_, struct_map, processing_finished_flag); +} + +absl::Status Filter::handleOnPresent(Json::ObjectSharedPtr parent_node, const std::string& key, + const Rule& rule, StructMap& struct_map) { + if (!rule.rule_.has_on_present()) { + return absl::OkStatus(); + } + + auto& on_present_keyval = rule.rule_.on_present(); + if (on_present_keyval.has_value()) { + applyKeyValue(on_present_keyval.value(), on_present_keyval, struct_map); + return absl::OkStatus(); + } + + absl::StatusOr result = parent_node->getValue(key); + if (!result.ok()) { + return result.status(); + } + + switch (on_present_keyval.type()) { + PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; + case envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata::PROTOBUF_VALUE: + if (auto value_result = + absl::visit(JsonValueToProtobufValueConverter(), std::move(result.value())); + value_result.ok()) { + applyKeyValue(value_result.value(), on_present_keyval, struct_map); + } else { + return value_result.status(); + } + break; + case envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata::NUMBER: + if (auto double_result = absl::visit(JsonValueToDoubleConverter(), std::move(result.value())); + double_result.ok()) { + applyKeyValue(double_result.value(), on_present_keyval, struct_map); + } else { + return double_result.status(); + } + break; + case envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata::STRING: + std::string str = absl::visit(JsonValueToStringConverter(), std::move(result.value())); + if (str.size() > MAX_PAYLOAD_VALUE_LEN) { + return absl::InvalidArgumentError( + fmt::format("metadata value is too long. value.length: {}", str.size())); + } + + // Note that this applies to on_present by not adding any metadata. + if (str.empty()) { + ENVOY_LOG(debug, "value is empty, not adding metadata. key: {}", on_present_keyval.key()); + return absl::OkStatus(); + } + + applyKeyValue(std::move(str), on_present_keyval, struct_map); + break; + } + return absl::OkStatus(); +} + +void Filter::processBody(const Buffer::Instance* body, const Rules& rules, + bool& processing_finished_flag, Stats::Counter& success, + Stats::Counter& no_body, Stats::Counter& non_json) { + // In case we have trailers but no body. + if (!body || body->length() == 0) { + handleAllOnMissing(rules, request_processing_finished_); + no_body.inc(); + return; + } + + absl::StatusOr result = + Json::Factory::loadFromStringNoThrow(body->toString()); + if (!result.ok()) { + ENVOY_LOG(debug, result.status().message()); + non_json.inc(); + handleAllOnError(rules, processing_finished_flag); + return; + } + + Json::ObjectSharedPtr body_json = std::move(result.value()); + // A pure string or number is considered a valid application/json body, but it is not a JSON + // object. Therefore, we treat this case as 'on_missing' for all rules in the absence of any + // key-value pairs to match. + if (!body_json) { + ENVOY_LOG( + debug, + "Apply on_missing for all rules on a valid application/json body but not a json object."); + handleAllOnMissing(rules, request_processing_finished_); + // This JSON body is valid and successfully parsed. + success.inc(); + return; + } + + StructMap struct_map; + for (const auto& rule : rules) { + const auto& keys = rule.keys_; + Json::ObjectSharedPtr node = body_json; + bool on_missing = false; + for (unsigned long i = 0; i < keys.size() - 1; i++) { + absl::StatusOr next_node_result = node->getObjectNoThrow(keys[i]); + if (!next_node_result.ok()) { + ENVOY_LOG(warn, result.status().message()); + handleOnMissing(rule, struct_map); + on_missing = true; + break; + } + node = std::move(next_node_result.value()); + } + if (on_missing) { + continue; + } + absl::Status result = handleOnPresent(std::move(node), keys.back(), rule, struct_map); + if (!result.ok()) { + ENVOY_LOG(warn, fmt::format("{} key: {}", result.message(), keys.back())); + handleOnMissing(rule, struct_map); + } + } + success.inc(); + + finalizeDynamicMetadata(*decoder_callbacks_, struct_map, processing_finished_flag); +} + +void Filter::processRequestBody() { + processBody(decoder_callbacks_->decodingBuffer(), config_->requestRules(), + request_processing_finished_, config_->stats().rq_success_, + config_->stats().rq_no_body_, config_->stats().rq_invalid_json_body_); +} + +Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { + ASSERT(config_->doRequest()); + if (!config_->requestContentTypeAllowed(headers.getContentTypeValue())) { + request_processing_finished_ = true; + config_->stats().rq_mismatched_content_type_.inc(); + return Http::FilterHeadersStatus::Continue; + } + + if (end_stream) { + handleAllOnMissing(config_->requestRules(), request_processing_finished_); + config_->stats().rq_no_body_.inc(); + return Http::FilterHeadersStatus::Continue; + } + return Http::FilterHeadersStatus::StopIteration; +} + +Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { + ASSERT(config_->doRequest()); + if (request_processing_finished_) { + return Http::FilterDataStatus::Continue; + } + + if (end_stream) { + decoder_callbacks_->addDecodedData(data, true); + + if (!decoder_callbacks_->decodingBuffer() || + decoder_callbacks_->decodingBuffer()->length() == 0) { + handleAllOnMissing(config_->requestRules(), request_processing_finished_); + config_->stats().rq_no_body_.inc(); + return Http::FilterDataStatus::Continue; + } + processRequestBody(); + return Http::FilterDataStatus::Continue; + } + + return Http::FilterDataStatus::StopIterationAndBuffer; +} + +Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap&) { + ASSERT(config_->doRequest()); + if (!request_processing_finished_) { + processRequestBody(); + } + return Http::FilterTrailersStatus::Continue; +} + +} // namespace JsonToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/json_to_metadata/filter.h b/source/extensions/filters/http/json_to_metadata/filter.h new file mode 100644 index 000000000000..562c6f0ab17a --- /dev/null +++ b/source/extensions/filters/http/json_to_metadata/filter.h @@ -0,0 +1,133 @@ +#pragma once + +#include +#include + +#include "envoy/extensions/filters/http/json_to_metadata/v3/json_to_metadata.pb.h" +#include "envoy/json/json_object.h" +#include "envoy/server/filter_config.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats.h" +#include "envoy/stats/stats_macros.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace JsonToMetadata { + +/** + * All stats for the Json to Metadata filter. @see stats_macros.h + */ +#define ALL_JSON_TO_METADATA_FILTER_STATS(COUNTER) \ + COUNTER(rq_success) \ + COUNTER(rq_mismatched_content_type) \ + COUNTER(rq_no_body) \ + COUNTER(rq_invalid_json_body) + +/** + * Wrapper struct for Json to Metadata filter stats. @see stats_macros.h + */ +struct JsonToMetadataStats { + ALL_JSON_TO_METADATA_FILTER_STATS(GENERATE_COUNTER_STRUCT) +}; + +using ProtoRule = envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata::Rule; +using KeyValuePair = + envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata::KeyValuePair; +using ValueType = envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata::ValueType; + +/** + * Data structure to store one rule. + */ +struct Rule { + Rule(const ProtoRule& rule); + const ProtoRule rule_; + std::vector keys_; +}; + +using Rules = std::vector; + +/** + * Configuration for the Json to Metadata filter. + */ +class FilterConfig { +public: + FilterConfig( + const envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata& proto_config, + Stats::Scope& scope); + + JsonToMetadataStats& stats() { return stats_; } + // True if we have rules for requests + bool doRequest() const { return !request_rules_.empty(); } + const Rules& requestRules() const { return request_rules_; } + bool requestContentTypeAllowed(absl::string_view) const; + +private: + using ProtobufRepeatedRule = Protobuf::RepeatedPtrField; + Rules generateRequestRules( + const envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata& proto_config) + const; + absl::flat_hash_set generateRequestAllowContentTypes( + const envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata& proto_config) + const; + JsonToMetadataStats stats_; + const Rules request_rules_; + const absl::flat_hash_set request_allow_content_types_; + const bool request_allow_empty_content_type_; +}; + +const uint32_t MAX_PAYLOAD_VALUE_LEN = 8 * 1024; + +/** + * HTTP Json to Metadata Filter. + */ +class Filter : public Http::PassThroughFilter, Logger::Loggable { +public: + Filter(std::shared_ptr config) : config_(config){}; + + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, + bool end_stream) override; + Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; + Http::FilterTrailersStatus decodeTrailers(Http::RequestTrailerMap&) override; + +private: + using StructMap = absl::flat_hash_map; + // Handle on_missing case of the `rule` and store in `struct_map`. + void handleOnMissing(const Rule& rule, StructMap& struct_map); + // Handle on_present case of the `rule` and store in `struct_map`, which depends on + // the value of `parent_node->key`. + absl::Status handleOnPresent(Json::ObjectSharedPtr parent_node, const std::string& key, + const Rule& rule, StructMap& struct_map); + + // Process the case without body, i.e., on_missing is applied for all rules. + void handleAllOnMissing(const Rules& rules, bool& processing_finished_flag); + // Process the case with error, i.e., on_error is applied for all rules. + void handleAllOnError(const Rules& rules, bool& processing_finished_flag); + // Parse the body while we have the whole json. + void processBody(const Buffer::Instance* body, const Rules& rules, bool& processing_finished_flag, + Stats::Counter& success, Stats::Counter& no_body, Stats::Counter& non_json); + void processRequestBody(); + + const std::string& decideNamespace(const std::string& nspace) const; + bool addMetadata(const std::string& meta_namespace, const std::string& key, + ProtobufWkt::Value val, const bool preserve_existing_metadata_value, + StructMap& struct_map); + void applyKeyValue(const std::string& value, const KeyValuePair& keyval, StructMap& struct_map); + void applyKeyValue(double value, const KeyValuePair& keyval, StructMap& struct_map); + void applyKeyValue(ProtobufWkt::Value value, const KeyValuePair& keyval, StructMap& struct_map); + void finalizeDynamicMetadata(Http::StreamFilterCallbacks& filter_callback, + const StructMap& struct_map, bool& processing_finished_flag); + + std::shared_ptr config_; + bool request_processing_finished_{false}; +}; + +} // namespace JsonToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/common/redis/supported_commands.h b/source/extensions/filters/network/common/redis/supported_commands.h index 3d7d754dfa4d..c6b3d81887e7 100644 --- a/source/extensions/filters/network/common/redis/supported_commands.h +++ b/source/extensions/filters/network/common/redis/supported_commands.h @@ -25,13 +25,14 @@ struct SupportedCommands { "georadius_ro", "georadiusbymember_ro", "get", "getbit", "getrange", "getset", "hdel", "hexists", "hget", "hgetall", "hincrby", "hincrbyfloat", "hkeys", "hlen", "hmget", "hmset", "hscan", "hset", "hsetnx", "hstrlen", "hvals", "incr", "incrby", "incrbyfloat", "lindex", - "linsert", "llen", "lpop", "lpush", "lpushx", "lrange", "lrem", "lset", "ltrim", "persist", - "pexpire", "pexpireat", "pfadd", "pfcount", "psetex", "pttl", "restore", "rpop", "rpush", - "rpushx", "sadd", "scard", "set", "setbit", "setex", "setnx", "setrange", "sismember", - "smembers", "spop", "srandmember", "srem", "sscan", "strlen", "ttl", "type", "zadd", - "zcard", "zcount", "zincrby", "zlexcount", "zpopmin", "zpopmax", "zrange", "zrangebylex", - "zrangebyscore", "zrank", "zrem", "zremrangebylex", "zremrangebyrank", "zremrangebyscore", - "zrevrange", "zrevrangebylex", "zrevrangebyscore", "zrevrank", "zscan", "zscore"); + "linsert", "llen", "lmove", "lpop", "lpush", "lpushx", "lrange", "lrem", "lset", "ltrim", + "persist", "pexpire", "pexpireat", "pfadd", "pfcount", "psetex", "pttl", "restore", "rpop", + "rpush", "rpushx", "sadd", "scard", "set", "setbit", "setex", "setnx", "setrange", + "sismember", "smembers", "spop", "srandmember", "srem", "sscan", "strlen", "ttl", "type", + "zadd", "zcard", "zcount", "zincrby", "zlexcount", "zpopmin", "zpopmax", "zrange", + "zrangebylex", "zrangebyscore", "zrank", "zrem", "zremrangebylex", "zremrangebyrank", + "zremrangebyscore", "zrevrange", "zrevrangebylex", "zrevrangebyscore", "zrevrank", "zscan", + "zscore"); } /** @@ -75,6 +76,11 @@ struct SupportedCommands { */ static const std::string& ping() { CONSTRUCT_ON_FIRST_USE(std::string, "ping"); } + /** + * @return time command + */ + static const std::string& time() { CONSTRUCT_ON_FIRST_USE(std::string, "time"); } + /** * @return quit command */ @@ -87,8 +93,8 @@ struct SupportedCommands { CONSTRUCT_ON_FIRST_USE(absl::flat_hash_set, "append", "bitfield", "decr", "decrby", "del", "discard", "exec", "expire", "expireat", "eval", "evalsha", "geoadd", "hdel", "hincrby", "hincrbyfloat", "hmset", "hset", "hsetnx", - "incr", "incrby", "incrbyfloat", "linsert", "lpop", "lpush", "lpushx", - "lrem", "lset", "ltrim", "mset", "multi", "persist", "pexpire", + "incr", "incrby", "incrbyfloat", "linsert", "lmove", "lpop", "lpush", + "lpushx", "lrem", "lset", "ltrim", "mset", "multi", "persist", "pexpire", "pexpireat", "pfadd", "psetex", "restore", "rpop", "rpush", "rpushx", "sadd", "set", "setbit", "setex", "setnx", "setrange", "spop", "srem", "zadd", "zincrby", "touch", "zpopmin", "zpopmax", "zrem", diff --git a/source/extensions/filters/network/connection_limit/connection_limit.cc b/source/extensions/filters/network/connection_limit/connection_limit.cc index 64298a89cea5..8220c82086e0 100644 --- a/source/extensions/filters/network/connection_limit/connection_limit.cc +++ b/source/extensions/filters/network/connection_limit/connection_limit.cc @@ -80,7 +80,6 @@ Network::FilterStatus Filter::onNewConnection() { absl::optional duration = config_->delay(); if (duration.has_value() && duration.value() > std::chrono::milliseconds(0)) { delay_timer_ = read_callbacks_->connection().dispatcher().createTimer([this]() -> void { - resetTimerState(); read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); }); delay_timer_->enableTimer(duration.value()); diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index c5cfd0802f81..f1af02142f13 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -627,8 +627,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( Http::FilterChainHelper - helper(filter_config_provider_manager_, context_.getServerFactoryContext(), context_, - stats_prefix_); + helper(filter_config_provider_manager_, context_.getServerFactoryContext(), + context_.clusterManager(), context_, stats_prefix_); helper.processFilters(config.http_filters(), "http", "http", filter_factories_); for (const auto& upgrade_config : config.upgrade_configs()) { diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index fd185ee5a3aa..1dc3cced8e60 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -623,6 +623,31 @@ SplitRequestPtr InstanceImpl::makeRequest(Common::Redis::RespValuePtr&& request, return nullptr; } + if (command_name == Common::Redis::SupportedCommands::time()) { + // Respond to TIME locally. + Common::Redis::RespValuePtr time_resp(new Common::Redis::RespValue()); + time_resp->type(Common::Redis::RespType::Array); + std::vector resp_array; + + auto now = dispatcher.timeSource().systemTime().time_since_epoch(); + + Common::Redis::RespValue time_in_secs; + time_in_secs.type(Common::Redis::RespType::BulkString); + time_in_secs.asString() = + std::to_string(std::chrono::duration_cast(now).count()); + resp_array.push_back(time_in_secs); + + Common::Redis::RespValue time_in_micro_secs; + time_in_micro_secs.type(Common::Redis::RespType::BulkString); + time_in_micro_secs.asString() = + std::to_string(std::chrono::duration_cast(now).count()); + resp_array.push_back(time_in_micro_secs); + + time_resp->asArray().swap(resp_array); + callbacks.onResponse(std::move(time_resp)); + return nullptr; + } + if (command_name == Common::Redis::SupportedCommands::quit()) { callbacks.onQuit(); return nullptr; @@ -630,7 +655,7 @@ SplitRequestPtr InstanceImpl::makeRequest(Common::Redis::RespValuePtr&& request, if (request->asArray().size() < 2 && Common::Redis::SupportedCommands::transactionCommands().count(command_name) == 0) { - // Commands other than PING and transaction commands all have at least two arguments. + // Commands other than PING, TIME and transaction commands all have at least two arguments. onInvalidRequest(callbacks); return nullptr; } diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 8c95c5f62e45..e745658cf5af 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -99,7 +99,10 @@ InstanceImpl::ThreadLocalPool::ThreadLocalPool( cluster_update_handle_ = parent->cm_.addThreadLocalClusterUpdateCallbacks(*this); Upstream::ThreadLocalCluster* cluster = parent->cm_.getThreadLocalCluster(cluster_name_); if (cluster != nullptr) { - onClusterAddOrUpdateNonVirtual(*cluster); + Upstream::ThreadLocalClusterCommand command = [&cluster]() -> Upstream::ThreadLocalCluster& { + return *cluster; + }; + onClusterAddOrUpdateNonVirtual(cluster->info()->name(), command); } } @@ -116,8 +119,8 @@ InstanceImpl::ThreadLocalPool::~ThreadLocalPool() { } void InstanceImpl::ThreadLocalPool::onClusterAddOrUpdateNonVirtual( - Upstream::ThreadLocalCluster& cluster) { - if (cluster.info()->name() != cluster_name_) { + absl::string_view cluster_name, Upstream::ThreadLocalClusterCommand& get_cluster) { + if (cluster_name != cluster_name_) { return; } // Ensure the filter is not deleted in the main thread during this method. @@ -132,6 +135,7 @@ void InstanceImpl::ThreadLocalPool::onClusterAddOrUpdateNonVirtual( } ASSERT(cluster_ == nullptr); + auto& cluster = get_cluster(); cluster_ = &cluster; // Update username and password when cluster updates. auth_username_ = ProtocolOptionsConfigImpl::authUsername(cluster_->info(), shared_parent->api_); diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index a7d43c5fcdb7..fe33f44ae5a0 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -159,14 +159,16 @@ class InstanceImpl : public Instance, public std::enable_shared_from_this& hosts_added); void onHostsRemoved(const std::vector& hosts_removed); void drainClients(); // Upstream::ClusterUpdateCallbacks - void onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) override { - onClusterAddOrUpdateNonVirtual(cluster); + void onClusterAddOrUpdate(absl::string_view cluster_name, + Upstream::ThreadLocalClusterCommand& get_cluster) override { + onClusterAddOrUpdateNonVirtual(cluster_name, get_cluster); } void onClusterRemoval(const std::string& cluster_name) override; diff --git a/source/extensions/filters/udp/dns_filter/dns_filter_utils.cc b/source/extensions/filters/udp/dns_filter/dns_filter_utils.cc index 5b8670b9360d..c37f6d77563d 100644 --- a/source/extensions/filters/udp/dns_filter/dns_filter_utils.cc +++ b/source/extensions/filters/udp/dns_filter/dns_filter_utils.cc @@ -52,11 +52,11 @@ std::string buildServiceName(const std::string& name, const std::string& proto, if (name[0] != '_') { result += "_"; } - result += name + "."; + absl::StrAppend(&result, name, "."); if (proto[0] != '_') { result += "_"; } - result += proto + '.' + domain; + absl::StrAppend(&result, proto, ".", domain); return result; } diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc index 903e46fc1ec3..26e12bceba9a 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc @@ -17,7 +17,10 @@ UdpProxyFilter::UdpProxyFilter(Network::UdpReadFilterCallbacks& callbacks, for (const auto& entry : config_->allClusterNames()) { Upstream::ThreadLocalCluster* cluster = config->clusterManager().getThreadLocalCluster(entry); if (cluster != nullptr) { - onClusterAddOrUpdate(*cluster); + Upstream::ThreadLocalClusterCommand command = [&cluster]() -> Upstream::ThreadLocalCluster& { + return *cluster; + }; + onClusterAddOrUpdate(cluster->info()->name(), command); } } @@ -36,9 +39,11 @@ UdpProxyFilter::~UdpProxyFilter() { } } -void UdpProxyFilter::onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) { - auto cluster_name = cluster.info()->name(); +void UdpProxyFilter::onClusterAddOrUpdate(absl::string_view cluster_name, + Upstream::ThreadLocalClusterCommand& get_cluster) { ENVOY_LOG(debug, "udp proxy: attaching to cluster {}", cluster_name); + + auto& cluster = get_cluster(); ASSERT((!cluster_infos_.contains(cluster_name)) || &cluster_infos_[cluster_name]->cluster_ != &cluster); diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h index a31b66c25696..a632e05951d8 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h @@ -394,7 +394,8 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, void fillProxyStreamInfo(); // Upstream::ClusterUpdateCallbacks - void onClusterAddOrUpdate(Upstream::ThreadLocalCluster& cluster) final; + void onClusterAddOrUpdate(absl::string_view cluster_name, + Upstream::ThreadLocalClusterCommand& get_cluster) final; void onClusterRemoval(const std::string& cluster_name) override; const UdpProxyFilterConfigSharedPtr config_; diff --git a/source/extensions/formatter/metadata/metadata.cc b/source/extensions/formatter/metadata/metadata.cc index 1648751b20e9..90a9d89476b8 100644 --- a/source/extensions/formatter/metadata/metadata.cc +++ b/source/extensions/formatter/metadata/metadata.cc @@ -74,7 +74,8 @@ MetadataFormatterCommandParser::parse(const std::string& command, const std::str } // Return a pointer to formatter provider. - return provider->second(filter_namespace, path, max_length); + return std::make_unique( + provider->second(filter_namespace, path, max_length)); } return nullptr; } diff --git a/source/extensions/formatter/metadata/metadata.h b/source/extensions/formatter/metadata/metadata.h index 4ee9453111b2..b6c9a02066fd 100644 --- a/source/extensions/formatter/metadata/metadata.h +++ b/source/extensions/formatter/metadata/metadata.h @@ -5,6 +5,7 @@ #include "envoy/config/typed_config.h" #include "envoy/registry/registry.h" +#include "source/common/formatter/stream_info_formatter.h" #include "source/common/formatter/substitution_formatter.h" namespace Envoy { @@ -22,7 +23,7 @@ class MetadataFormatterCommandParser : public ::Envoy::Formatter::CommandParser private: // Map used to dispatch types of metadata to individual handlers which will // access required metadata object. - using FormatterProviderFunc = std::function<::Envoy::Formatter::FormatterProviderPtr( + using FormatterProviderFunc = std::function<::Envoy::Formatter::StreamInfoFormatterProviderPtr( const std::string& filter_namespace, const std::vector& path, absl::optional max_length)>; std::map metadata_formatter_providers_; diff --git a/source/extensions/health_checkers/grpc/health_checker_impl.h b/source/extensions/health_checkers/grpc/health_checker_impl.h index ad8ff42fb06a..326035eac59b 100644 --- a/source/extensions/health_checkers/grpc/health_checker_impl.h +++ b/source/extensions/health_checkers/grpc/health_checker_impl.h @@ -39,6 +39,8 @@ class GrpcHealthCheckerFactory : public Server::Configuration::CustomHealthCheck } }; +DECLARE_FACTORY(GrpcHealthCheckerFactory); + /** * gRPC health checker implementation. */ diff --git a/source/extensions/health_checkers/http/health_checker_impl.h b/source/extensions/health_checkers/http/health_checker_impl.h index 6230166aee8e..f5e5c2c5a1d7 100644 --- a/source/extensions/health_checkers/http/health_checker_impl.h +++ b/source/extensions/health_checkers/http/health_checker_impl.h @@ -39,6 +39,8 @@ class HttpHealthCheckerFactory : public Server::Configuration::CustomHealthCheck } }; +DECLARE_FACTORY(HttpHealthCheckerFactory); + /** * HTTP health checker implementation. Connection keep alive is used where possible. */ diff --git a/source/extensions/health_checkers/redis/config.h b/source/extensions/health_checkers/redis/config.h index ea8820e87b35..da48cd32d941 100644 --- a/source/extensions/health_checkers/redis/config.h +++ b/source/extensions/health_checkers/redis/config.h @@ -29,6 +29,8 @@ class RedisHealthCheckerFactory : public Server::Configuration::CustomHealthChec } }; +DECLARE_FACTORY(RedisHealthCheckerFactory); + } // namespace RedisHealthChecker } // namespace HealthCheckers } // namespace Extensions diff --git a/source/extensions/health_checkers/tcp/health_checker_impl.h b/source/extensions/health_checkers/tcp/health_checker_impl.h index 4ac85c934270..1787332d6aac 100644 --- a/source/extensions/health_checkers/tcp/health_checker_impl.h +++ b/source/extensions/health_checkers/tcp/health_checker_impl.h @@ -39,6 +39,8 @@ class TcpHealthCheckerFactory : public Server::Configuration::CustomHealthChecke } }; +DECLARE_FACTORY(TcpHealthCheckerFactory); + /** * TCP health checker implementation. */ diff --git a/source/extensions/health_checkers/thrift/config.h b/source/extensions/health_checkers/thrift/config.h index 645ad19a2877..882b51c41ad0 100644 --- a/source/extensions/health_checkers/thrift/config.h +++ b/source/extensions/health_checkers/thrift/config.h @@ -27,6 +27,8 @@ class ThriftHealthCheckerFactory : public Server::Configuration::CustomHealthChe } }; +DECLARE_FACTORY(ThriftHealthCheckerFactory); + } // namespace ThriftHealthChecker } // namespace HealthCheckers } // namespace Extensions diff --git a/source/extensions/http/header_validators/envoy_default/BUILD b/source/extensions/http/header_validators/envoy_default/BUILD index d60c660a3c6b..aa7ce06ead00 100644 --- a/source/extensions/http/header_validators/envoy_default/BUILD +++ b/source/extensions/http/header_validators/envoy_default/BUILD @@ -41,6 +41,7 @@ envoy_cc_library( visibility = [ "//test/common/http/http1:__subpackages__", "//test/extensions/http/header_validators/envoy_default:__subpackages__", + "//test/integration:__subpackages__", ], ) diff --git a/source/extensions/http/header_validators/envoy_default/config_overrides.h b/source/extensions/http/header_validators/envoy_default/config_overrides.h index 8121b32892bf..a3c032955794 100644 --- a/source/extensions/http/header_validators/envoy_default/config_overrides.h +++ b/source/extensions/http/header_validators/envoy_default/config_overrides.h @@ -11,8 +11,23 @@ namespace EnvoyDefault { struct ConfigOverrides { ConfigOverrides() = default; ConfigOverrides(const Envoy::Runtime::Snapshot& snapshot) - : preserve_url_encoded_case_( - snapshot.getBoolean("envoy.uhv.preserve_url_encoded_case", true)) {} + : reject_percent_00_(snapshot.getBoolean("envoy.uhv.reject_percent_00", true)), + preserve_url_encoded_case_( + snapshot.getBoolean("envoy.uhv.preserve_url_encoded_case", true)), + allow_non_compliant_characters_in_path_( + snapshot.getBoolean("envoy.uhv.allow_non_compliant_characters_in_path", true)) {} + + // This flag enables check for the %00 sequence in the URL path. If this sequence is + // found request is rejected as invalid. This check requires path normalization to be + // enabled to occur. + // https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 allows %00 sequence, and + // this check is implemented for backward compatibility with legacy path normalization + // only. + // + // This option currently is `true` by default and can be overridden using the + // "envoy.uhv.reject_percent_00" runtime value. Note that the default value + // will be changed to `false` in the future to make it RFC compliant. + const bool reject_percent_00_{true}; // This flag enables preservation of the case of percent-encoded triplets in URL path for // compatibility with legacy path normalization. @@ -26,6 +41,39 @@ struct ConfigOverrides { // will be changed to `false` in the future to make it easier to write path matchers that // look for percent-encoded triplets. const bool preserve_url_encoded_case_{true}; + + // This flag enables validation of the :path header character set compatible with legacy Envoy + // codecs. When this flag is false header validator checks the URL path in accordance with the + // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 RFC. + // + // This option currently is `true` by default and can be overridden using the + // "envoy.uhv.allow_non_compliant_characters_in_path" runtime value. Note that the default value + // will be changed to `false` in the future to make Envoy behavior standard compliant and + // consistent across all HTTP protocol versions. + // + // In the relaxed mode header validator allows the following additional characters: + // HTTP/1 protocol: " < > [ ] ^ ` { } \ | # + // HTTP/2 and HTTP/3 protocols: all characters allowed for HTTP/1, space, TAB + // HTTP/2 protocol: also allows all extended ASCII (>= 0x80) + // + // NOTE: the " < > [ ] ^ ` { } \ | characters are not explicitly prohibited by the RFC-3986, they + // are just not part of any defined set. # is only allowed as a fragment separator. Extended + // ASCII, space, TAB are prohibited. + // + // In addition when this flag is true AND path normalization is enabled, Envoy will do the + // following: + // 1. all additionally allowed characters with the exception of the [] and \ characters are + // percent encoded in the path segment of the URL only. These characters in query or fragment will + // remain unencoded. + // 2. \ character is translated to / in path segment. + // + // This option provides backward compatibility with the existing (pre header validator) Envoy + // behavior. Envoy's legacy codecs were not compliant with the + // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + // + // With the `envoy.uhv.allow_non_compliant_characters_in_path` set to false the header validator + // rejects requests with characters not allowed by the RFC in the :path header. + const bool allow_non_compliant_characters_in_path_{true}; }; } // namespace EnvoyDefault diff --git a/source/extensions/http/header_validators/envoy_default/header_validator.cc b/source/extensions/http/header_validators/envoy_default/header_validator.cc index 66ebf0f52929..e492dd1ee88a 100644 --- a/source/extensions/http/header_validators/envoy_default/header_validator.cc +++ b/source/extensions/http/header_validators/envoy_default/header_validator.cc @@ -23,26 +23,6 @@ template std::from_chars_result fromChars(const absl::string_view string_value, IntType& value) { return std::from_chars(string_value.data(), string_value.data() + string_value.size(), value); } - -// Same table as the kPathHeaderCharTable but with the backslash character allowed -// This table is used when the "envoy.reloadable_features.uhv_translate_backslash_to_slash" -// runtime keys is set to "true". -constexpr std::array kPathHeaderCharTableWithBackSlashAllowed = { - // control characters - 0b00000000000000000000000000000000, - // !"#$%&'()*+,-./0123456789:;<=>? - 0b01001111111111111111111111110100, - //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ - 0b11111111111111111111111111101001, - //`abcdefghijklmnopqrstuvwxyz{|}~ - 0b01111111111111111111111111100010, - // extended ascii - 0b00000000000000000000000000000000, - 0b00000000000000000000000000000000, - 0b00000000000000000000000000000000, - 0b00000000000000000000000000000000, -}; - } // namespace using ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig; @@ -410,62 +390,123 @@ HeaderValidator::validateHostHeaderRegName(absl::string_view host) { HeaderValidator::HeaderValueValidationResult HeaderValidator::validatePathHeaderCharacters(const HeaderString& value) { + return validatePathHeaderCharacterSet(value, kPathHeaderCharTable, + ::Envoy::Http::kUriQueryAndFragmentCharTable); +} + +HeaderValidator::HeaderValueValidationResult HeaderValidator::validatePathHeaderCharacterSet( + const HeaderString& value, const std::array& allowed_path_chracters, + const std::array& allowed_query_fragment_characters) { + static const HeaderValueValidationResult bad_path_result{ + HeaderValueValidationResult::Action::Reject, UhvResponseCodeDetail::get().InvalidUrl}; const auto& path = value.getStringView(); - bool is_valid = !path.empty(); + if (path.empty()) { + return bad_path_result; + } auto iter = path.begin(); auto end = path.end(); - // When the envoy.reloadable_features.uhv_translate_backslash_to_slash == true - // the validation method needs to allow backslashes in path, so they can translated - // to slashes during path normalization. - const bool allow_backslash = - Runtime::runtimeFeatureEnabled("envoy.reloadable_features.uhv_translate_backslash_to_slash"); - - const std::array& allowed_path_chracters = - allow_backslash ? kPathHeaderCharTableWithBackSlashAllowed : kPathHeaderCharTable; // Validate the path component of the URI - for (; iter != end && is_valid; ++iter) { - const char ch = *iter; - if (ch == '?' || ch == '#') { + for (; iter != end; ++iter) { + if (*iter == '?' || *iter == '#') { // This is the start of the query or fragment portion of the path which uses a different // character table. break; } - is_valid &= testCharInTable(allowed_path_chracters, ch); + if (!testCharInTable(allowed_path_chracters, *iter)) { + return bad_path_result; + } } - if (is_valid && iter != end && *iter == '?') { + if (iter != end && *iter == '?') { // Validate the query component of the URI ++iter; - for (; iter != end && is_valid; ++iter) { - const char ch = *iter; - if (ch == '#') { + for (; iter != end; ++iter) { + if (*iter == '#') { break; } - is_valid &= testCharInTable(::Envoy::Http::kUriQueryAndFragmentCharTable, ch); + if (!testCharInTable(allowed_query_fragment_characters, *iter)) { + return bad_path_result; + } } } - if (is_valid && iter != end && *iter == '#') { + if (iter != end) { + ASSERT(*iter == '#'); if (!config_.strip_fragment_from_path()) { return {HeaderValueValidationResult::Action::Reject, UhvResponseCodeDetail::get().FragmentInUrlPath}; } // Validate the fragment component of the URI ++iter; - for (; iter != end && is_valid; ++iter) { - is_valid &= testCharInTable(::Envoy::Http::kUriQueryAndFragmentCharTable, *iter); + for (; iter != end; ++iter) { + if (!testCharInTable(allowed_query_fragment_characters, *iter)) { + return bad_path_result; + } } } - if (!is_valid) { - return {HeaderValueValidationResult::Action::Reject, UhvResponseCodeDetail::get().InvalidUrl}; + return HeaderValueValidationResult::success(); +} + +void HeaderValidator::encodeAdditionalCharactersInPath( + // TODO(#28780): reuse Utility::PercentEncoding class for this code. + + ::Envoy::Http::RequestHeaderMap& header_map) { + // " < > ^ ` { } | TAB space extended-ASCII + static constexpr std::array kCharactersToEncode = { + // control characters + 0b00000000010000000000000000000000, + // !"#$%&'()*+,-./0123456789:;<=>? + 0b10100000000000000000000000001010, + //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + 0b00000000000000000000000000000010, + //`abcdefghijklmnopqrstuvwxyz{|}~ + 0b10000000000000000000000000011100, + // extended ascii + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + }; + + absl::string_view path = header_map.path(); + // Check if URL path contains any characters in the kCharactersToEncode set + auto char_to_encode = path.begin(); + for (; char_to_encode != path.end() && !testCharInTable(kCharactersToEncode, *char_to_encode); + ++char_to_encode) { + // Return early if we got to query or fragment without finding any characters that has to be + // encoded. + if (*char_to_encode == '?' || *char_to_encode == '#') { + return; + } } + if (char_to_encode == path.end()) { + return; + } + std::string encoded_path(path.begin(), char_to_encode); + encoded_path.reserve(path.size()); - return HeaderValueValidationResult::success(); + for (; char_to_encode != path.end(); ++char_to_encode) { + if (*char_to_encode == '?' || *char_to_encode == '#') { + break; + } + if (testCharInTable(kCharactersToEncode, *char_to_encode)) { + absl::StrAppend(&encoded_path, + fmt::format("%{:02X}", static_cast(*char_to_encode))); + } else { + encoded_path.push_back(*char_to_encode); + } + } + // Append query and fragment if present + encoded_path.append(char_to_encode, path.end()); + // Encoding changes the length of the path + if (encoded_path.size() > path.size()) { + header_map.setPath(encoded_path); + } } bool HeaderValidator::hasChunkedTransferEncoding(const HeaderString& value) { @@ -621,6 +662,44 @@ HeaderValidator::sanitizeEncodedSlashes(::Envoy::Http::RequestHeaderMap& header_ return PathNormalizer::PathNormalizationResult::success(); } +PathNormalizer::PathNormalizationResult +HeaderValidator::transformUrlPath(::Envoy::Http::RequestHeaderMap& header_map) { + if (!config_.uri_path_normalization_options().skip_path_normalization()) { + auto path_result = path_normalizer_.normalizePathUri(header_map); + if (!path_result.ok()) { + return path_result; + } + auto percent_00_result = checkForPercent00InUrlPath(header_map); + if (!percent_00_result.ok()) { + return {PathNormalizer::PathNormalizationResult::Action::Reject, percent_00_result.details()}; + } + if (config_overrides_.allow_non_compliant_characters_in_path_) { + encodeAdditionalCharactersInPath(header_map); + } + } else { + // Path normalization includes sanitization of encoded slashes for performance reasons. + // If normalization is disabled, sanitize encoded slashes here + auto result = sanitizeEncodedSlashes(header_map); + if (!result.ok()) { + return result; + } + } + return PathNormalizer::PathNormalizationResult::success(); +} + +HeaderValidator::HeaderValueValidationResult +HeaderValidator::checkForPercent00InUrlPath(const ::Envoy::Http::RequestHeaderMap& header_map) { + if (!header_map.Path() || !config_overrides_.reject_percent_00_) { + return HeaderValueValidationResult::success(); + } + if (absl::StrContains(header_map.getPathValue(), "%00")) { + return {HeaderValueValidationResult::Action::Reject, + UhvResponseCodeDetail::get().Percent00InPath}; + } + + return HeaderValueValidationResult::success(); +} + } // namespace EnvoyDefault } // namespace HeaderValidators } // namespace Http diff --git a/source/extensions/http/header_validators/envoy_default/header_validator.h b/source/extensions/http/header_validators/envoy_default/header_validator.h index cc6f8c6a8aa3..0f20c611a0db 100644 --- a/source/extensions/http/header_validators/envoy_default/header_validator.h +++ b/source/extensions/http/header_validators/envoy_default/header_validator.h @@ -165,6 +165,23 @@ class HeaderValidator { */ void sanitizeHeadersWithUnderscores(::Envoy::Http::HeaderMap& header_map); + /* + * Validate the :path pseudo header using specific allowed character set. + */ + HeaderValueValidationResult + validatePathHeaderCharacterSet(const ::Envoy::Http::HeaderString& value, + const std::array& allowed_path_chracters, + const std::array& allowed_query_fragment_characters); + + // URL-encode additional characters in URL path. This method is called iff + // `envoy.uhv.allow_non_compliant_characters_in_path` is true. + // Encoded characters: + // + // " < > ^ ` { } | TAB space extended-ASCII + // This method is provided for backward compatibility with Envoy's pre header validator + // behavior. See comments in the HeaderValidatorConfigOverrides declaration above for more + // information. + void encodeAdditionalCharactersInPath(::Envoy::Http::RequestHeaderMap& header_map); /** * Check if the :path header contains a fragment. If the fragment is found it is stripped from * the :path. @@ -177,6 +194,19 @@ class HeaderValidator { PathNormalizer::PathNormalizationResult sanitizeEncodedSlashes(::Envoy::Http::RequestHeaderMap& header_map); + /** + * Transform URL path according to configuration (i.e. apply path normalization). + */ + PathNormalizer::PathNormalizationResult + transformUrlPath(::Envoy::Http::RequestHeaderMap& header_map); + + /** + * Check for presence of %00 sequence based on configuration. + * Reject request if %00 sequence was found. + */ + HeaderValueValidationResult + checkForPercent00InUrlPath(const ::Envoy::Http::RequestHeaderMap& header_map); + const envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig config_; ::Envoy::Http::Protocol protocol_; diff --git a/source/extensions/http/header_validators/envoy_default/http1_header_validator.cc b/source/extensions/http/header_validators/envoy_default/http1_header_validator.cc index 1fcb3652900e..d2b5a57fa43f 100644 --- a/source/extensions/http/header_validators/envoy_default/http1_header_validator.cc +++ b/source/extensions/http/header_validators/envoy_default/http1_header_validator.cc @@ -42,12 +42,67 @@ Http1HeaderValidator::Http1HeaderValidator(const HeaderValidatorConfig& config, {":method", absl::bind_front(&HeaderValidator::validateMethodHeader, this)}, {":authority", absl::bind_front(&HeaderValidator::validateHostHeader, this)}, {":scheme", absl::bind_front(&HeaderValidator::validateSchemeHeader, this)}, - {":path", absl::bind_front(&HeaderValidator::validatePathHeaderCharacters, this)}, + {":path", getPathValidationMethod()}, {"transfer-encoding", absl::bind_front(&Http1HeaderValidator::validateTransferEncodingHeader, this)}, {"content-length", absl::bind_front(&HeaderValidator::validateContentLengthHeader, this)}, } {} +HeaderValidator::HeaderValidatorFunction Http1HeaderValidator::getPathValidationMethod() { + if (config_overrides_.allow_non_compliant_characters_in_path_) { + return absl::bind_front(&Http1HeaderValidator::validatePathHeaderWithAdditionalCharacters, + this); + } + return absl::bind_front(&HeaderValidator::validatePathHeaderCharacters, this); +} + +HeaderValidator::HeaderValueValidationResult +Http1HeaderValidator::validatePathHeaderWithAdditionalCharacters( + const HeaderString& path_header_value) { + ASSERT(config_overrides_.allow_non_compliant_characters_in_path_); + // Same table as the kPathHeaderCharTable but with the following additional character allowed + // " < > [ ] ^ ` { } \ | + // This table is used when the "envoy.uhv.allow_non_compliant_characters_in_path" + // runtime value is set to "true". + static constexpr std::array kPathHeaderCharTableWithAdditionalCharacters = { + // control characters + 0b00000000000000000000000000000000, + // !"#$%&'()*+,-./0123456789:;<=>? + 0b01101111111111111111111111111110, + //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + 0b11111111111111111111111111111111, + //`abcdefghijklmnopqrstuvwxyz{|}~ + 0b11111111111111111111111111111110, + // extended ascii + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + }; + + // Same table as the kUriQueryAndFragmentCharTable but with the following additional character + // allowed " < > [ ] ^ ` { } \ | # This table is used when the + // "envoy.uhv.allow_non_compliant_characters_in_path" runtime value is set to "true". + static constexpr std::array kQueryAndFragmentCharTableWithAdditionalCharacters = { + // control characters + 0b00000000000000000000000000000000, + // !"#$%&'()*+,-./0123456789:;<=>? + 0b01111111111111111111111111111111, + //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + 0b11111111111111111111111111111111, + //`abcdefghijklmnopqrstuvwxyz{|}~ + 0b11111111111111111111111111111110, + // extended ascii + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + }; + return HeaderValidator::validatePathHeaderCharacterSet( + path_header_value, kPathHeaderCharTableWithAdditionalCharacters, + kQueryAndFragmentCharTableWithAdditionalCharacters); +} + HeaderValidator::HeaderEntryValidationResult Http1HeaderValidator::validateRequestHeaderEntry(const HeaderString& key, const HeaderString& value) { @@ -309,18 +364,9 @@ ServerHttp1HeaderValidator::transformRequestHeaders(::Envoy::Http::RequestHeader sanitizeContentLength(header_map); sanitizeHeadersWithUnderscores(header_map); sanitizePathWithFragment(header_map); - if (!config_.uri_path_normalization_options().skip_path_normalization()) { - auto path_result = path_normalizer_.normalizePathUri(header_map); - if (!path_result.ok()) { - return path_result; - } - } else { - // Path normalization includes sanitization of encoded slashes for performance reasons. - // If normalization is disabled, sanitize encoded slashes here - auto result = sanitizeEncodedSlashes(header_map); - if (!result.ok()) { - return result; - } + auto path_result = transformUrlPath(header_map); + if (!path_result.ok()) { + return path_result; } return ::Envoy::Http::ServerHeaderValidator::RequestHeadersTransformationResult::success(); } diff --git a/source/extensions/http/header_validators/envoy_default/http1_header_validator.h b/source/extensions/http/header_validators/envoy_default/http1_header_validator.h index 0f0d0d40d1e6..1ef47977b06e 100644 --- a/source/extensions/http/header_validators/envoy_default/http1_header_validator.h +++ b/source/extensions/http/header_validators/envoy_default/http1_header_validator.h @@ -67,6 +67,21 @@ class Http1HeaderValidator : public HeaderValidator { HeaderEntryValidationResult validateResponseHeaderEntry(const ::Envoy::Http::HeaderString& key, const ::Envoy::Http::HeaderString& value); + // This method validates :path header value using character set that includes characters + // that https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 RFC is ambiguous about: + // + // " < > [ ] ^ ` { } \ | # + // + // This method is called iff `envoy.uhv.allow_non_compliant_characters_in_path` is + // `true`, which is the default value. Note the default will be switched to `false` in the future + // for standard compliance. + HeaderValidator::HeaderValueValidationResult + validatePathHeaderWithAdditionalCharacters(const ::Envoy::Http::HeaderString& path_header_value); + + // Chooses path validation method based on the value of the override flag that affects the + // validation algorithm. + HeaderValidatorFunction getPathValidationMethod(); + const HeaderValidatorMap request_header_validator_map_; }; diff --git a/source/extensions/http/header_validators/envoy_default/http2_header_validator.cc b/source/extensions/http/header_validators/envoy_default/http2_header_validator.cc index 536c443b0528..e67ad1f4e0e4 100644 --- a/source/extensions/http/header_validators/envoy_default/http2_header_validator.cc +++ b/source/extensions/http/header_validators/envoy_default/http2_header_validator.cc @@ -53,13 +53,25 @@ Http2HeaderValidator::Http2HeaderValidator(const HeaderValidatorConfig& config, {":method", absl::bind_front(&HeaderValidator::validateMethodHeader, this)}, {":authority", absl::bind_front(&Http2HeaderValidator::validateAuthorityHeader, this)}, {":scheme", absl::bind_front(&HeaderValidator::validateSchemeHeader, this)}, - {":path", absl::bind_front(&HeaderValidator::validatePathHeaderCharacters, this)}, + {":path", getPathValidationMethod()}, {":protocol", absl::bind_front(&Http2HeaderValidator::validateProtocolHeader, this)}, {"te", absl::bind_front(&Http2HeaderValidator::validateTEHeader, this)}, {"content-length", absl::bind_front(&Http2HeaderValidator::validateContentLengthHeader, this)}, } {} +HeaderValidator::HeaderValidatorFunction Http2HeaderValidator::getPathValidationMethod() { + if (config_overrides_.allow_non_compliant_characters_in_path_) { + if (protocol_ == ::Envoy::Http::Protocol::Http2) { + return absl::bind_front( + &Http2HeaderValidator::validatePathHeaderWithAdditionalCharactersHttp2, this); + } + return absl::bind_front(&Http2HeaderValidator::validatePathHeaderWithAdditionalCharactersHttp3, + this); + } + return absl::bind_front(&HeaderValidator::validatePathHeaderCharacters, this); +} + HeaderValidator::HeaderEntryValidationResult Http2HeaderValidator::validateRequestHeaderEntry(const HeaderString& key, const HeaderString& value) { @@ -98,6 +110,101 @@ Http2HeaderValidator::validateResponseHeaderEntry(const HeaderString& key, return validateGenericHeaderValue(value); } +HeaderValidator::HeaderValueValidationResult +Http2HeaderValidator::validatePathHeaderWithAdditionalCharactersHttp2( + const HeaderString& path_header_value) { + ASSERT(config_overrides_.allow_non_compliant_characters_in_path_); + // Same table as the kPathHeaderCharTable but with the following additional character allowed + // " < > [ ] ^ ` { } \ | SPACE TAB and all extended ASCII + // This table is used when the "envoy.uhv.allow_non_compliant_characters_in_path" + // runtime value is set to "true". + static constexpr std::array kPathHeaderCharTableWithAdditionalCharacters = { + // control characters + 0b00000000010000000000000000000000, + // !"#$%&'()*+,-./0123456789:;<=>? + 0b11101111111111111111111111111110, + //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + 0b11111111111111111111111111111111, + //`abcdefghijklmnopqrstuvwxyz{|}~ + 0b11111111111111111111111111111110, + // extended ascii + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + }; + + // Same table as the kUriQueryAndFragmentCharTable but with the following additional character + // allowed " < > [ ] ^ ` { } \ | # SPACE TAB and all extended ASCII This table is used when the + // "envoy.uhv.allow_non_compliant_characters_in_path" runtime value is set to "true". + static constexpr std::array kQueryAndFragmentCharTableWithAdditionalCharacters = { + // control characters + 0b00000000010000000000000000000000, + // !"#$%&'()*+,-./0123456789:;<=>? + 0b11111111111111111111111111111111, + //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + 0b11111111111111111111111111111111, + //`abcdefghijklmnopqrstuvwxyz{|}~ + 0b11111111111111111111111111111110, + // extended ascii + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + 0b11111111111111111111111111111111, + }; + return HeaderValidator::validatePathHeaderCharacterSet( + path_header_value, kPathHeaderCharTableWithAdditionalCharacters, + kQueryAndFragmentCharTableWithAdditionalCharacters); +} + +HeaderValidator::HeaderValueValidationResult +Http2HeaderValidator::validatePathHeaderWithAdditionalCharactersHttp3( + const HeaderString& path_header_value) { + ASSERT(config_overrides_.allow_non_compliant_characters_in_path_); + // Same table as the kPathHeaderCharTable but with the following additional character allowed + // " < > [ ] ^ ` { } \ | SPACE TAB + // This table is used when the "envoy.uhv.allow_non_compliant_characters_in_path" + // runtime value is set to "true". + static constexpr std::array kPathHeaderCharTableWithAdditionalCharacters = { + // control characters + 0b00000000010000000000000000000000, + // !"#$%&'()*+,-./0123456789:;<=>? + 0b11101111111111111111111111111110, + //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + 0b11111111111111111111111111111111, + //`abcdefghijklmnopqrstuvwxyz{|}~ + 0b11111111111111111111111111111110, + // extended ascii + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + }; + + // Same table as the kUriQueryAndFragmentCharTable but with the following additional character + // allowed " < > [ ] ^ ` { } \ | # SPACE TAB + // This table is used when the "envoy.uhv.allow_non_compliant_characters_in_path" + // runtime value is set to "true". + static constexpr std::array kQueryAndFragmentCharTableWithAdditionalCharacters = { + // control characters + 0b00000000010000000000000000000000, + // !"#$%&'()*+,-./0123456789:;<=>? + 0b11111111111111111111111111111111, + //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + 0b11111111111111111111111111111111, + //`abcdefghijklmnopqrstuvwxyz{|}~ + 0b11111111111111111111111111111110, + // extended ascii + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + 0b00000000000000000000000000000000, + }; + return HeaderValidator::validatePathHeaderCharacterSet( + path_header_value, kPathHeaderCharTableWithAdditionalCharacters, + kQueryAndFragmentCharTableWithAdditionalCharacters); +} + ValidationResult Http2HeaderValidator::validateRequestHeaders(const ::Envoy::Http::RequestHeaderMap& header_map) { static const absl::node_hash_set kAllowedPseudoHeadersForConnect = { @@ -430,18 +537,9 @@ ::Envoy::Http::ServerHeaderValidator::RequestHeadersTransformationResult ServerHttp2HeaderValidator::transformRequestHeaders(::Envoy::Http::RequestHeaderMap& header_map) { sanitizeHeadersWithUnderscores(header_map); sanitizePathWithFragment(header_map); - if (!config_.uri_path_normalization_options().skip_path_normalization()) { - auto path_result = path_normalizer_.normalizePathUri(header_map); - if (!path_result.ok()) { - return path_result; - } - } else { - // Path normalization includes sanitization of encoded slashes for performance reasons. - // If normalization is disabled, sanitize encoded slashes here - auto result = sanitizeEncodedSlashes(header_map); - if (!result.ok()) { - return result; - } + auto path_result = transformUrlPath(header_map); + if (!path_result.ok()) { + return path_result; } // Transform H/2 extended CONNECT to H/1 UPGRADE, so that request processing always observes H/1 diff --git a/source/extensions/http/header_validators/envoy_default/http2_header_validator.h b/source/extensions/http/header_validators/envoy_default/http2_header_validator.h index 69616029dc7e..16664b375940 100644 --- a/source/extensions/http/header_validators/envoy_default/http2_header_validator.h +++ b/source/extensions/http/header_validators/envoy_default/http2_header_validator.h @@ -53,6 +53,30 @@ class Http2HeaderValidator : public HeaderValidator { HeaderEntryValidationResult validateResponseHeaderEntry(const ::Envoy::Http::HeaderString& key, const ::Envoy::Http::HeaderString& value); + // This method validates :path header value for HTTP/2 protocol using character set that includes + // characters either prohibited by https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 RFC + // or where RFC is ambiguous. + // + // " < > [ ] ^ ` { } \ | # SPACE TAB and all extended ASCII + // + // NOTE: RFC-3986 is ambiguous about " < > [ ] ^ ` { } \ | + // # is only allowed as a fragment separator (and not allowed within the fragment) + // SPACE TAB and all extended ASCII are prohibited + // + // This method is called iff `envoy.uhv.allow_non_compliant_characters_in_path` is + // `true`, which is the default value. Note the default will be switched to `false` in the future + // for standard compliance. + HeaderValidator::HeaderValueValidationResult validatePathHeaderWithAdditionalCharactersHttp2( + const ::Envoy::Http::HeaderString& path_header_value); + + // Same method as above but for the HTTP/3 protocol. It does not allow extended ASCII. + HeaderValidator::HeaderValueValidationResult validatePathHeaderWithAdditionalCharactersHttp3( + const ::Envoy::Http::HeaderString& path_header_value); + + // Chooses path validation method based on the value of the override flag that affects the + // validation algorithm. + HeaderValidatorFunction getPathValidationMethod(); + const HeaderValidatorMap request_header_validator_map_; }; diff --git a/source/extensions/http/header_validators/envoy_default/path_normalizer.cc b/source/extensions/http/header_validators/envoy_default/path_normalizer.cc index fba2d0a62933..7674d3808d62 100644 --- a/source/extensions/http/header_validators/envoy_default/path_normalizer.cc +++ b/source/extensions/http/header_validators/envoy_default/path_normalizer.cc @@ -238,8 +238,9 @@ PathNormalizer::normalizePathUri(RequestHeaderMap& header_map) const { redirect |= result.action() == PathNormalizationResult::Action::Redirect; } - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.uhv_translate_backslash_to_slash")) { + // The `envoy.uhv.allow_non_compliant_characters_in_path` flag allows the \ (back slash) + // character, which legacy path normalization was changing to / (forward slash). + if (config_overrides_.allow_non_compliant_characters_in_path_) { translateBackToForwardSlashes(path); } diff --git a/source/extensions/listener_managers/listener_manager/listener_impl.cc b/source/extensions/listener_managers/listener_manager/listener_impl.cc index 51b42225f556..832cb69bfaf4 100644 --- a/source/extensions/listener_managers/listener_manager/listener_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_impl.cc @@ -946,7 +946,14 @@ Init::Manager& PerListenerFactoryContextImpl::initManager() { return listener_im bool ListenerImpl::createNetworkFilterChain( Network::Connection& connection, const Filter::NetworkFilterFactoriesList& filter_factories) { - return Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); + if (!Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories)) { + ENVOY_LOG(debug, "New connection accepted while missing configuration. " + "Close socket and stop the iteration onAccept."); + missing_listener_config_stats_.network_extension_config_missing_.inc(); + return false; + } + + return true; } bool ListenerImpl::createListenerFilterChain(Network::ListenerFilterManager& manager) { diff --git a/source/extensions/listener_managers/listener_manager/listener_impl.h b/source/extensions/listener_managers/listener_manager/listener_impl.h index e19db92d4c15..98a139e3a0f0 100644 --- a/source/extensions/listener_managers/listener_manager/listener_impl.h +++ b/source/extensions/listener_managers/listener_manager/listener_impl.h @@ -34,7 +34,9 @@ constexpr absl::string_view ENABLE_UPDATE_LISTENER_SOCKET_OPTIONS_RUNTIME_FLAG{ /** * All missing listener config stats. @see stats_macros.h */ -#define ALL_MISSING_LISTENER_CONFIG_STATS(COUNTER) COUNTER(extension_config_missing) +#define ALL_MISSING_LISTENER_CONFIG_STATS(COUNTER) \ + COUNTER(extension_config_missing) \ + COUNTER(network_extension_config_missing) /** * Struct definition for all missing listener config stats. @see stats_macros.h diff --git a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc index 53182c097862..5cc5c1b9cd53 100644 --- a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc @@ -94,7 +94,7 @@ Filter::NetworkFilterFactoriesList ProdListenerComponentFactory::createNetworkFi ret.push_back(config_provider_manager.createDynamicFilterConfigProvider( proto_config.config_discovery(), proto_config.name(), filter_chain_factory_context.getServerFactoryContext(), filter_chain_factory_context, - is_terminal, "network", nullptr)); + filter_chain_factory_context.clusterManager(), is_terminal, "network", nullptr)); continue; } @@ -157,8 +157,8 @@ ProdListenerComponentFactory::createListenerFilterFactoryListImpl( } } auto filter_config_provider = config_provider_manager.createDynamicFilterConfigProvider( - config_discovery, name, context.getServerFactoryContext(), context, false, "listener", - createListenerFilterMatcher(proto_config)); + config_discovery, name, context.getServerFactoryContext(), context, + context.clusterManager(), false, "listener", createListenerFilterMatcher(proto_config)); ret.push_back(std::move(filter_config_provider)); } else { ENVOY_LOG(debug, " config: {}", diff --git a/source/extensions/load_balancing_policies/subset/subset_lb.cc b/source/extensions/load_balancing_policies/subset/subset_lb.cc index 8d901df87385..6d1ff4bc09e3 100644 --- a/source/extensions/load_balancing_policies/subset/subset_lb.cc +++ b/source/extensions/load_balancing_policies/subset/subset_lb.cc @@ -828,12 +828,12 @@ void SubsetLoadBalancer::HostSubsetImpl::update(const HostHashSet& matching_host auto excluded_hosts_per_locality = original_host_set_.excludedHostsPerLocality().filter({cached_predicate})[0]; - HostSetImpl::updateHosts(HostSetImpl::updateHostsParams( - hosts, hosts_per_locality, healthy_hosts, healthy_hosts_per_locality, - degraded_hosts, degraded_hosts_per_locality, excluded_hosts, - excluded_hosts_per_locality), - determineLocalityWeights(*hosts_per_locality), hosts_added, - hosts_removed, absl::nullopt, absl::nullopt); + HostSetImpl::updateHosts( + HostSetImpl::updateHostsParams( + hosts, hosts_per_locality, healthy_hosts, healthy_hosts_per_locality, degraded_hosts, + degraded_hosts_per_locality, excluded_hosts, excluded_hosts_per_locality), + determineLocalityWeights(*hosts_per_locality), hosts_added, hosts_removed, + original_host_set_.weightedPriorityHealth(), original_host_set_.overprovisioningFactor()); } LocalityWeightsConstSharedPtr SubsetLoadBalancer::HostSubsetImpl::determineLocalityWeights( diff --git a/source/extensions/load_balancing_policies/subset/subset_lb.h b/source/extensions/load_balancing_policies/subset/subset_lb.h index 664cc902e93b..fe97b0e30bb1 100644 --- a/source/extensions/load_balancing_policies/subset/subset_lb.h +++ b/source/extensions/load_balancing_policies/subset/subset_lb.h @@ -465,9 +465,6 @@ class SubsetLoadBalancer : public LoadBalancer, Logger::Loggable tryFindSelectorFallbackParams(LoadBalancerContext* context); diff --git a/source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.cc b/source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.cc index 92bb75e6756a..24bd9facded8 100644 --- a/source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.cc +++ b/source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.cc @@ -1,5 +1,7 @@ #include "source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.h" +#include + #include "source/common/network/socket_option_impl.h" #include "source/common/quic/envoy_quic_utils.h" @@ -82,5 +84,45 @@ EnvoyDeterministicConnectionIdGeneratorFactory::createCompatibleLinuxBpfSocketOp #endif } +static uint32_t bpfEquivalentFunction(const Buffer::Instance& packet, uint32_t concurrency, + uint32_t default_value) { + // This is a re-implementation of the same algorithm written in BPF in + // createCompatibleLinuxBpfSocketOption + const uint64_t packet_length = packet.length(); + if (packet_length < 9) { + return default_value; + } + + uint8_t first_octet; + packet.copyOut(0, sizeof(first_octet), &first_octet); + + uint32_t connection_id_snippet; + if (first_octet & 0x80) { + // IETF QUIC long header. + // The connection id starts from 7th byte. + // Minimum length of a long header packet is 14. + if (packet_length < 14) { + return default_value; + } + + packet.copyOut(6, sizeof(connection_id_snippet), &connection_id_snippet); + } else { + // IETF QUIC short header, or gQUIC. + // The connection id starts from 2nd byte. + packet.copyOut(1, sizeof(connection_id_snippet), &connection_id_snippet); + } + + connection_id_snippet = htonl(connection_id_snippet); + return connection_id_snippet % concurrency; +} + +QuicConnectionIdWorkerSelector +EnvoyDeterministicConnectionIdGeneratorFactory::getCompatibleConnectionIdWorkerSelector( + uint32_t concurrency) { + return [concurrency](const Buffer::Instance& packet, uint32_t default_value) { + return bpfEquivalentFunction(packet, concurrency, default_value); + }; +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.h b/source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.h index fe122ccabf65..20f7e58fc365 100644 --- a/source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.h +++ b/source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.h @@ -34,6 +34,8 @@ class EnvoyDeterministicConnectionIdGeneratorFactory QuicConnectionIdGeneratorPtr createQuicConnectionIdGenerator(uint32_t worker_index) override; Network::Socket::OptionConstSharedPtr createCompatibleLinuxBpfSocketOption(uint32_t concurrency) override; + QuicConnectionIdWorkerSelector + getCompatibleConnectionIdWorkerSelector(uint32_t concurrency) override; private: #if defined(SO_ATTACH_REUSEPORT_CBPF) && defined(__linux__) diff --git a/source/server/active_udp_listener.cc b/source/server/active_udp_listener.cc index ed5195be4005..e4bb6f1f3886 100644 --- a/source/server/active_udp_listener.cc +++ b/source/server/active_udp_listener.cc @@ -33,19 +33,12 @@ void ActiveUdpListenerBase::post(Network::UdpRecvData&& data) { ASSERT(!udp_listener_->dispatcher().isThreadSafe(), "Shouldn't be posting if thread safe; use onWorkerData() instead."); - // It is not possible to capture a unique_ptr because the post() API copies the lambda, so we must - // bundle the socket inside a shared_ptr that can be captured. - // TODO(mattklein123): It may be possible to change the post() API such that the lambda is only - // moved, but this is non-trivial and needs investigation. - auto data_to_post = std::make_shared(); - *data_to_post = std::move(data); - auto address = listen_socket_.connectionInfoProvider().localAddress(); - udp_listener_->dispatcher().post([data_to_post, tag = config_->listenerTag(), &parent = parent_, - address]() { + udp_listener_->dispatcher().post([data = std::move(data), tag = config_->listenerTag(), + &parent = parent_, address]() mutable { Network::UdpListenerCallbacksOptRef listener = parent.getUdpListenerCallbacks(tag, *address); if (listener.has_value()) { - listener->get().onDataWorker(std::move(*data_to_post)); + listener->get().onDataWorker(std::move(data)); } }); } diff --git a/source/server/admin/html/histograms.js b/source/server/admin/html/histograms.js index f60357999204..9a63d3a17c35 100644 --- a/source/server/admin/html/histograms.js +++ b/source/server/admin/html/histograms.js @@ -139,11 +139,9 @@ function formatRange(lowerBound, width) { * @param {string} bucketPosPercent The bucket position, expressed as a string percentage. * @param {!Object} bucket the bucket record from JSON, augmented with an annotations list * @param {!Element} bucketSpan a span element for the bucket; used to color it yellow. - * @param {boolean} showingCount whether the the count is shown on the histogram itself, - * which dictates whether we will also show it in the popup. * @return {!Function} a function to call when the mouse enters the span. */ - function showPopupFn(detailPopup, bucketPosPercent, bucket, bucketSpan, showingCount) { + function showPopupFn(detailPopup, bucketPosPercent, bucket, bucketSpan) { return (event) => { if (globalState.pendingTimeout) { window.clearTimeout(globalState.pendingTimeout); @@ -156,9 +154,7 @@ function formatRange(lowerBound, width) { detailPopup.replaceChildren(); appendNewElement(detailPopup, 'div').textContent = formatRange(bucket.lower_bound, bucket.width); - if (!showingCount) { - appendNewElement(detailPopup, 'div').textContent = 'count=' + bucket.count; - } + appendNewElement(detailPopup, 'div').textContent = 'count=' + bucket.count; if (bucket.annotations) { for (annotation of bucket.annotations) { const span = appendNewElement(detailPopup, 'div'); @@ -468,6 +464,7 @@ class Painter { this.detailPopup.addEventListener('mouseleave', leavePopup); this.textInterval = Math.ceil(numBuckets / constants.maxBucketsWithText); + this.prevAnnotationVpx = null; } /** @@ -503,7 +500,8 @@ class Painter { // Don't draw textual labels for the percentiles and intervals if there are // more than one: they'll just get garbled. The user can over over the // bucket to see the detail. - if (bucket.annotations.length == 1) { + if (bucket.annotations.length == 1 && + (!this.prevAnnotationVpx || percentileVpx - this.prevAnnotationVpx > this.widthVpx/20)) { const percentilePLabel = appendNewElement(this.annotationsDiv, 'span', 'percentile-label'); percentilePLabel.style.bottom = 0; percentilePLabel.textContent = annotation.toString(); @@ -513,6 +511,7 @@ class Painter { percentileVLabel.style.bottom = '30%'; percentileVLabel.textContent = format(annotation.value); percentileVLabel.style.left = percentilePercent; + this.prevAnnotationVpx = percentileVpx; } } @@ -532,16 +531,21 @@ class Painter { bucketSpan.style.width = this.bucketWidthPercent; bucketSpan.style.left = this.leftPercent; - let showingCount = false; if (++this.textIntervalIndex == this.textInterval) { - showingCount = true; this.textIntervalIndex = 0; this.drawBucketLabels(bucket, heightPercent); } + // Position the popup so it's left edge aligns with the left edge of the + // bucket by default. Adjust it to the left by a bit so it doesn't get + // clipped by the right edge of the viewport. This is heuristic but seems to + // work well for a few test cases. + let popupPos = this.leftVpx; + if (popupPos / this.widthVpx > 0.9) { + popupPos -= this.widthVpx/15; + } bucketSpan.addEventListener('mouseenter', showPopupFn( - this.detailPopup, formatPercent(this.vpxToPosition(this.leftVpx)), bucket, - bucketSpan, showingCount)); + this.detailPopup, formatPercent(this.vpxToPosition(popupPos)), bucket, bucketSpan)); bucketSpan.addEventListener('mouseleave', timeoutFn(this.detailPopup)); diff --git a/source/server/api_listener_impl.cc b/source/server/api_listener_impl.cc index 3a7f4232e397..b22c9d786585 100644 --- a/source/server/api_listener_impl.cc +++ b/source/server/api_listener_impl.cc @@ -48,10 +48,12 @@ HttpApiListener::HttpApiListener(const envoy::config::listener::v3::Listener& co Server::Instance& server, const std::string& name) : ApiListenerImplBase(config, server, name) { if (config.api_listener().api_listener().type_url() == - absl::StrCat("type.googleapis.com/", - envoy::extensions::filters::network::http_connection_manager::v3:: - EnvoyMobileHttpConnectionManager::descriptor() - ->full_name())) { + absl::StrCat( + "type.googleapis.com/", + createReflectableMessage(envoy::extensions::filters::network::http_connection_manager:: + v3::EnvoyMobileHttpConnectionManager::default_instance()) + ->GetDescriptor() + ->full_name())) { auto typed_config = MessageUtil::anyConvertAndValidate< envoy::extensions::filters::network::http_connection_manager::v3:: EnvoyMobileHttpConnectionManager>(config.api_listener().api_listener(), diff --git a/source/server/overload_manager_impl.cc b/source/server/overload_manager_impl.cc index 579e3c677d10..0642f9977613 100644 --- a/source/server/overload_manager_impl.cc +++ b/source/server/overload_manager_impl.cc @@ -565,7 +565,6 @@ LoadShedPoint* OverloadManagerImpl::getLoadShedPoint(absl::string_view point_nam if (auto it = loadshed_points_.find(point_name); it != loadshed_points_.end()) { return it->second.get(); } - ENVOY_LOG(trace, "LoadShedPoint {} is not found. Is it configured?", point_name); return nullptr; } diff --git a/test/common/access_log/access_log_impl_test.cc b/test/common/access_log/access_log_impl_test.cc index 6463872383ac..3587a4776389 100644 --- a/test/common/access_log/access_log_impl_test.cc +++ b/test/common/access_log/access_log_impl_test.cc @@ -1108,9 +1108,9 @@ name: accesslog InstanceSharedPtr log = AccessLogFactory::fromProto(parseAccessLogFromV3Yaml(yaml), context_); - for (const auto& [flag_string, response_flag] : - StreamInfo::ResponseFlagUtils::ALL_RESPONSE_STRING_FLAGS) { - UNREFERENCED_PARAMETER(flag_string); + for (const auto& [flag_strings, response_flag] : + StreamInfo::ResponseFlagUtils::ALL_RESPONSE_STRINGS_FLAGS) { + UNREFERENCED_PARAMETER(flag_strings); TestStreamInfo stream_info(time_source_); stream_info.setResponseFlag(response_flag); diff --git a/test/common/common/log_macros_test.cc b/test/common/common/log_macros_test.cc index cb9e86515ca2..2cdecdb5042e 100644 --- a/test/common/common/log_macros_test.cc +++ b/test/common/common/log_macros_test.cc @@ -213,6 +213,23 @@ class SparseLogMacrosTest : public testing::TestWithParam, ENVOY_LOG_PERIODIC(error, 1s, "foo7 '{}'", evaluations()++); } } + + void logOnceIf(bool condition) { + if (use_misc_macros_) { + ENVOY_LOG_ONCE_MISC_IF(error, condition, "foo8 '{}'", evaluations()++); + } else { + ENVOY_LOG_ONCE_IF(error, condition, "foo8 '{}'", evaluations()++); + } + } + + void logSomethingThriceIf(bool condition) { + if (use_misc_macros_) { + ENVOY_LOG_FIRST_N_MISC_IF(error, 3, condition, "foo9 '{}'", evaluations()++); + } else { + ENVOY_LOG_FIRST_N_IF(error, 3, condition, "foo9 '{}'", evaluations()++); + } + } + std::atomic& evaluations() { MUTABLE_CONSTRUCT_ON_FIRST_USE(std::atomic); }; const bool use_misc_macros_; @@ -271,6 +288,26 @@ TEST_P(SparseLogMacrosTest, All) { // We shouldn't observe additional argument evaluations for log lines below the configured // log level. EXPECT_EQ(29, evaluations()); + + spamCall([this]() { logSomethingThriceIf(false); }, kNumThreads); + // As the condition was false the logs didn't evaluate. + EXPECT_EQ(29, evaluations()); + spamCall([this]() { logOnceIf(false); }, kNumThreads); + // As the condition was false the logs didn't evaluate. + EXPECT_EQ(29, evaluations()); + + spamCall([this]() { logOnceIf(true); }, kNumThreads); + // First call evaluates. + EXPECT_EQ(30, evaluations()); + + // First and last call evaluates as the condition holds. + logSomethingThriceIf(true); + logSomethingThriceIf(false); + logSomethingThriceIf(true); + EXPECT_EQ(32, evaluations()); + spamCall([this]() { logSomethingThriceIf(true); }, kNumThreads); + // Only one remaining log was left. + EXPECT_EQ(33, evaluations()); } TEST(RegistryTest, LoggerWithName) { diff --git a/test/common/common/logger_test.cc b/test/common/common/logger_test.cc index 476ceb136692..ade7a4616066 100644 --- a/test/common/common/logger_test.cc +++ b/test/common/common/logger_test.cc @@ -473,6 +473,10 @@ class ClassForTaggedLog : public Envoy::Logger::Loggable& tags) { @@ -560,6 +564,51 @@ TEST(TaggedLogTest, TestConnLog) { object.logMessageWithConnection(); } +TEST(TaggedLogTest, TestConnEventLog) { + Envoy::Logger::Registry::setLogFormat("%v"); + MockLogSink sink(Envoy::Logger::Registry::getSink()); + EXPECT_CALL(sink, log(_, _)) + .WillOnce(Invoke([](auto msg, auto&) { + // Using the mock connection write a log, skipping it. + EXPECT_THAT(msg, HasSubstr("TestRandomGenerator")); + })) + .WillOnce(Invoke([](auto msg, auto&) { + EXPECT_THAT(msg, HasSubstr("[Tags: \"ConnectionId\":\"200\"] fake message val")); + })); + + EXPECT_CALL(sink, logWithStableName("test_event", "info", "filter", + "[Tags: \"ConnectionId\":\"200\"] fake message val")); + ClassForTaggedLog object; + object.logEventWithConnection(); +} + +TEST(TaggedLogTest, TestConnEventLogWithJsonFormat) { + ProtobufWkt::Struct log_struct; + (*log_struct.mutable_fields())["Level"].set_string_value("%l"); + (*log_struct.mutable_fields())["Message"].set_string_value("%j"); + Envoy::Logger::Registry::setLogLevel(spdlog::level::info); + EXPECT_TRUE(Envoy::Logger::Registry::setJsonLogFormat(log_struct).ok()); + EXPECT_TRUE(Envoy::Logger::Registry::jsonLogFormatSet()); + + MockLogSink sink(Envoy::Logger::Registry::getSink()); + EXPECT_CALL(sink, log(_, _)) + .WillOnce(Invoke([](auto msg, auto&) { + // Using the mock connection write a log, skipping it. + EXPECT_THAT(msg, HasSubstr("TestRandomGenerator")); + })) + .WillOnce(Invoke([](auto msg, auto&) { + EXPECT_NO_THROW(Json::Factory::loadFromString(std::string(msg))); + EXPECT_THAT(msg, HasSubstr("\"Level\":\"info\"")); + EXPECT_THAT(msg, HasSubstr("\"Message\":\"fake message val\"")); + EXPECT_THAT(msg, HasSubstr("\"ConnectionId\":\"200\"")); + })); + + EXPECT_CALL(sink, logWithStableName("test_event", "info", "filter", + "[Tags: \"ConnectionId\":\"200\"] fake message val")); + ClassForTaggedLog object; + object.logEventWithConnection(); +} + TEST(TaggedLogTest, TestTaggedStreamLog) { Envoy::Logger::Registry::setLogFormat("%v"); MockLogSink sink(Envoy::Logger::Registry::getSink()); diff --git a/test/common/config/grpc_subscription_test_harness.h b/test/common/config/grpc_subscription_test_harness.h index e1e41448ef87..6467981a8510 100644 --- a/test/common/config/grpc_subscription_test_harness.h +++ b/test/common/config/grpc_subscription_test_harness.h @@ -59,21 +59,24 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { auto backoff_strategy = std::make_unique( SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/*method_descriptor_, + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/rate_limit_settings_, + /*scope_=*/*stats_store_.rootScope(), + /*config_validators_=*/std::move(config_validators_), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; + if (should_use_unified_) { - mux_ = std::make_shared( - std::unique_ptr(async_client_), dispatcher_, *method_descriptor_, - *stats_store_.rootScope(), rate_limit_settings_, local_info_, true, - std::move(config_validators_), std::move(backoff_strategy), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); + mux_ = std::make_shared(grpc_mux_context, true); } else { - mux_ = std::make_shared( - local_info_, std::unique_ptr(async_client_), dispatcher_, - *method_descriptor_, *stats_store_.rootScope(), rate_limit_settings_, true, - std::move(config_validators_), std::move(backoff_strategy), - /*xds_config_tracker=*/XdsConfigTrackerOptRef(), - /*xds_resources_delegate=*/ - XdsResourcesDelegateOptRef(), - /*target_xds_authority=*/""); + mux_ = std::make_shared(grpc_mux_context, true); } subscription_ = std::make_unique( mux_, callbacks_, resource_decoder_, stats_, Config::TypeUrl::get().ClusterLoadAssignment, diff --git a/test/common/filter/config_discovery_impl_test.cc b/test/common/filter/config_discovery_impl_test.cc index b5993b8ffcd8..ed7db3e69a22 100644 --- a/test/common/filter/config_discovery_impl_test.cc +++ b/test/common/filter/config_discovery_impl_test.cc @@ -191,8 +191,9 @@ class FilterConfigDiscoveryImplTest : public FilterConfigDiscoveryTestBase { } return filter_config_provider_manager_->createDynamicFilterConfigProvider( - config_source, name, server_factory_context_, factory_context_, last_filter_config, - getFilterType(), getMatcher()); + config_source, name, server_factory_context_, factory_context_, + server_factory_context_.cluster_manager_, last_filter_config, getFilterType(), + getMatcher()); } void setup(bool warm = true, bool default_configuration = false, bool last_filter_config = true) { @@ -313,7 +314,7 @@ class NetworkUpstreamFilterConfigDiscoveryImplTest Server::Configuration::NamedUpstreamNetworkFilterConfigFactory, Server::Configuration::MockFactoryContext> { public: - const std::string getFilterType() const override { return "network"; } + const std::string getFilterType() const override { return "upstream_network"; } const std::string getConfigReloadCounter() const override { return "extension_config_discovery.upstream_network_filter.foo.config_reload"; } @@ -576,22 +577,21 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, WrongDefaultConfig) { EXPECT_THROW_WITH_MESSAGE( config_discovery_test.filter_config_provider_manager_->createDynamicFilterConfigProvider( config_source, "foo", config_discovery_test.server_factory_context_, - config_discovery_test.factory_context_, true, config_discovery_test.getFilterType(), - config_discovery_test.getMatcher()), + config_discovery_test.factory_context_, + config_discovery_test.server_factory_context_.cluster_manager_, true, + config_discovery_test.getFilterType(), config_discovery_test.getMatcher()), EnvoyException, "Error: cannot find filter factory foo for default filter " "configuration with type URL " "type.googleapis.com/test.integration.filters.Bogus."); } -// Raise exception when filter is not the last filter in filter chain, but the filter is terminal -// filter. This test does not apply to listener filter. +// For filters which are not listener and upstream network, raise exception when filter is not the +// last filter in filter chain, but the filter is terminal. For listener and upstream network filter +// check that there is no exception raised. TYPED_TEST(FilterConfigDiscoveryImplTestParameter, TerminalFilterInvalid) { InSequence s; TypeParam config_discovery_test; - if (config_discovery_test.getFilterType() == "listener") { - return; - } config_discovery_test.setup(true, false, false); const std::string response_yaml = R"EOF( @@ -607,6 +607,14 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, TerminalFilterInvalid) { const auto decoded_resources = TestUtility::decodeResources(response); EXPECT_CALL(config_discovery_test.init_watcher_, ready()); + + if (config_discovery_test.getFilterType() == "listener" || + config_discovery_test.getFilterType() == "upstream_network") { + EXPECT_NO_THROW(config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, + response.version_info())); + return; + } + EXPECT_THROW_WITH_MESSAGE( config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, response.version_info()), diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 5e0e0996785b..38026594000f 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -9,6 +9,8 @@ #include "source/common/common/logger.h" #include "source/common/common/utility.h" +#include "source/common/formatter/http_specific_formatter.h" +#include "source/common/formatter/stream_info_formatter.h" #include "source/common/formatter/substitution_formatter.h" #include "source/common/http/header_map_impl.h" #include "source/common/json/json_loader.h" @@ -651,6 +653,19 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { ProtoEq(ValueUtil::stringValue("LR"))); } + { + StreamInfoFormatter response_flags_format("RESPONSE_FLAGS_LONG"); + ON_CALL(stream_info, hasResponseFlag(StreamInfo::ResponseFlag::LocalReset)) + .WillByDefault(Return(true)); + EXPECT_EQ("LocalReset", + response_flags_format.format(request_headers, response_headers, response_trailers, + stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_THAT(response_flags_format.formatValue(request_headers, response_headers, + response_trailers, stream_info, body, + AccessLog::AccessLogType::NotSet), + ProtoEq(ValueUtil::stringValue("LocalReset"))); + } + { StreamInfoFormatter upstream_format("UPSTREAM_LOCAL_ADDRESS"); @@ -2379,7 +2394,7 @@ void populateMetadataTestData(envoy::config::core::v3::Metadata& metadata) { (*metadata.mutable_filter_metadata())["com.test"] = struct_obj; } -TEST(SubstitutionFormatterTest, DynamicMetadataFormatter) { +TEST(SubstitutionFormatterTest, DynamicMetadataFieldExtractor) { envoy::config::core::v3::Metadata metadata; populateMetadataTestData(metadata); NiceMock stream_info; @@ -2392,87 +2407,60 @@ TEST(SubstitutionFormatterTest, DynamicMetadataFormatter) { { DynamicMetadataFormatter formatter("com.test", {}, absl::optional()); - std::string val = formatter - .format(request_headers, response_headers, response_trailers, stream_info, - body, AccessLog::AccessLogType::NotSet) - .value(); + std::string val = formatter.format(stream_info).value(); EXPECT_TRUE(val.find("\"test_key\":\"test_value\"") != std::string::npos); EXPECT_TRUE(val.find("\"test_obj\":{\"inner_key\":\"inner_value\"}") != std::string::npos); ProtobufWkt::Value expected_val; expected_val.mutable_struct_value()->CopyFrom(metadata.filter_metadata().at("com.test")); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(expected_val)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(expected_val)); } { DynamicMetadataFormatter formatter("com.test", {"test_key"}, absl::optional()); - EXPECT_EQ("test_value", formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::stringValue("test_value"))); + EXPECT_EQ("test_value", formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::stringValue("test_value"))); } { DynamicMetadataFormatter formatter("com.test", {"test_obj"}, absl::optional()); - EXPECT_EQ("{\"inner_key\":\"inner_value\"}", - formatter.format(request_headers, response_headers, response_trailers, stream_info, - body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("{\"inner_key\":\"inner_value\"}", formatter.format(stream_info)); ProtobufWkt::Value expected_val; (*expected_val.mutable_struct_value()->mutable_fields())["inner_key"] = ValueUtil::stringValue("inner_value"); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(expected_val)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(expected_val)); } { DynamicMetadataFormatter formatter("com.test", {"test_obj", "inner_key"}, absl::optional()); - EXPECT_EQ("inner_value", formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::stringValue("inner_value"))); + EXPECT_EQ("inner_value", formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::stringValue("inner_value"))); } // not found cases { DynamicMetadataFormatter formatter("com.notfound", {}, absl::optional()); - EXPECT_EQ(absl::nullopt, formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } { DynamicMetadataFormatter formatter("com.test", {"notfound"}, absl::optional()); - EXPECT_EQ(absl::nullopt, formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } { DynamicMetadataFormatter formatter("com.test", {"test_obj", "notfound"}, absl::optional()); - EXPECT_EQ(absl::nullopt, formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // size limit { DynamicMetadataFormatter formatter("com.test", {"test_key"}, absl::optional(5)); - EXPECT_EQ("test_", formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("test_", formatter.format(stream_info)); // N.B. Does not truncate. - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::stringValue("test_value"))); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::stringValue("test_value"))); } { @@ -2483,9 +2471,7 @@ TEST(SubstitutionFormatterTest, DynamicMetadataFormatter) { (*metadata.mutable_filter_metadata())["com.test"] = struct_obj; DynamicMetadataFormatter formatter("com.test", {"nan_val"}, absl::optional()); - absl::optional value = - formatter.format(request_headers, response_headers, response_trailers, stream_info, body, - AccessLog::AccessLogType::NotSet); + absl::optional value = formatter.format(stream_info); EXPECT_EQ("google.protobuf.Value cannot encode double values for nan, because it would be " "parsed as a string", value.value()); @@ -2499,9 +2485,7 @@ TEST(SubstitutionFormatterTest, DynamicMetadataFormatter) { (*metadata.mutable_filter_metadata())["com.test"] = struct_obj; DynamicMetadataFormatter formatter("com.test", {"inf_val"}, absl::optional()); - absl::optional value = - formatter.format(request_headers, response_headers, response_trailers, stream_info, body, - AccessLog::AccessLogType::NotSet); + absl::optional value = formatter.format(stream_info); EXPECT_EQ("google.protobuf.Value cannot encode double values for infinity, because it would be " "parsed as a string", value.value()); @@ -2537,101 +2521,75 @@ TEST(SubstitutionFormatterTest, FilterStateFormatter) { { FilterStateFormatter formatter("key", absl::optional(), false); - EXPECT_EQ("\"test_value\"", - formatter.format(request_headers, response_headers, response_trailers, stream_info, - body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::stringValue("test_value"))); + EXPECT_EQ("\"test_value\"", formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::stringValue("test_value"))); } { FilterStateFormatter formatter("key-struct", absl::optional(), false); - EXPECT_EQ("{\"inner_key\":\"inner_value\"}", - formatter.format(request_headers, response_headers, response_trailers, stream_info, - body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("{\"inner_key\":\"inner_value\"}", formatter.format(stream_info)); ProtobufWkt::Value expected; (*expected.mutable_struct_value()->mutable_fields())["inner_key"] = ValueUtil::stringValue("inner_value"); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(expected)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(expected)); } // not found case { FilterStateFormatter formatter("key-not-found", absl::optional(), false); - EXPECT_EQ(absl::nullopt, formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // no serialization case { FilterStateFormatter formatter("key-no-serialization", absl::optional(), false); - EXPECT_EQ(absl::nullopt, formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // serialization error case { FilterStateFormatter formatter("key-serialization-error", absl::optional(), false); - EXPECT_EQ(absl::nullopt, formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // size limit { FilterStateFormatter formatter("key", absl::optional(5), false); - EXPECT_EQ("\"test", formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("\"test", formatter.format(stream_info)); // N.B. Does not truncate. - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::stringValue("test_value"))); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::stringValue("test_value"))); } // serializeAsString case { FilterStateFormatter formatter("test_key", absl::optional(), true); - EXPECT_EQ("test_value By PLAIN", - formatter.format(request_headers, response_headers, response_trailers, stream_info, - body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("test_value By PLAIN", formatter.format(stream_info)); } // size limit for serializeAsString { FilterStateFormatter formatter("test_key", absl::optional(10), true); - EXPECT_EQ("test_value", formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("test_value", formatter.format(stream_info)); } // no serialization case for serializeAsString { FilterStateFormatter formatter("key-no-serialization", absl::optional(), true); - EXPECT_EQ(absl::nullopt, formatter.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(formatter.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, formatter.format(stream_info)); + EXPECT_THAT(formatter.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } } @@ -2646,12 +2604,8 @@ TEST(SubstitutionFormatterTest, DownstreamPeerCertVStartFormatter) { NiceMock stream_info; stream_info.downstream_connection_info_provider_->setSslConnection(nullptr); DownstreamPeerCertVStartFormatter cert_start_formart("DOWNSTREAM_PEER_CERT_V_START(%Y/%m/%d)"); - EXPECT_EQ(absl::nullopt, - cert_start_formart.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_start_formart.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_start_formart.format(stream_info)); + EXPECT_THAT(cert_start_formart.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // No validFromPeerCertificate { @@ -2660,12 +2614,8 @@ TEST(SubstitutionFormatterTest, DownstreamPeerCertVStartFormatter) { auto connection_info = std::make_shared(); EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(absl::nullopt)); stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); - EXPECT_EQ(absl::nullopt, - cert_start_formart.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_start_formart.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_start_formart.format(stream_info)); + EXPECT_THAT(cert_start_formart.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // Default format string { @@ -2676,9 +2626,7 @@ TEST(SubstitutionFormatterTest, DownstreamPeerCertVStartFormatter) { SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(time)); stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); - EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), - cert_start_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), cert_start_format.format(stream_info)); } // Custom format string { @@ -2689,9 +2637,7 @@ TEST(SubstitutionFormatterTest, DownstreamPeerCertVStartFormatter) { SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(time)); stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); - EXPECT_EQ("Mar 28 23:35:58 2018 UTC", - cert_start_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("Mar 28 23:35:58 2018 UTC", cert_start_format.format(stream_info)); } } @@ -2706,12 +2652,8 @@ TEST(SubstitutionFormatterTest, DownstreamPeerCertVEndFormatter) { NiceMock stream_info; stream_info.downstream_connection_info_provider_->setSslConnection(nullptr); DownstreamPeerCertVEndFormatter cert_end_format("%Y/%m/%d"); - EXPECT_EQ(absl::nullopt, - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_end_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_end_format.format(stream_info)); + EXPECT_THAT(cert_end_format.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // No expirationPeerCertificate { @@ -2721,12 +2663,8 @@ TEST(SubstitutionFormatterTest, DownstreamPeerCertVEndFormatter) { EXPECT_CALL(*connection_info, expirationPeerCertificate()) .WillRepeatedly(Return(absl::nullopt)); stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); - EXPECT_EQ(absl::nullopt, - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_end_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_end_format.format(stream_info)); + EXPECT_THAT(cert_end_format.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // Default format string { @@ -2737,9 +2675,7 @@ TEST(SubstitutionFormatterTest, DownstreamPeerCertVEndFormatter) { SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(time)); stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); - EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), cert_end_format.format(stream_info)); } // Custom format string { @@ -2750,9 +2686,7 @@ TEST(SubstitutionFormatterTest, DownstreamPeerCertVEndFormatter) { SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(time)); stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); - EXPECT_EQ("Mar 28 23:35:58 2018 UTC", - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("Mar 28 23:35:58 2018 UTC", cert_end_format.format(stream_info)); } } @@ -2767,24 +2701,16 @@ TEST(SubstitutionFormatterTest, UpstreamPeerCertVStartFormatter) { NiceMock stream_info; EXPECT_CALL(stream_info, upstreamInfo()).WillRepeatedly(Return(nullptr)); UpstreamPeerCertVStartFormatter cert_start_format("%Y/%m/%d"); - EXPECT_EQ(absl::nullopt, - cert_start_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_start_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_start_format.format(stream_info)); + EXPECT_THAT(cert_start_format.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // No upstreamSslConnection { NiceMock stream_info; stream_info.upstreamInfo()->setUpstreamSslConnection(nullptr); DownstreamPeerCertVStartFormatter cert_start_format("UPSTREAM_PEER_CERT_V_START(%Y/%m/%d)"); - EXPECT_EQ(absl::nullopt, - cert_start_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_start_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_start_format.format(stream_info)); + EXPECT_THAT(cert_start_format.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // No validFromPeerCertificate { @@ -2793,12 +2719,8 @@ TEST(SubstitutionFormatterTest, UpstreamPeerCertVStartFormatter) { auto connection_info = std::make_shared(); EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(absl::nullopt)); stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); - EXPECT_EQ(absl::nullopt, - cert_start_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_start_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_start_format.format(stream_info)); + EXPECT_THAT(cert_start_format.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // Default format string { @@ -2809,9 +2731,7 @@ TEST(SubstitutionFormatterTest, UpstreamPeerCertVStartFormatter) { SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(time)); stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); - EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), - cert_start_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), cert_start_format.format(stream_info)); } // Custom format string { @@ -2822,9 +2742,7 @@ TEST(SubstitutionFormatterTest, UpstreamPeerCertVStartFormatter) { SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(time)); stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); - EXPECT_EQ("Mar 28 23:35:58 2018 UTC", - cert_start_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("Mar 28 23:35:58 2018 UTC", cert_start_format.format(stream_info)); } } @@ -2839,24 +2757,16 @@ TEST(SubstitutionFormatterTest, UpstreamPeerCertVEndFormatter) { NiceMock stream_info; EXPECT_CALL(stream_info, upstreamInfo()).WillRepeatedly(Return(nullptr)); UpstreamPeerCertVEndFormatter cert_end_format("%Y/%m/%d"); - EXPECT_EQ(absl::nullopt, - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_end_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_end_format.format(stream_info)); + EXPECT_THAT(cert_end_format.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // No upstreamSslConnection { NiceMock stream_info; stream_info.upstreamInfo()->setUpstreamSslConnection(nullptr); UpstreamPeerCertVEndFormatter cert_end_format("%Y/%m/%d"); - EXPECT_EQ(absl::nullopt, - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_end_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_end_format.format(stream_info)); + EXPECT_THAT(cert_end_format.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // No expirationPeerCertificate { @@ -2866,12 +2776,8 @@ TEST(SubstitutionFormatterTest, UpstreamPeerCertVEndFormatter) { EXPECT_CALL(*connection_info, expirationPeerCertificate()) .WillRepeatedly(Return(absl::nullopt)); stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); - EXPECT_EQ(absl::nullopt, - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(cert_end_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), - ProtoEq(ValueUtil::nullValue())); + EXPECT_EQ(absl::nullopt, cert_end_format.format(stream_info)); + EXPECT_THAT(cert_end_format.formatValue(stream_info), ProtoEq(ValueUtil::nullValue())); } // Default format string { @@ -2882,9 +2788,7 @@ TEST(SubstitutionFormatterTest, UpstreamPeerCertVEndFormatter) { SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(time)); stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); - EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), cert_end_format.format(stream_info)); } // Custom format string { @@ -2895,9 +2799,7 @@ TEST(SubstitutionFormatterTest, UpstreamPeerCertVEndFormatter) { SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(time)); stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); - EXPECT_EQ("Mar 28 23:35:58 2018 UTC", - cert_end_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); + EXPECT_EQ("Mar 28 23:35:58 2018 UTC", cert_end_format.format(stream_info)); } } @@ -2913,11 +2815,8 @@ TEST(SubstitutionFormatterTest, StartTimeFormatter) { time_t test_epoch = 1522280158; SystemTime time = std::chrono::system_clock::from_time_t(test_epoch); EXPECT_CALL(stream_info, startTime()).WillRepeatedly(Return(time)); - EXPECT_EQ("2018/03/28", - start_time_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(start_time_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), + EXPECT_EQ("2018/03/28", start_time_format.format(stream_info)); + EXPECT_THAT(start_time_format.formatValue(stream_info), ProtoEq(ValueUtil::stringValue("2018/03/28"))); } @@ -2925,11 +2824,8 @@ TEST(SubstitutionFormatterTest, StartTimeFormatter) { StartTimeFormatter start_time_format(""); SystemTime time; EXPECT_CALL(stream_info, startTime()).WillRepeatedly(Return(time)); - EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), - start_time_format.format(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet)); - EXPECT_THAT(start_time_format.formatValue(request_headers, response_headers, response_trailers, - stream_info, body, AccessLog::AccessLogType::NotSet), + EXPECT_EQ(AccessLogDateTimeFormatter::fromTime(time), start_time_format.format(stream_info)); + EXPECT_THAT(start_time_format.formatValue(stream_info), ProtoEq(ValueUtil::stringValue(AccessLogDateTimeFormatter::fromTime(time)))); } } diff --git a/test/common/grpc/grpc_client_integration.h b/test/common/grpc/grpc_client_integration.h index 84abeda51cf3..70993af3d200 100644 --- a/test/common/grpc/grpc_client_integration.h +++ b/test/common/grpc/grpc_client_integration.h @@ -121,6 +121,26 @@ class DeltaSotwIntegrationParamTest SotwOrDelta sotwOrDelta() const { return std::get<2>(GetParam()); } }; +class DeltaSotwDeferredClustersIntegrationParamTest + : public BaseGrpcClientIntegrationParamTest, + public testing::TestWithParam< + std::tuple> { +public: + ~DeltaSotwDeferredClustersIntegrationParamTest() override = default; + static std::string protocolTestParamsToString( + const ::testing::TestParamInfo< + std::tuple>& p) { + return fmt::format("{}_{}_{}_{}", TestUtility::ipVersionToString(std::get<0>(p.param)), + std::get<1>(p.param) == ClientType::GoogleGrpc ? "GoogleGrpc" : "EnvoyGrpc", + std::get<2>(p.param) == SotwOrDelta::Delta ? "Delta" : "StateOfTheWorld", + std::get<3>(p.param) == true ? "DeferredClusters" : ""); + } + Network::Address::IpVersion ipVersion() const override { return std::get<0>(GetParam()); } + ClientType clientType() const override { return std::get<1>(GetParam()); } + SotwOrDelta sotwOrDelta() const { return std::get<2>(GetParam()); } + bool useDeferredCluster() const { return std::get<3>(GetParam()); } +}; + // Skip tests based on gRPC client type. #define SKIP_IF_GRPC_CLIENT(client_type) \ if (clientType() == (client_type)) { \ @@ -142,6 +162,11 @@ class DeltaSotwIntegrationParamTest testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest()), \ testing::Values(Grpc::SotwOrDelta::Sotw, Grpc::SotwOrDelta::Delta)) +#define DELTA_SOTW_GRPC_CLIENT_DEFERRED_CLUSTERS_INTEGRATION_PARAMS \ + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ + testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest()), \ + testing::Values(Grpc::SotwOrDelta::Sotw, Grpc::SotwOrDelta::Delta), \ + testing::Values(true, false)) #define UNIFIED_LEGACY_GRPC_CLIENT_INTEGRATION_PARAMS \ testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ testing::ValuesIn(TestEnvironment::getsGrpcVersionsForTest()), \ diff --git a/test/common/grpc/grpc_client_integration_test.cc b/test/common/grpc/grpc_client_integration_test.cc index 3fd32c96d8fc..b65121808489 100644 --- a/test/common/grpc/grpc_client_integration_test.cc +++ b/test/common/grpc/grpc_client_integration_test.cc @@ -29,6 +29,44 @@ TEST_P(GrpcClientIntegrationTest, BasicStream) { dispatcher_helper_.runDispatcher(); } +// Validate that a simple request-reply stream works with bytes metering in Envoy gRPC. +TEST_P(GrpcClientIntegrationTest, BasicStreamWithBytesMeter) { + // Currently, only Envoy gRPC's bytes metering is based on the bytes meter in Envoy. + // Therefore, skip the test for google gRPC. + SKIP_IF_GRPC_CLIENT(ClientType::GoogleGrpc); + // The check in this test is based on HTTP2 codec logic (i.e., including H2_FRAME_HEADER_SIZE). + // Skip this test if default protocol of this integration test is no longer HTTP2. + if (fake_upstream_config_.upstream_protocol_ != Http::CodecType::HTTP2) + return; + initialize(); + auto stream = createStream(empty_metadata_); + + // Create the send request. + helloworld::HelloRequest request_msg; + request_msg.set_name(HELLO_REQUEST); + + RequestArgs request_args; + request_args.request = &request_msg; + stream->sendRequest(request_args); + stream->sendServerInitialMetadata(empty_metadata_); + + auto send_buf = Common::serializeMessage(request_msg); + Common::prependGrpcFrameHeader(*send_buf); + + auto upstream_meter = stream->grpc_stream_->streamInfo().getUpstreamBytesMeter(); + uint64_t total_bytes_sent = upstream_meter->wireBytesSent(); + uint64_t header_bytes_sent = upstream_meter->headerBytesSent(); + // Verify the number of sent bytes that is tracked in stream info equals to the length of + // request buffer. + // Note, in HTTP2 codec, H2_FRAME_HEADER_SIZE is always included in bytes meter so we need to + // account for it in the check here as well. + EXPECT_EQ(total_bytes_sent - header_bytes_sent, + send_buf->length() + Http::Http2::H2_FRAME_HEADER_SIZE); + + stream->sendReply(/*check_response_size=*/true); + stream->sendServerTrailers(Status::WellKnownGrpcStatus::Ok, "", empty_metadata_); +} + // Validate that a client destruction with open streams cleans up appropriately. TEST_P(GrpcClientIntegrationTest, ClientDestruct) { initialize(); @@ -66,6 +104,53 @@ TEST_P(GrpcClientIntegrationTest, MultiStream) { dispatcher_helper_.runDispatcher(); } +// Validate that multiple streams work with bytes metering in Envoy gRPC. +TEST_P(GrpcClientIntegrationTest, MultiStreamWithBytesMeter) { + SKIP_IF_GRPC_CLIENT(ClientType::GoogleGrpc); + // The check in this test is based on HTTP2 codec logic (i.e., including H2_FRAME_HEADER_SIZE). + // Skip this test if default protocol of this integration test is no longer HTTP2. + if (fake_upstream_config_.upstream_protocol_ != Http::CodecType::HTTP2) + return; + initialize(); + auto stream_0 = createStream(empty_metadata_); + auto stream_1 = createStream(empty_metadata_); + // Create the send request. + helloworld::HelloRequest request_msg; + request_msg.set_name(HELLO_REQUEST); + + RequestArgs request_args; + request_args.request = &request_msg; + stream_0->sendRequest(request_args); + stream_1->sendRequest(request_args); + + auto send_buf = Common::serializeMessage(request_msg); + Common::prependGrpcFrameHeader(*send_buf); + + // Access stream info to make sure it is present. + envoy::config::core::v3::Metadata m; + (*m.mutable_filter_metadata())["com.foo.bar"] = {}; + EXPECT_THAT(stream_0->grpc_stream_.streamInfo().dynamicMetadata(), ProtoEq(m)); + EXPECT_THAT(stream_1->grpc_stream_.streamInfo().dynamicMetadata(), ProtoEq(m)); + + auto upstream_meter_0 = stream_0->grpc_stream_->streamInfo().getUpstreamBytesMeter(); + uint64_t total_bytes_sent = upstream_meter_0->wireBytesSent(); + uint64_t header_bytes_sent = upstream_meter_0->headerBytesSent(); + EXPECT_EQ(total_bytes_sent - header_bytes_sent, + send_buf->length() + Http::Http2::H2_FRAME_HEADER_SIZE); + + auto upstream_meter_1 = stream_1->grpc_stream_->streamInfo().getUpstreamBytesMeter(); + uint64_t total_bytes_sent_1 = upstream_meter_1->wireBytesSent(); + uint64_t header_bytes_sent_1 = upstream_meter_1->headerBytesSent(); + EXPECT_EQ(total_bytes_sent_1 - header_bytes_sent_1, + send_buf->length() + Http::Http2::H2_FRAME_HEADER_SIZE); + + stream_0->sendServerInitialMetadata(empty_metadata_); + stream_0->sendReply(true); + stream_1->sendServerTrailers(Status::WellKnownGrpcStatus::Unavailable, "", empty_metadata_, true); + stream_0->sendServerTrailers(Status::WellKnownGrpcStatus::Ok, "", empty_metadata_); + dispatcher_helper_.runDispatcher(); +} + // Validate that multiple request-reply unary RPCs works. TEST_P(GrpcClientIntegrationTest, MultiRequest) { initialize(); @@ -357,7 +442,10 @@ TEST_P(GrpcClientIntegrationTest, MaximumKnownPlusOne) { TEST_P(GrpcClientIntegrationTest, ReceiveAfterLocalClose) { initialize(); auto stream = createStream(empty_metadata_); - stream->sendRequest(true); + + RequestArgs request_args; + request_args.end_stream = true; + stream->sendRequest(request_args); stream->sendServerInitialMetadata(empty_metadata_); stream->sendReply(); stream->sendServerTrailers(Status::WellKnownGrpcStatus::Ok, "", empty_metadata_); diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index a30067cb190a..8b5a9c444227 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -105,21 +105,32 @@ class DispatcherHelper { Event::Dispatcher& dispatcher_; }; +struct RequestArgs { + helloworld::HelloRequest* request = nullptr; + bool end_stream = false; +}; + // Stream related test utilities. class HelloworldStream : public MockAsyncStreamCallbacks { public: HelloworldStream(DispatcherHelper& dispatcher_helper) : dispatcher_helper_(dispatcher_helper) {} - void sendRequest(bool end_stream = false) { + void sendRequest(RequestArgs request_args = {}) { helloworld::HelloRequest request_msg; request_msg.set_name(HELLO_REQUEST); - grpc_stream_->sendMessage(request_msg, end_stream); + // Update the request pointer to local request message when it is not set at caller site (i.e., + // nullptr). + if (request_args.request == nullptr) { + request_args.request = &request_msg; + } + + grpc_stream_->sendMessage(*request_args.request, request_args.end_stream); helloworld::HelloRequest received_msg; AssertionResult result = fake_stream_->waitForGrpcMessage(dispatcher_helper_.dispatcher_, received_msg); RELEASE_ASSERT(result, result.message()); - EXPECT_THAT(request_msg, ProtoEq(received_msg)); + EXPECT_THAT(*request_args.request, ProtoEq(received_msg)); } void expectInitialMetadata(const TestMetadata& metadata) { @@ -156,10 +167,29 @@ class HelloworldStream : public MockAsyncStreamCallbacks fake_stream_->encodeHeaders(Http::TestResponseHeaderMapImpl(*reply_headers), false); } - void sendReply() { + // `check_response_size` only could be set to true in Envoy gRPC because bytes metering is only + // implemented in Envoy gPRC mode. + void sendReply(bool check_response_size = false) { helloworld::HelloReply reply; reply.set_message(HELLO_REPLY); - EXPECT_CALL(*this, onReceiveMessage_(HelloworldReplyEq(HELLO_REPLY))).WillExitIfNeeded(); + EXPECT_CALL(*this, onReceiveMessage_(HelloworldReplyEq(HELLO_REPLY))) + .WillOnce(Invoke([this, check_response_size](const helloworld::HelloReply& reply_msg) { + if (check_response_size) { + auto recv_buf = Common::serializeMessage(reply_msg); + // gRPC message in Envoy gRPC mode is prepended with a frame header. + Common::prependGrpcFrameHeader(*recv_buf); + // Verify that the number of received byte that is tracked in the stream info equals to + // the length of reply response buffer. + auto upstream_meter = this->grpc_stream_->streamInfo().getUpstreamBytesMeter(); + uint64_t total_bytes_rev = upstream_meter->wireBytesReceived(); + uint64_t header_bytes_rev = upstream_meter->headerBytesReceived(); + // In HTTP2 codec, H2_FRAME_HEADER_SIZE is always included in bytes meter so we need to + // account for it in the check here as well. + EXPECT_EQ(total_bytes_rev - header_bytes_rev, + recv_buf->length() + Http::Http2::H2_FRAME_HEADER_SIZE); + } + dispatcher_helper_.exitDispatcherIfNeeded(); + })); dispatcher_helper_.setStreamEventPending(); fake_stream_->sendGrpcMessage(reply); } @@ -250,9 +280,8 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { virtual void initialize() { if (fake_upstream_ == nullptr) { - FakeUpstreamConfig config(test_time_.timeSystem()); - config.upstream_protocol_ = Http::CodecType::HTTP2; - fake_upstream_ = std::make_unique(0, ipVersion(), config); + fake_upstream_config_.upstream_protocol_ = Http::CodecType::HTTP2; + fake_upstream_ = std::make_unique(0, ipVersion(), fake_upstream_config_); } switch (clientType()) { case ClientType::EnvoyGrpc: @@ -448,6 +477,7 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { } DangerousDeprecatedTestTime test_time_; + FakeUpstreamConfig fake_upstream_config_{test_time_.timeSystem()}; std::unique_ptr fake_upstream_; FakeHttpConnectionPtr fake_connection_; std::vector fake_streams_; diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 036012a751cd..a90ffa9c0ce9 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -265,7 +265,6 @@ envoy_cc_test( "conn_manager_impl_test.cc", "conn_manager_impl_test_2.cc", ], - shard_count = 3, deps = [ ":conn_manager_impl_test_base_lib", ":custom_header_extension_lib", diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index af4ae2b45092..db8c95e4070a 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -1870,20 +1870,20 @@ TEST_F(AsyncClientImplTest, DumpState) { // Must not be in anonymous namespace for friend to work. class AsyncClientImplUnitTest : public AsyncClientImplTest { public: - std::unique_ptr route_impl_{new AsyncStreamImpl::RouteImpl( - client_, absl::nullopt, + std::unique_ptr route_impl_{new NullRouteImpl( + client_.cluster_->name(), client_.singleton_manager_, absl::nullopt, Protobuf::RepeatedPtrField(), absl::nullopt)}; - AsyncStreamImpl::NullVirtualHost vhost_; - AsyncStreamImpl::NullCommonConfig config_; + NullVirtualHost vhost_; + NullCommonConfig config_; void setupRouteImpl(const std::string& yaml_config) { envoy::config::route::v3::RetryPolicy retry_policy; TestUtility::loadFromYaml(yaml_config, retry_policy); - route_impl_ = std::make_unique( - client_, absl::nullopt, + route_impl_ = std::make_unique( + client_.cluster_->name(), client_.singleton_manager_, absl::nullopt, Protobuf::RepeatedPtrField(), std::move(retry_policy)); } diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 657848ba176b..23e995dd1ac1 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -525,12 +525,6 @@ TEST_F(HttpConnectionManagerImplTest, InvalidPathWithDualFilter) { // Invalid paths are rejected with 400. TEST_F(HttpConnectionManagerImplTest, PathFailedtoSanitize) { -#ifdef ENVOY_ENABLE_UHV - // TODO(#26642): Enable this test after UHV rejects requests with the %00 sequence in the URL path - // in compatibility mode - delete codec_; - return; -#endif setup(false, ""); // Enable path sanitizer normalize_path_ = true; @@ -565,8 +559,9 @@ TEST_F(HttpConnectionManagerImplTest, PathFailedtoSanitize) { EXPECT_CALL(response_encoder_, encodeHeaders(_, true)) .WillOnce(Invoke([&](const ResponseHeaderMap& headers, bool) -> void { EXPECT_EQ("400", headers.getStatusValue()); - EXPECT_EQ("path_normalization_failed", - filter->decoder_callbacks_->streamInfo().responseCodeDetails().value()); + // Error details are different in UHV and legacy modes + EXPECT_THAT(filter->decoder_callbacks_->streamInfo().responseCodeDetails().value(), + HasSubstr("path")); })); EXPECT_CALL(*filter, onStreamComplete()); EXPECT_CALL(*filter, onDestroy()); @@ -2398,15 +2393,17 @@ TEST_F(HttpConnectionManagerImplTest, TestAccessLogWithInvalidRequest) { })); EXPECT_CALL(*handler, log(_, _, _, _, _)) - .WillOnce(Invoke([](const HeaderMap*, const HeaderMap*, const HeaderMap*, - const StreamInfo::StreamInfo& stream_info, AccessLog::AccessLogType) { + .WillOnce(Invoke([this](const HeaderMap*, const HeaderMap*, const HeaderMap*, + const StreamInfo::StreamInfo& stream_info, AccessLog::AccessLogType) { EXPECT_TRUE(stream_info.responseCode()); EXPECT_EQ(stream_info.responseCode().value(), uint32_t(400)); EXPECT_EQ("missing_host_header", stream_info.responseCodeDetails().value()); EXPECT_NE(nullptr, stream_info.downstreamAddressProvider().localAddress()); EXPECT_NE(nullptr, stream_info.downstreamAddressProvider().remoteAddress()); EXPECT_NE(nullptr, stream_info.downstreamAddressProvider().directRemoteAddress()); - EXPECT_EQ(nullptr, stream_info.route()); + // Even the request is invalid, will still try to find a route before response filter chain + // path. + EXPECT_EQ(route_config_provider_.route_config_->route_, stream_info.route()); })); EXPECT_CALL(*codec_, dispatch(_)) diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index da97a8587f9b..c99236baf35e 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -4822,5 +4822,41 @@ TEST_P(Http1ServerConnectionImplTest, Char22InHeaderValue) { EXPECT_EQ(status.message(), "http/1.1 protocol error: header value contains invalid chars"); } +TEST_P(Http1ClientConnectionImplTest, MultipleContentLength) { + initialize(); + + NiceMock response_decoder; + Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); + TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); + + Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\n" + "Content-Length: 3\r\n" + "Content-Length: 3\r\n" + "\r\n" + "foo\r\n\r\n"); + auto status = codec_->dispatch(response); + EXPECT_FALSE(status.ok()); + EXPECT_EQ(status.message(), "http/1.1 protocol error: HPE_UNEXPECTED_CONTENT_LENGTH"); +} + +TEST_P(Http1ServerConnectionImplTest, MultipleContentLength) { + initialize(); + + StrictMock decoder; + EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); + EXPECT_CALL(decoder, + sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, "http1.codec_error")); + + Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\n" + "Content-Length: 3\r\n" + "Content-Length: 3\r\n" + "\r\n" + "foo\r\n\r\n"); + auto status = codec_->dispatch(buffer); + EXPECT_FALSE(status.ok()); + EXPECT_EQ(status.message(), "http/1.1 protocol error: HPE_UNEXPECTED_CONTENT_LENGTH"); +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/http2/BUILD b/test/common/http/http2/BUILD index 2f07020e559a..35f902f8a194 100644 --- a/test/common/http/http2/BUILD +++ b/test/common/http/http2/BUILD @@ -13,6 +13,7 @@ envoy_package() envoy_cc_test( name = "codec_impl_test", + size = "large", srcs = ["codec_impl_test.cc"], external_deps = [ "quiche_http2_adapter", diff --git a/test/common/io/BUILD b/test/common/io/BUILD index d1231c0cb139..cb990fe222b4 100644 --- a/test/common/io/BUILD +++ b/test/common/io/BUILD @@ -17,6 +17,7 @@ envoy_cc_test( ], deps = [ "//source/common/io:io_uring_impl_lib", + "//source/common/network:address_lib", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", diff --git a/test/common/io/io_uring_impl_test.cc b/test/common/io/io_uring_impl_test.cc index 92fba458a7d4..cf3775b95459 100644 --- a/test/common/io/io_uring_impl_test.cc +++ b/test/common/io/io_uring_impl_test.cc @@ -1,4 +1,5 @@ #include "source/common/io/io_uring_impl.h" +#include "source/common/network/address_impl.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" @@ -10,14 +11,21 @@ namespace Envoy { namespace Io { namespace { +class TestRequest : public Request { +public: + TestRequest(int& data) : data_(data) {} + ~TestRequest() { data_ = -1; } + + int& data_; +}; + class IoUringImplTest : public ::testing::Test { public: - IoUringImplTest() : api_(Api::createApiForTest()) { - if (isIoUringSupported()) { + IoUringImplTest() : api_(Api::createApiForTest()), should_skip_(!isIoUringSupported()) { + if (!should_skip_) { factory_ = std::make_unique(2, false, context_.threadLocal()); factory_->onServerInitialized(); - } else { - should_skip_ = true; + io_uring_ = factory_->getOrCreate(); } } @@ -32,16 +40,16 @@ class IoUringImplTest : public ::testing::Test { return; } - auto& uring = factory_->getOrCreate(); - if (uring.isEventfdRegistered()) { - uring.unregisterEventfd(); + if (io_uring_->isEventfdRegistered()) { + io_uring_->unregisterEventfd(); } } Api::ApiPtr api_; testing::NiceMock context_; std::unique_ptr factory_{}; - bool should_skip_{}; + OptRef io_uring_{}; + const bool should_skip_{}; }; class IoUringImplParamTest @@ -74,15 +82,13 @@ TEST_P(IoUringImplParamTest, InvalidParams) { SET_SOCKET_INVALID(fd); auto dispatcher = api_->allocateDispatcher("test_thread"); - auto& uring = factory_->getOrCreate(); - - os_fd_t event_fd = uring.registerEventfd(); + os_fd_t event_fd = io_uring_->registerEventfd(); const Event::FileTriggerType trigger = Event::PlatformDefaultTriggerType; int32_t completions_nr = 0; auto file_event = dispatcher->createFileEvent( event_fd, - [&uring, &completions_nr](uint32_t) { - uring.forEveryCompletion([&completions_nr](void*, int32_t res) { + [this, &completions_nr](uint32_t) { + io_uring_->forEveryCompletion([&completions_nr](Request*, int32_t res, bool) { EXPECT_TRUE(res < 0); completions_nr++; }); @@ -90,36 +96,176 @@ TEST_P(IoUringImplParamTest, InvalidParams) { trigger, Event::FileReadyType::Read); auto prepare_method = GetParam(); - IoUringResult res = prepare_method(uring, fd); + IoUringResult res = prepare_method(*io_uring_, fd); EXPECT_EQ(res, IoUringResult::Ok); - res = prepare_method(uring, fd); + res = prepare_method(*io_uring_, fd); EXPECT_EQ(res, IoUringResult::Ok); - res = prepare_method(uring, fd); + res = prepare_method(*io_uring_, fd); EXPECT_EQ(res, IoUringResult::Failed); - res = uring.submit(); + res = io_uring_->submit(); EXPECT_EQ(res, IoUringResult::Ok); - res = uring.submit(); + res = io_uring_->submit(); EXPECT_EQ(res, IoUringResult::Ok); dispatcher->run(Event::Dispatcher::RunType::NonBlock); EXPECT_EQ(completions_nr, 2); } -TEST_F(IoUringImplTest, Instantiate) { - auto& uring1 = factory_->getOrCreate(); - auto& uring2 = factory_->getOrCreate(); - EXPECT_EQ(&uring1, &uring2); +TEST_F(IoUringImplTest, InjectCompletion) { + auto dispatcher = api_->allocateDispatcher("test_thread"); + + os_fd_t fd = 11; + os_fd_t event_fd = io_uring_->registerEventfd(); + const Event::FileTriggerType trigger = Event::PlatformDefaultTriggerType; + int32_t completions_nr = 0; + int data1 = 1; + TestRequest request(data1); + + auto file_event = dispatcher->createFileEvent( + event_fd, + [this, &completions_nr](uint32_t) { + io_uring_->forEveryCompletion( + [&completions_nr](Request* user_data, int32_t res, bool injected) { + EXPECT_TRUE(injected); + EXPECT_EQ(1, dynamic_cast(user_data)->data_); + EXPECT_EQ(-11, res); + completions_nr++; + }); + }, + trigger, Event::FileReadyType::Read); + + io_uring_->injectCompletion(fd, &request, -11); + + file_event->activate(Event::FileReadyType::Read); + + dispatcher->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_EQ(completions_nr, 1); +} + +TEST_F(IoUringImplTest, NestInjectCompletion) { + auto dispatcher = api_->allocateDispatcher("test_thread"); + + os_fd_t fd = 11; + os_fd_t fd2 = 11; + os_fd_t event_fd = io_uring_->registerEventfd(); + const Event::FileTriggerType trigger = Event::PlatformDefaultTriggerType; + int32_t completions_nr = 0; + int data1 = 1; + TestRequest request(data1); + int data2 = 2; + TestRequest request2(data2); + + auto file_event = dispatcher->createFileEvent( + event_fd, + [this, &fd2, &completions_nr, &request2](uint32_t) { + io_uring_->forEveryCompletion([this, &fd2, &completions_nr, + &request2](Request* user_data, int32_t res, bool injected) { + EXPECT_TRUE(injected); + if (completions_nr == 0) { + EXPECT_EQ(1, dynamic_cast(user_data)->data_); + EXPECT_EQ(-11, res); + io_uring_->injectCompletion(fd2, &request2, -22); + } else { + EXPECT_EQ(2, dynamic_cast(user_data)->data_); + EXPECT_EQ(-22, res); + } + + completions_nr++; + }); + }, + trigger, Event::FileReadyType::Read); + + io_uring_->injectCompletion(fd, &request, -11); + + file_event->activate(Event::FileReadyType::Read); + + dispatcher->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_EQ(completions_nr, 2); +} + +TEST_F(IoUringImplTest, RemoveInjectCompletion) { + auto dispatcher = api_->allocateDispatcher("test_thread"); + + os_fd_t fd = 11; + os_fd_t fd2 = 22; + os_fd_t event_fd = io_uring_->registerEventfd(); + const Event::FileTriggerType trigger = Event::PlatformDefaultTriggerType; + int32_t completions_nr = 0; + int data1 = 1; + TestRequest request(data1); + int data2 = 2; + TestRequest* request2 = new TestRequest(data2); + + auto file_event = dispatcher->createFileEvent( + event_fd, + [this, &completions_nr](uint32_t) { + io_uring_->forEveryCompletion( + [&completions_nr](Request* user_data, int32_t res, bool injected) { + EXPECT_TRUE(injected); + EXPECT_EQ(1, dynamic_cast(user_data)->data_); + EXPECT_EQ(-11, res); + completions_nr++; + }); + }, + trigger, Event::FileReadyType::Read); + + io_uring_->injectCompletion(fd, &request, -11); + io_uring_->injectCompletion(fd2, request2, -22); + io_uring_->removeInjectedCompletion(fd2); + EXPECT_EQ(-1, data2); + file_event->activate(Event::FileReadyType::Read); + + dispatcher->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_EQ(completions_nr, 1); +} + +TEST_F(IoUringImplTest, NestRemoveInjectCompletion) { + auto dispatcher = api_->allocateDispatcher("test_thread"); + + os_fd_t fd = 11; + os_fd_t fd2 = 22; + os_fd_t event_fd = io_uring_->registerEventfd(); + const Event::FileTriggerType trigger = Event::PlatformDefaultTriggerType; + int32_t completions_nr = 0; + int data1 = 1; + TestRequest request(data1); + int data2 = 2; + TestRequest* request2 = new TestRequest(data2); + + auto file_event = dispatcher->createFileEvent( + event_fd, + [this, &fd2, &completions_nr, &data2](uint32_t) { + io_uring_->forEveryCompletion( + [this, &fd2, &completions_nr, &data2](Request* user_data, int32_t res, bool injected) { + EXPECT_TRUE(injected); + if (completions_nr == 0) { + EXPECT_EQ(1, dynamic_cast(user_data)->data_); + EXPECT_EQ(-11, res); + } else { + io_uring_->removeInjectedCompletion(fd2); + EXPECT_EQ(-1, data2); + } + completions_nr++; + }); + }, + trigger, Event::FileReadyType::Read); + + io_uring_->injectCompletion(fd, &request, -11); + io_uring_->injectCompletion(fd2, request2, -22); + + file_event->activate(Event::FileReadyType::Read); + + dispatcher->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_EQ(completions_nr, 2); } TEST_F(IoUringImplTest, RegisterEventfd) { - auto& uring = factory_->getOrCreate(); - - EXPECT_FALSE(uring.isEventfdRegistered()); - uring.registerEventfd(); - EXPECT_TRUE(uring.isEventfdRegistered()); - uring.unregisterEventfd(); - EXPECT_FALSE(uring.isEventfdRegistered()); - EXPECT_DEATH(uring.unregisterEventfd(), "unable to unregister eventfd"); + EXPECT_FALSE(io_uring_->isEventfdRegistered()); + io_uring_->registerEventfd(); + EXPECT_TRUE(io_uring_->isEventfdRegistered()); + io_uring_->unregisterEventfd(); + EXPECT_FALSE(io_uring_->isEventfdRegistered()); + EXPECT_DEATH(io_uring_->unregisterEventfd(), ""); } TEST_F(IoUringImplTest, PrepareReadvAllDataFitsOneChunk) { @@ -135,15 +281,14 @@ TEST_F(IoUringImplTest, PrepareReadvAllDataFitsOneChunk) { iov.iov_base = buffer; iov.iov_len = 4096; - auto& uring = factory_->getOrCreate(); - os_fd_t event_fd = uring.registerEventfd(); + os_fd_t event_fd = io_uring_->registerEventfd(); const Event::FileTriggerType trigger = Event::PlatformDefaultTriggerType; int32_t completions_nr = 0; auto file_event = dispatcher->createFileEvent( event_fd, - [&uring, &completions_nr, d = dispatcher.get()](uint32_t) { - uring.forEveryCompletion([&completions_nr](void*, int32_t res) { + [this, &completions_nr, d = dispatcher.get()](uint32_t) { + io_uring_->forEveryCompletion([&completions_nr](Request*, int32_t res, bool) { completions_nr++; EXPECT_EQ(res, strlen("test text")); }); @@ -151,9 +296,9 @@ TEST_F(IoUringImplTest, PrepareReadvAllDataFitsOneChunk) { }, trigger, Event::FileReadyType::Read); - uring.prepareReadv(fd, &iov, 1, 0, nullptr); + io_uring_->prepareReadv(fd, &iov, 1, 0, nullptr); EXPECT_STREQ(static_cast(iov.iov_base), ""); - uring.submit(); + io_uring_->submit(); dispatcher->run(Event::Dispatcher::RunType::Block); @@ -184,34 +329,38 @@ TEST_F(IoUringImplTest, PrepareReadvQueueOverflow) { iov3.iov_base = buffer3; iov3.iov_len = 2; - auto& uring = factory_->getOrCreate(); - - os_fd_t event_fd = uring.registerEventfd(); + os_fd_t event_fd = io_uring_->registerEventfd(); const Event::FileTriggerType trigger = Event::PlatformDefaultTriggerType; int32_t completions_nr = 0; auto file_event = dispatcher->createFileEvent( event_fd, - [&uring, &completions_nr](uint32_t) { - uring.forEveryCompletion([&completions_nr](void* user_data, int32_t res) { + [this, &completions_nr](uint32_t) { + io_uring_->forEveryCompletion([&completions_nr](Request* user_data, int32_t res, bool) { EXPECT_TRUE(user_data != nullptr); EXPECT_EQ(res, 2); completions_nr++; // Note: generally events are not guaranteed to complete in the same order // we submit them, but for this case of reading from a single file it's ok // to expect the same order. - EXPECT_EQ(reinterpret_cast(user_data), completions_nr); + EXPECT_EQ(dynamic_cast(user_data)->data_, completions_nr); }); }, trigger, Event::FileReadyType::Read); - IoUringResult res = uring.prepareReadv(fd, &iov1, 1, 0, reinterpret_cast(1)); + int data1 = 1; + TestRequest request1(data1); + IoUringResult res = io_uring_->prepareReadv(fd, &iov1, 1, 0, &request1); EXPECT_EQ(res, IoUringResult::Ok); - res = uring.prepareReadv(fd, &iov2, 1, 2, reinterpret_cast(2)); + int data2 = 2; + TestRequest request2(data2); + res = io_uring_->prepareReadv(fd, &iov2, 1, 2, &request2); EXPECT_EQ(res, IoUringResult::Ok); - res = uring.prepareReadv(fd, &iov3, 1, 4, reinterpret_cast(3)); + int data3 = 3; + TestRequest request3(data3); + res = io_uring_->prepareReadv(fd, &iov3, 1, 4, &request3); // Expect the submission queue overflow. EXPECT_EQ(res, IoUringResult::Failed); - res = uring.submit(); + res = io_uring_->submit(); EXPECT_EQ(res, IoUringResult::Ok); // Even though we haven't been notified about ops completion the buffers @@ -228,9 +377,9 @@ TEST_F(IoUringImplTest, PrepareReadvQueueOverflow) { EXPECT_EQ(completions_nr, 2); // Check a new event gets handled in the next dispatcher run. - res = uring.prepareReadv(fd, &iov3, 1, 4, reinterpret_cast(3)); + res = io_uring_->prepareReadv(fd, &iov3, 1, 4, &request3); EXPECT_EQ(res, IoUringResult::Ok); - res = uring.submit(); + res = io_uring_->submit(); EXPECT_EQ(res, IoUringResult::Ok); EXPECT_EQ(static_cast(iov3.iov_base)[0], 'e'); diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index b69a7054dc5e..0ed33236b0e7 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -1091,6 +1091,22 @@ type_url: type.googleapis.com/envoy.unknown.Message EXPECT_TRUE(TestUtility::protoEqual(expected, actual)); } +TYPED_TEST(TypedStructUtilityTest, RedactTypedStructWithErrorContent) { + envoy::test::Sensitive actual; + TestUtility::loadFromYaml(R"EOF( +insensitive_typed_struct: + type_url: type.googleapis.com/envoy.test.Sensitive + value: + # The target field is string but value here is int. + insensitive_string: 123 + # The target field is int but value here is string. + insensitive_int: "abc" +)EOF", + actual); + + EXPECT_NO_THROW(MessageUtil::redact(actual)); +} + TYPED_TEST(TypedStructUtilityTest, RedactEmptyTypeUrlTypedStruct) { TypeParam actual; TypeParam expected = actual; diff --git a/test/common/quic/active_quic_listener_test.cc b/test/common/quic/active_quic_listener_test.cc index df74245cb862..6501c4c6a7b2 100644 --- a/test/common/quic/active_quic_listener_test.cc +++ b/test/common/quic/active_quic_listener_test.cc @@ -56,6 +56,10 @@ class TestActiveQuicListener : public ActiveQuicListener { } }; +uint32_t testWorkerSelector(const Buffer::Instance&, uint32_t default_value) { + return default_value; +} + class TestActiveQuicListenerFactory : public ActiveQuicListenerFactory { public: using ActiveQuicListenerFactory::ActiveQuicListenerFactory; @@ -75,7 +79,7 @@ class TestActiveQuicListenerFactory : public ActiveQuicListenerFactory { runtime, worker_index, concurrency, dispatcher, parent, std::move(listen_socket), listener_config, quic_config, kernel_worker_routing, enabled, quic_stat_names, packets_to_read_to_connection_count_ratio, crypto_server_stream_factory, - proof_source_factory, std::move(cid_generator)); + proof_source_factory, std::move(cid_generator), testWorkerSelector); } }; diff --git a/test/common/quic/client_connection_factory_impl_test.cc b/test/common/quic/client_connection_factory_impl_test.cc index f93fcce3482f..e8d8f0e6e1ac 100644 --- a/test/common/quic/client_connection_factory_impl_test.cc +++ b/test/common/quic/client_connection_factory_impl_test.cc @@ -32,6 +32,8 @@ class QuicNetworkConnectionTest : public Event::TestUsingSimulatedTime, auto* protocol_options = cluster_->http3_options_.mutable_quic_protocol_options(); protocol_options->mutable_max_concurrent_streams()->set_value(43); protocol_options->mutable_initial_stream_window_size()->set_value(65555); + protocol_options->set_connection_options("5RTO,ACKD"); + protocol_options->set_client_connection_options("6RTO,AKD4"); quic_info_ = createPersistentQuicInfoForCluster(dispatcher_, *cluster_); EXPECT_EQ(quic_info_->quic_config_.max_time_before_crypto_handshake(), quic::QuicTime::Delta::FromSeconds(10)); @@ -41,6 +43,21 @@ class QuicNetworkConnectionTest : public Event::TestUsingSimulatedTime, protocol_options->max_concurrent_streams().value()); EXPECT_EQ(quic_info_->quic_config_.GetInitialMaxStreamDataBytesIncomingBidirectionalToSend(), protocol_options->initial_stream_window_size().value()); + EXPECT_EQ(2, quic_info_->quic_config_.SendConnectionOptions().size()); + std::string quic_copts = ""; + for (auto& copt : quic_info_->quic_config_.SendConnectionOptions()) { + quic_copts.append(quic::QuicTagToString(copt)); + } + EXPECT_EQ(quic_copts, "5RTOACKD"); + EXPECT_EQ( + 2, quic_info_->quic_config_.ClientRequestedIndependentOptions(quic::Perspective::IS_CLIENT) + .size()); + std::string quic_ccopts = ""; + for (auto& ccopt : + quic_info_->quic_config_.ClientRequestedIndependentOptions(quic::Perspective::IS_CLIENT)) { + quic_ccopts.append(quic::QuicTagToString(ccopt)); + } + EXPECT_EQ(quic_ccopts, "6RTOAKD4"); test_address_ = Network::Utility::resolveUrl( absl::StrCat("tcp://", Network::Test::getLoopbackAddressUrlString(GetParam()), ":30")); diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index 9d2fa1762d34..7fc4a9aa7d8a 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -313,6 +313,7 @@ TEST_F(EnvoyQuicServerSessionTest, NewStream) { headers.OnHeader(":authority", host); headers.OnHeader(":method", "GET"); headers.OnHeader(":path", "/"); + headers.OnHeader(":scheme", "https"); headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); // Request headers should be propagated to decoder. EXPECT_CALL(request_decoder, decodeHeaders_(_, /*end_stream=*/true)) @@ -548,6 +549,7 @@ TEST_F(EnvoyQuicServerSessionTest, WriteUpdatesDelayCloseTimer) { request_headers.OnHeader(":authority", host); request_headers.OnHeader(":method", "GET"); request_headers.OnHeader(":path", "/"); + request_headers.OnHeader(":scheme", "https"); request_headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); // Request headers should be propagated to decoder. EXPECT_CALL(request_decoder, decodeHeaders_(_, /*end_stream=*/true)) @@ -647,6 +649,7 @@ TEST_F(EnvoyQuicServerSessionTest, FlushCloseNoTimeout) { std::string host("www.abc.com"); request_headers.OnHeader(":authority", host); request_headers.OnHeader(":method", "GET"); + request_headers.OnHeader(":scheme", "https"); request_headers.OnHeader(":path", "/"); request_headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); // Request headers should be propagated to decoder. @@ -897,6 +900,7 @@ TEST_F(EnvoyQuicServerSessionTest, SendBufferWatermark) { request_headers.OnHeader(":authority", host); request_headers.OnHeader(":method", "GET"); request_headers.OnHeader(":path", "/"); + request_headers.OnHeader(":scheme", "https"); request_headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); // Request headers should be propagated to decoder. EXPECT_CALL(request_decoder, decodeHeaders_(_, /*end_stream=*/true)) diff --git a/test/common/quic/envoy_quic_server_stream_test.cc b/test/common/quic/envoy_quic_server_stream_test.cc index d56e63ad0f76..5cdb1d9bb3a8 100644 --- a/test/common/quic/envoy_quic_server_stream_test.cc +++ b/test/common/quic/envoy_quic_server_stream_test.cc @@ -264,6 +264,7 @@ TEST_F(EnvoyQuicServerStreamTest, GetRequestAndResponse) { spdy_headers[":authority"] = host_; spdy_headers[":method"] = "GET"; spdy_headers[":path"] = "/"; + spdy_headers[":scheme"] = "https"; spdy_headers.AppendValueOrAddHeader("cookie", "a=b"); spdy_headers.AppendValueOrAddHeader("cookie", "c=d"); std::string payload = spdyHeaderToHttp3StreamPayload(spdy_headers); diff --git a/test/common/quic/envoy_quic_utils_test.cc b/test/common/quic/envoy_quic_utils_test.cc index c7f57b64f12a..de24bbec1de6 100644 --- a/test/common/quic/envoy_quic_utils_test.cc +++ b/test/common/quic/envoy_quic_utils_test.cc @@ -189,15 +189,31 @@ TEST(EnvoyQuicUtilsTest, ConvertQuicConfig) { EXPECT_EQ(100, quic_config.GetMaxUnidirectionalStreamsToSend()); EXPECT_EQ(16777216, quic_config.GetInitialMaxStreamDataBytesIncomingBidirectionalToSend()); EXPECT_EQ(25165824, quic_config.GetInitialSessionFlowControlWindowToSend()); + EXPECT_TRUE(quic_config.SendConnectionOptions().empty()); + EXPECT_TRUE(quic_config.ClientRequestedIndependentOptions(quic::Perspective::IS_CLIENT).empty()); // Test converting values. config.mutable_max_concurrent_streams()->set_value(2); config.mutable_initial_stream_window_size()->set_value(3); config.mutable_initial_connection_window_size()->set_value(50); + config.set_connection_options("5RTO,ACKD"); + config.set_client_connection_options("6RTO,AKD4"); convertQuicConfig(config, quic_config); EXPECT_EQ(2, quic_config.GetMaxBidirectionalStreamsToSend()); EXPECT_EQ(2, quic_config.GetMaxUnidirectionalStreamsToSend()); EXPECT_EQ(3, quic_config.GetInitialMaxStreamDataBytesIncomingBidirectionalToSend()); + EXPECT_EQ(2, quic_config.SendConnectionOptions().size()); + EXPECT_EQ(2, quic_config.ClientRequestedIndependentOptions(quic::Perspective::IS_CLIENT).size()); + std::string quic_copts = ""; + for (auto& copt : quic_config.SendConnectionOptions()) { + quic_copts.append(quic::QuicTagToString(copt)); + } + EXPECT_EQ(quic_copts, "5RTOACKD"); + std::string quic_ccopts = ""; + for (auto& ccopt : quic_config.ClientRequestedIndependentOptions(quic::Perspective::IS_CLIENT)) { + quic_ccopts.append(quic::QuicTagToString(ccopt)); + } + EXPECT_EQ(quic_ccopts, "6RTOAKD4"); } TEST(EnvoyQuicUtilsTest, HeaderMapMaxSizeLimit) { diff --git a/test/common/stream_info/utility_test.cc b/test/common/stream_info/utility_test.cc index 95ff568f1990..ed7f6747b4f2 100644 --- a/test/common/stream_info/utility_test.cc +++ b/test/common/stream_info/utility_test.cc @@ -22,10 +22,11 @@ namespace { using envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager; TEST(ResponseFlagUtilsTest, toShortStringConversion) { - for (const auto& [flag_string, flag_enum] : ResponseFlagUtils::ALL_RESPONSE_STRING_FLAGS) { + for (const auto& [flag_strings, flag_enum] : ResponseFlagUtils::ALL_RESPONSE_STRINGS_FLAGS) { NiceMock stream_info; ON_CALL(stream_info, hasResponseFlag(flag_enum)).WillByDefault(Return(true)); - EXPECT_EQ(flag_string, ResponseFlagUtils::toShortString(stream_info)); + EXPECT_EQ(flag_strings.short_string_, ResponseFlagUtils::toShortString(stream_info)); + EXPECT_EQ(flag_strings.long_string_, ResponseFlagUtils::toString(stream_info)); } // No flag is set. @@ -33,6 +34,7 @@ TEST(ResponseFlagUtilsTest, toShortStringConversion) { NiceMock stream_info; ON_CALL(stream_info, hasResponseFlag(_)).WillByDefault(Return(false)); EXPECT_EQ("-", ResponseFlagUtils::toShortString(stream_info)); + EXPECT_EQ("-", ResponseFlagUtils::toString(stream_info)); } // Test combinations. @@ -44,14 +46,17 @@ TEST(ResponseFlagUtilsTest, toShortStringConversion) { ON_CALL(stream_info, hasResponseFlag(ResponseFlag::UpstreamRequestTimeout)) .WillByDefault(Return(true)); EXPECT_EQ("UT,DI,FI", ResponseFlagUtils::toShortString(stream_info)); + EXPECT_EQ("UpstreamRequestTimeout,DelayInjected,FaultInjected", + ResponseFlagUtils::toString(stream_info)); } } TEST(ResponseFlagsUtilsTest, toResponseFlagConversion) { EXPECT_FALSE(ResponseFlagUtils::toResponseFlag("NonExistentFlag").has_value()); - for (const auto& [flag_string, flag_enum] : ResponseFlagUtils::ALL_RESPONSE_STRING_FLAGS) { - absl::optional response_flag = ResponseFlagUtils::toResponseFlag(flag_string); + for (const auto& [flag_strings, flag_enum] : ResponseFlagUtils::ALL_RESPONSE_STRINGS_FLAGS) { + absl::optional response_flag = + ResponseFlagUtils::toResponseFlag(flag_strings.short_string_); EXPECT_TRUE(response_flag.has_value()); EXPECT_EQ(flag_enum, response_flag.value()); } diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index b9dd58f3dcab..af3bfca7260a 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -58,6 +58,28 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "deferred_cluster_initialization_test", + srcs = ["deferred_cluster_initialization_test.cc"], + external_deps = [ + "abseil_base", + ], + deps = [ + ":test_cluster_manager", + "//envoy/upstream:cluster_manager_interface", + "//source/extensions/clusters/eds:eds_lib", + "//source/extensions/clusters/static:static_cluster_lib", + "//test/mocks/config:config_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", + "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", + ], +) + envoy_cc_test( name = "cluster_manager_impl_test", size = "large", @@ -254,6 +276,7 @@ envoy_cc_test( "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) diff --git a/test/common/upstream/cluster_discovery_manager_test.cc b/test/common/upstream/cluster_discovery_manager_test.cc index e1a4175687e1..c88bb75de319 100644 --- a/test/common/upstream/cluster_discovery_manager_test.cc +++ b/test/common/upstream/cluster_discovery_manager_test.cc @@ -36,7 +36,8 @@ class TestClusterLifecycleCallbackHandler : public ClusterLifecycleCallbackHandl void invokeClusterAdded(ThreadLocalCluster& cluster) { for (auto& cb : update_callbacks_) { - cb->onClusterAddOrUpdate(cluster); + ThreadLocalClusterCommand command = [&cluster]() -> ThreadLocalCluster& { return cluster; }; + cb->onClusterAddOrUpdate(cluster.info()->name(), command); } } diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 322668d9ebbb..b6de4c485a27 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -91,7 +91,7 @@ class ClusterManagerImplTest : public testing::Test { router_context_(factory_.stats_.symbolTable()), registered_dns_factory_(dns_resolver_factory_) {} - void create(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + virtual void create(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) { cluster_manager_ = std::make_unique( bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, validation_context_, @@ -1224,10 +1224,27 @@ TEST_F(ClusterManagerImplTest, VerifyBufferLimits) { factory_.tls_.shutdownThread(); } -TEST_F(ClusterManagerImplTest, ShutdownOrder) { +class ClusterManagerLifecycleTest : public ClusterManagerImplTest, + public testing::WithParamInterface { +protected: + void create(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) override { + if (useDeferredCluster()) { + auto bootstrap_with_deferred_cluster = bootstrap; + bootstrap_with_deferred_cluster.mutable_cluster_manager() + ->set_enable_deferred_cluster_creation(true); + ClusterManagerImplTest::create(bootstrap_with_deferred_cluster); + } else { + ClusterManagerImplTest::create(bootstrap); + } + } + bool useDeferredCluster() const { return GetParam(); } +}; + +INSTANTIATE_TEST_SUITE_P(ClusterManagerLifecycleTest, ClusterManagerLifecycleTest, testing::Bool()); + +TEST_P(ClusterManagerLifecycleTest, ShutdownOrder) { const std::string json = fmt::sprintf("{\"static_resources\":{%s}}", clustersJson({defaultStaticClusterJson("cluster_1")})); - create(parseBootstrapFromV3Json(json)); Cluster& cluster = cluster_manager_->activeClusters().begin()->second; EXPECT_EQ("cluster_1", cluster.info()->name()); @@ -1242,11 +1259,21 @@ TEST_F(ClusterManagerImplTest, ShutdownOrder) { cluster_manager_->getThreadLocalCluster("cluster_1")->loadBalancer().chooseHost(nullptr)); // Local reference, primary reference, thread local reference, host reference - EXPECT_EQ(4U, cluster.info().use_count()); + if (useDeferredCluster()) { + // Additional reference in Cluster Initialization Object. + EXPECT_EQ(5U, cluster.info().use_count()); + } else { + EXPECT_EQ(4U, cluster.info().use_count()); + } // Thread local reference should be gone. factory_.tls_.shutdownThread(); - EXPECT_EQ(3U, cluster.info().use_count()); + if (useDeferredCluster()) { + // Additional reference in Cluster Initialization Object. + EXPECT_EQ(4U, cluster.info().use_count()); + } else { + EXPECT_EQ(3U, cluster.info().use_count()); + } } TEST_F(ClusterManagerImplTest, TwoEqualCommonLbConfigSharedPool) { @@ -1275,7 +1302,7 @@ TEST_F(ClusterManagerImplTest, TwoUnequalCommonLbConfigSharedPool) { EXPECT_NE(common_config_ptr_a, common_config_ptr_b); } -TEST_F(ClusterManagerImplTest, InitializeOrder) { +TEST_P(ClusterManagerLifecycleTest, InitializeOrder) { time_system_.setSystemTime(std::chrono::milliseconds(1234567891234)); const std::string json = fmt::sprintf( @@ -1487,7 +1514,7 @@ TEST_F(ClusterManagerImplTest, InitializeOrder) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster5.get())); } -TEST_F(ClusterManagerImplTest, DynamicRemoveWithLocalCluster) { +TEST_P(ClusterManagerLifecycleTest, DynamicRemoveWithLocalCluster) { InSequence s; // Setup a cluster manager with a static local cluster. @@ -1547,7 +1574,7 @@ TEST_F(ClusterManagerImplTest, DynamicRemoveWithLocalCluster) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); } -TEST_F(ClusterManagerImplTest, RemoveWarmingCluster) { +TEST_P(ClusterManagerLifecycleTest, RemoveWarmingCluster) { time_system_.setSystemTime(std::chrono::milliseconds(1234567891234)); create(defaultConfig()); @@ -1592,7 +1619,7 @@ TEST_F(ClusterManagerImplTest, RemoveWarmingCluster) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); } -TEST_F(ClusterManagerImplTest, TestModifyWarmingClusterDuringInitialization) { +TEST_P(ClusterManagerLifecycleTest, TestModifyWarmingClusterDuringInitialization) { const std::string json = fmt::sprintf( R"EOF( { @@ -1691,7 +1718,7 @@ TEST_F(ClusterManagerImplTest, TestModifyWarmingClusterDuringInitialization) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(cds_cluster.get())); } -TEST_F(ClusterManagerImplTest, ModifyWarmingCluster) { +TEST_P(ClusterManagerLifecycleTest, ModifyWarmingCluster) { time_system_.setSystemTime(std::chrono::milliseconds(1234567891234)); create(defaultConfig()); @@ -1775,7 +1802,7 @@ TEST_F(ClusterManagerImplTest, ModifyWarmingCluster) { // Regression test for https://github.com/envoyproxy/envoy/issues/14598. // Make sure the revert isn't blocked due to being the same as the active version. -TEST_F(ClusterManagerImplTest, TestRevertWarmingCluster) { +TEST_P(ClusterManagerLifecycleTest, TestRevertWarmingCluster) { time_system_.setSystemTime(std::chrono::milliseconds(1234567891234)); create(defaultConfig()); @@ -1855,7 +1882,7 @@ TEST_F(ClusterManagerImplTest, TestRevertWarmingCluster) { } // Verify that shutting down the cluster manager destroys warming clusters. -TEST_F(ClusterManagerImplTest, ShutdownWithWarming) { +TEST_P(ClusterManagerLifecycleTest, ShutdownWithWarming) { create(defaultConfig()); InSequence s; @@ -1877,7 +1904,7 @@ TEST_F(ClusterManagerImplTest, ShutdownWithWarming) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); } -TEST_F(ClusterManagerImplTest, DynamicAddRemove) { +TEST_P(ClusterManagerLifecycleTest, DynamicAddRemove) { create(defaultConfig()); InSequence s; @@ -1894,7 +1921,7 @@ TEST_F(ClusterManagerImplTest, DynamicAddRemove) { .WillOnce(Return(std::make_pair(cluster1, nullptr))); EXPECT_CALL(*cluster1, initializePhase()).Times(0); EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_CALL(*callbacks, onClusterAddOrUpdate(_)); + EXPECT_CALL(*callbacks, onClusterAddOrUpdate(_, _)); EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); @@ -1923,7 +1950,7 @@ TEST_F(ClusterManagerImplTest, DynamicAddRemove) { // Test inline init. initialize_callback(); })); - EXPECT_CALL(*callbacks, onClusterAddOrUpdate(_)); + EXPECT_CALL(*callbacks, onClusterAddOrUpdate(_, _)); EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(update_cluster, "")); EXPECT_EQ(cluster2->info_, cluster_manager_->getThreadLocalCluster("fake_cluster")->info()); @@ -1981,7 +2008,7 @@ TEST_F(ClusterManagerImplTest, DynamicAddRemove) { } // Validates that a callback can remove itself from the callbacks list. -TEST_F(ClusterManagerImplTest, ClusterAddOrUpdateCallbackRemovalDuringIteration) { +TEST_P(ClusterManagerLifecycleTest, ClusterAddOrUpdateCallbackRemovalDuringIteration) { create(defaultConfig()); InSequence s; @@ -1998,10 +2025,11 @@ TEST_F(ClusterManagerImplTest, ClusterAddOrUpdateCallbackRemovalDuringIteration) .WillOnce(Return(std::make_pair(cluster1, nullptr))); EXPECT_CALL(*cluster1, initializePhase()).Times(0); EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_CALL(*callbacks, onClusterAddOrUpdate(_)).WillOnce(Invoke([&cb](ThreadLocalCluster&) { - // This call will remove the callback from the list. - cb.reset(); - })); + EXPECT_CALL(*callbacks, onClusterAddOrUpdate(_, _)) + .WillOnce(Invoke([&cb](absl::string_view, ThreadLocalClusterCommand&) { + // This call will remove the callback from the list. + cb.reset(); + })); EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); @@ -2029,7 +2057,7 @@ TEST_F(ClusterManagerImplTest, ClusterAddOrUpdateCallbackRemovalDuringIteration) })); // There shouldn't be a call to onClusterAddOrUpdate on the callbacks as the // handler was removed. - EXPECT_CALL(*callbacks, onClusterAddOrUpdate(_)).Times(0); + EXPECT_CALL(*callbacks, onClusterAddOrUpdate(_, _)).Times(0); EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(update_cluster, "")); checkStats(1 /*added*/, 1 /*modified*/, 0 /*removed*/, 1 /*active*/, 0 /*warming*/); @@ -2039,7 +2067,7 @@ TEST_F(ClusterManagerImplTest, ClusterAddOrUpdateCallbackRemovalDuringIteration) EXPECT_TRUE(Mock::VerifyAndClearExpectations(callbacks.get())); } -TEST_F(ClusterManagerImplTest, AddOrUpdateClusterStaticExists) { +TEST_P(ClusterManagerLifecycleTest, AddOrUpdateClusterStaticExists) { const std::string json = fmt::sprintf("{\"static_resources\":{%s}}", clustersJson({defaultStaticClusterJson("fake_cluster")})); std::shared_ptr cluster1(new NiceMock()); @@ -2068,7 +2096,7 @@ TEST_F(ClusterManagerImplTest, AddOrUpdateClusterStaticExists) { } // Verifies that we correctly propagate the host_set state to the TLS clusters. -TEST_F(ClusterManagerImplTest, HostsPostedToTlsCluster) { +TEST_P(ClusterManagerLifecycleTest, HostsPostedToTlsCluster) { const std::string json = fmt::sprintf("{\"static_resources\":{%s}}", clustersJson({defaultStaticClusterJson("fake_cluster")})); std::shared_ptr cluster1(new NiceMock()); @@ -2117,7 +2145,7 @@ TEST_F(ClusterManagerImplTest, HostsPostedToTlsCluster) { } // Test that we close all HTTP connection pool connections when there is a host health failure. -TEST_F(ClusterManagerImplTest, CloseHttpConnectionsOnHealthFailure) { +TEST_P(ClusterManagerLifecycleTest, CloseHttpConnectionsOnHealthFailure) { const std::string json = fmt::sprintf("{\"static_resources\":{%s}}", clustersJson({defaultStaticClusterJson("some_cluster")})); std::shared_ptr cluster1(new NiceMock()); @@ -2184,7 +2212,7 @@ TEST_F(ClusterManagerImplTest, CloseHttpConnectionsOnHealthFailure) { // Test that we drain or close all HTTP or TCP connection pool connections when there is a host // health failure and 'CLOSE_CONNECTIONS_ON_HOST_HEALTH_FAILURE' set to true. -TEST_F(ClusterManagerImplTest, +TEST_P(ClusterManagerLifecycleTest, CloseConnectionsOnHealthFailureWithCloseConnectionsOnHostHealthFailure) { const std::string json = fmt::sprintf("{\"static_resources\":{%s}}", clustersJson({defaultStaticClusterJson("some_cluster")})); @@ -2240,7 +2268,7 @@ TEST_F(ClusterManagerImplTest, // Verify that the pool gets deleted if it is idle, and that a crash does not occur due to // deleting a container while iterating through it (see `do_not_delete_` in // `ClusterManagerImpl::ThreadLocalClusterManagerImpl::onHostHealthFailure()`). -TEST_F(ClusterManagerImplTest, CloseHttpConnectionsAndDeletePoolOnHealthFailure) { +TEST_P(ClusterManagerLifecycleTest, CloseHttpConnectionsAndDeletePoolOnHealthFailure) { const std::string json = fmt::sprintf("{\"static_resources\":{%s}}", clustersJson({defaultStaticClusterJson("some_cluster")})); std::shared_ptr cluster1(new NiceMock()); @@ -2287,7 +2315,7 @@ TEST_F(ClusterManagerImplTest, CloseHttpConnectionsAndDeletePoolOnHealthFailure) } // Test that we close all TCP connection pool connections when there is a host health failure. -TEST_F(ClusterManagerImplTest, CloseTcpConnectionPoolsOnHealthFailure) { +TEST_P(ClusterManagerLifecycleTest, CloseTcpConnectionPoolsOnHealthFailure) { const std::string json = fmt::sprintf("{\"static_resources\":{%s}}", clustersJson({defaultStaticClusterJson("some_cluster")})); std::shared_ptr cluster1(new NiceMock()); @@ -2354,7 +2382,7 @@ TEST_F(ClusterManagerImplTest, CloseTcpConnectionPoolsOnHealthFailure) { // Test that we close all TCP connection pool connections when there is a host health failure, // when configured to do so. -TEST_F(ClusterManagerImplTest, CloseTcpConnectionsOnHealthFailure) { +TEST_P(ClusterManagerLifecycleTest, CloseTcpConnectionsOnHealthFailure) { const std::string yaml = R"EOF( static_resources: clusters: @@ -2432,7 +2460,7 @@ TEST_F(ClusterManagerImplTest, CloseTcpConnectionsOnHealthFailure) { // Test that we do not close TCP connection pool connections when there is a host health failure, // when not configured to do so. -TEST_F(ClusterManagerImplTest, DoNotCloseTcpConnectionsOnHealthFailure) { +TEST_P(ClusterManagerLifecycleTest, DoNotCloseTcpConnectionsOnHealthFailure) { const std::string yaml = R"EOF( static_resources: clusters: @@ -2482,7 +2510,7 @@ TEST_F(ClusterManagerImplTest, DoNotCloseTcpConnectionsOnHealthFailure) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); } -TEST_F(ClusterManagerImplTest, DynamicHostRemove) { +TEST_P(ClusterManagerLifecycleTest, DynamicHostRemove) { const std::string yaml = R"EOF( static_resources: clusters: @@ -2619,7 +2647,7 @@ TEST_F(ClusterManagerImplTest, DynamicHostRemove) { factory_.tls_.shutdownThread(); } -TEST_F(ClusterManagerImplTest, DynamicHostRemoveWithTls) { +TEST_P(ClusterManagerLifecycleTest, DynamicHostRemoveWithTls) { const std::string yaml = R"EOF( static_resources: clusters: @@ -2843,7 +2871,7 @@ TEST_F(ClusterManagerImplTest, DynamicHostRemoveWithTls) { // Test that default DNS resolver with TCP lookups is used, when there are no DNS custom resolvers // configured per cluster and `dns_resolver_options.use_tcp_for_dns_lookups` is set in bootstrap // config. -TEST_F(ClusterManagerImplTest, UseTcpInDefaultDnsResolver) { +TEST_P(ClusterManagerLifecycleTest, UseTcpInDefaultDnsResolver) { const std::string yaml = R"EOF( dns_resolution_config: dns_resolver_options: @@ -2871,7 +2899,7 @@ TEST_F(ClusterManagerImplTest, UseTcpInDefaultDnsResolver) { // Test that custom DNS resolver is used, when custom resolver is configured // per cluster and deprecated field `dns_resolvers` is specified. -TEST_F(ClusterManagerImplTest, CustomDnsResolverSpecifiedViaDeprecatedField) { +TEST_P(ClusterManagerLifecycleTest, CustomDnsResolverSpecifiedViaDeprecatedField) { const std::string yaml = R"EOF( static_resources: clusters: @@ -2907,7 +2935,7 @@ TEST_F(ClusterManagerImplTest, CustomDnsResolverSpecifiedViaDeprecatedField) { // Test that custom DNS resolver is used, when custom resolver is configured // per cluster and deprecated field `dns_resolvers` is specified with multiple resolvers. -TEST_F(ClusterManagerImplTest, CustomDnsResolverSpecifiedViaDeprecatedFieldMultipleResolvers) { +TEST_P(ClusterManagerLifecycleTest, CustomDnsResolverSpecifiedViaDeprecatedFieldMultipleResolvers) { const std::string yaml = R"EOF( static_resources: clusters: @@ -3585,7 +3613,7 @@ TEST_F(ClusterManagerImplTest, TypedDnsResolverConfigSpecifiedOveridingDeprecate // ClusterManagerImpl::ThreadLocalClusterManagerImpl::drainConnPools(), where a removal at one // priority from the ConnPoolsContainer would delete the ConnPoolsContainer mid-iteration over the // pool. -TEST_F(ClusterManagerImplTest, DynamicHostRemoveDefaultPriority) { +TEST_P(ClusterManagerLifecycleTest, DynamicHostRemoveDefaultPriority) { const std::string yaml = R"EOF( static_resources: clusters: @@ -3677,7 +3705,7 @@ class MockTcpConnPoolWithDestroy : public Tcp::ConnectionPool::MockInstance { // Regression test for https://github.com/envoyproxy/envoy/issues/3518. Make sure we handle a // drain callback during CP destroy. -TEST_F(ClusterManagerImplTest, ConnPoolDestroyWithDraining) { +TEST_P(ClusterManagerLifecycleTest, ConnPoolDestroyWithDraining) { const std::string yaml = R"EOF( static_resources: clusters: @@ -3793,7 +3821,7 @@ TEST_F(ClusterManagerImplTest, OriginalDstInitialization) { // there's no hosts changes in between. // Also tests that if hosts are added/removed between mergeable updates, delivery will // happen and the scheduled update will be cancelled. -TEST_F(ClusterManagerImplTest, MergedUpdates) { +TEST_P(ClusterManagerLifecycleTest, MergedUpdates) { createWithLocalClusterUpdate(); // Ensure we see the right set of added/removed hosts on every call. @@ -3930,7 +3958,7 @@ TEST_F(ClusterManagerImplTest, MergedUpdates) { } // Tests that mergeable updates outside of a window get applied immediately. -TEST_F(ClusterManagerImplTest, MergedUpdatesOutOfWindow) { +TEST_P(ClusterManagerLifecycleTest, MergedUpdatesOutOfWindow) { createWithLocalClusterUpdate(); // Ensure we see the right set of added/removed hosts on every call. @@ -3966,7 +3994,7 @@ TEST_F(ClusterManagerImplTest, MergedUpdatesOutOfWindow) { } // Tests that mergeable updates inside of a window are not applied immediately. -TEST_F(ClusterManagerImplTest, MergedUpdatesInsideWindow) { +TEST_P(ClusterManagerLifecycleTest, MergedUpdatesInsideWindow) { createWithLocalClusterUpdate(); Cluster& cluster = cluster_manager_->activeClusters().begin()->second; @@ -3994,7 +4022,7 @@ TEST_F(ClusterManagerImplTest, MergedUpdatesInsideWindow) { // Tests that mergeable updates outside of a window get applied immediately when // merging is disabled, and that the counters are correct. -TEST_F(ClusterManagerImplTest, MergedUpdatesOutOfWindowDisabled) { +TEST_P(ClusterManagerLifecycleTest, MergedUpdatesOutOfWindowDisabled) { createWithLocalClusterUpdate(false); // Ensure we see the right set of added/removed hosts on every call. @@ -4027,7 +4055,7 @@ TEST_F(ClusterManagerImplTest, MergedUpdatesOutOfWindowDisabled) { EXPECT_EQ(0, factory_.stats_.counter("cluster_manager.update_merge_cancelled").value()); } -TEST_F(ClusterManagerImplTest, MergedUpdatesDestroyedOnUpdate) { +TEST_P(ClusterManagerLifecycleTest, MergedUpdatesDestroyedOnUpdate) { // We create the default cluster, although for this test we won't use it since // we can only update dynamic clusters. createWithLocalClusterUpdate(); @@ -4332,7 +4360,7 @@ TEST_F(ClusterManagerImplTest, HttpPoolDataForwardsCallsToConnectionPool) { // Test that the read only cross-priority host map in the main thread is correctly synchronized to // the worker thread when the cluster's host set is updated. -TEST_F(ClusterManagerImplTest, CrossPriorityHostMapSyncTest) { +TEST_P(ClusterManagerLifecycleTest, CrossPriorityHostMapSyncTest) { std::string yaml = R"EOF( static_resources: clusters: @@ -5461,7 +5489,7 @@ TEST_F(TcpKeepaliveTest, TcpKeepaliveWithAllOptions) { } // Make sure the drainConnections() with a predicate can correctly exclude a host. -TEST_F(ClusterManagerImplTest, DrainConnectionsPredicate) { +TEST_P(ClusterManagerLifecycleTest, DrainConnectionsPredicate) { const std::string yaml = R"EOF( static_resources: clusters: @@ -5506,7 +5534,7 @@ TEST_F(ClusterManagerImplTest, DrainConnectionsPredicate) { }); } -TEST_F(ClusterManagerImplTest, ConnPoolsDrainedOnHostSetChange) { +TEST_P(ClusterManagerLifecycleTest, ConnPoolsDrainedOnHostSetChange) { const std::string yaml = R"EOF( static_resources: clusters: @@ -5649,7 +5677,7 @@ TEST_F(ClusterManagerImplTest, ConnPoolsDrainedOnHostSetChange) { hosts_added, {}, absl::nullopt, 100); } -TEST_F(ClusterManagerImplTest, ConnPoolsNotDrainedOnHostSetChange) { +TEST_P(ClusterManagerLifecycleTest, ConnPoolsNotDrainedOnHostSetChange) { const std::string yaml = R"EOF( static_resources: clusters: @@ -5718,7 +5746,7 @@ TEST_F(ClusterManagerImplTest, ConnPoolsNotDrainedOnHostSetChange) { hosts_added, {}, absl::nullopt, 100); } -TEST_F(ClusterManagerImplTest, ConnPoolsIdleDeleted) { +TEST_P(ClusterManagerLifecycleTest, ConnPoolsIdleDeleted) { TestScopedRuntime scoped_runtime; const std::string yaml = R"EOF( diff --git a/test/common/upstream/cluster_update_tracker_test.cc b/test/common/upstream/cluster_update_tracker_test.cc index c97ac7831db1..9ab61529bf07 100644 --- a/test/common/upstream/cluster_update_tracker_test.cc +++ b/test/common/upstream/cluster_update_tracker_test.cc @@ -52,14 +52,16 @@ TEST_F(ClusterUpdateTrackerTest, ShouldProperlyHandleUpdateCallbacks) { { // Simulate addition of an irrelevant cluster. - cluster_tracker.onClusterAddOrUpdate(irrelevant_); + ThreadLocalClusterCommand command = [this]() -> ThreadLocalCluster& { return irrelevant_; }; + cluster_tracker.onClusterAddOrUpdate("unrelated_cluster", command); EXPECT_FALSE(cluster_tracker.threadLocalCluster().has_value()); } { // Simulate addition of the relevant cluster. - cluster_tracker.onClusterAddOrUpdate(expected_); + ThreadLocalClusterCommand command = [this]() -> ThreadLocalCluster& { return expected_; }; + cluster_tracker.onClusterAddOrUpdate(cluster_name_, command); ASSERT_TRUE(cluster_tracker.threadLocalCluster().has_value()); EXPECT_EQ(cluster_tracker.threadLocalCluster()->get().info(), expected_.cluster_.info_); diff --git a/test/common/upstream/deferred_cluster_initialization_test.cc b/test/common/upstream/deferred_cluster_initialization_test.cc new file mode 100644 index 000000000000..ff0824bf0e71 --- /dev/null +++ b/test/common/upstream/deferred_cluster_initialization_test.cc @@ -0,0 +1,815 @@ +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.validate.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/endpoint/v3/endpoint.pb.h" +#include "envoy/config/endpoint/v3/endpoint_components.pb.h" +#include "envoy/service/discovery/v3/discovery.pb.h" + +#include "source/extensions/clusters/eds/eds.h" +#include "source/extensions/clusters/static/static_cluster.h" + +#include "test/common/upstream/test_cluster_manager.h" +#include "test/mocks/config/mocks.h" +#include "test/mocks/protobuf/mocks.h" +#include "test/mocks/server/instance.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Upstream { +namespace { + +using testing::_; + +using ClusterType = absl::variant; + +void setClusterType(envoy::config::cluster::v3::Cluster& cluster, const ClusterType& cluster_type) { + cluster.clear_cluster_discovery_type(); + if (absl::holds_alternative(cluster_type)) { + cluster.set_type(absl::get(cluster_type)); + } else if (absl::holds_alternative( + cluster_type)) { + cluster.mutable_cluster_type()->CopyFrom( + absl::get(cluster_type)); + } +} + +bool hostsInHostsVector(const Envoy::Upstream::HostVector& host_vector, + std::vector host_ports) { + size_t matches = 0; + std::sort(host_ports.begin(), host_ports.end()); + for (const auto& host : host_vector) { + if (std::binary_search(host_ports.begin(), host_ports.end(), host->address()->ip()->port())) { + ++matches; + } + } + return matches == host_ports.size(); +} + +envoy::config::cluster::v3::Cluster parseClusterFromV3Yaml(const std::string& yaml_config, + const ClusterType& cluster_type) { + auto cluster = parseClusterFromV3Yaml(yaml_config); + setClusterType(cluster, cluster_type); + return cluster; +} + +class DeferredClusterInitializationTest : public testing::TestWithParam { +protected: + DeferredClusterInitializationTest() + : http_context_(factory_.stats_.symbolTable()), grpc_context_(factory_.stats_.symbolTable()), + router_context_(factory_.stats_.symbolTable()) {} + + void create(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + cluster_manager_ = std::make_unique( + bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, + factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, validation_context_, + *factory_.api_, http_context_, grpc_context_, router_context_, server_); + cluster_manager_->setPrimaryClustersInitializedCb( + [this, bootstrap]() { cluster_manager_->initializeSecondaryClusters(bootstrap); }); + } + + ClusterType getStaticClusterType() const { + if (GetParam()) { + envoy::config::cluster::v3::Cluster::CustomClusterType custom_cluster_type; + custom_cluster_type.set_name("envoy.cluster.static"); + return custom_cluster_type; + } + + return envoy::config::cluster::v3::Cluster::STATIC; + } + + ClusterType getEdsClusterType() const { + if (GetParam()) { + ASSERT(false, "EDS cluster support via CustomClusterType unimplemented."); + envoy::config::cluster::v3::Cluster::CustomClusterType custom_cluster_type; + return custom_cluster_type; + } + + return envoy::config::cluster::v3::Cluster::EDS; + } + + envoy::config::bootstrap::v3::Bootstrap + parseBootstrapFromV3YamlEnableDeferredCluster(const std::string& yaml) { + envoy::config::bootstrap::v3::Bootstrap bootstrap; + TestUtility::loadFromYaml(yaml, bootstrap); + bootstrap.mutable_cluster_manager()->set_enable_deferred_cluster_creation(true); + ClusterType cluster_type = getStaticClusterType(); + for (auto& cluster : *bootstrap.mutable_static_resources()->mutable_clusters()) { + setClusterType(cluster, cluster_type); + } + return bootstrap; + } + + uint64_t readGauge(const std::string& gauge_name) const { + auto gauge_or = factory_.stats_.findGaugeByString(gauge_name); + ASSERT(gauge_or.has_value()); + return gauge_or.value().get().value(); + } + + NiceMock factory_; + NiceMock validation_context_; + std::unique_ptr cluster_manager_; + AccessLog::MockAccessLogManager log_manager_; + NiceMock admin_; + Http::ContextImpl http_context_; + Grpc::ContextImpl grpc_context_; + Router::ContextImpl router_context_; + NiceMock server_; +}; + +class StaticClusterTest : public DeferredClusterInitializationTest {}; + +INSTANTIATE_TEST_SUITE_P(UseCustomClusterType, StaticClusterTest, testing::Bool()); + +// Test that bootstrap static clusters are deferred initialized. +TEST_P(StaticClusterTest, BootstrapStaticClustersAreDeferredInitialized) { + const std::string yaml = R"EOF( + static_resources: + clusters: + - name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: cluster_1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11001 + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11002 + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(yaml); + + EXPECT_LOG_CONTAINS("debug", "Deferring add or update for TLS cluster cluster_1", + create(bootstrap)); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); +} + +// Test that cds cluster are deferred initialized. +TEST_P(StaticClusterTest, CdsStaticClustersAreDeferredInitialized) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + clusters: + - name: bootstrap_cluster + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: bootstrap_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + const std::string static_cds_cluster_yaml = R"EOF( + name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: cluster_1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11001 + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11002 + )EOF"; + + EXPECT_LOG_CONTAINS("debug", "Deferring add or update for TLS cluster cluster_1", { + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(static_cds_cluster_yaml, getStaticClusterType()), "version1")); + }); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); +} + +// Test that we can merge deferred cds cluster configuration. +TEST_P(StaticClusterTest, MergeStaticCdsClusterUpdates) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + + { + const std::string static_cds_cluster_yaml_v1 = R"EOF( + name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: cluster_1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60001 + )EOF"; + + EXPECT_LOG_CONTAINS("debug", "Deferring add or update for TLS cluster cluster_1", { + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(static_cds_cluster_yaml_v1, getStaticClusterType()), "version1")); + }); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + } + + { + const std::string static_cds_cluster_yaml_v2 = R"EOF( + name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: cluster_1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11001 + priority: 0 + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11002 + priority: 1 + )EOF"; + + EXPECT_LOG_CONTAINS("debug", "Deferring add or update for TLS cluster cluster_1", { + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(static_cds_cluster_yaml_v2, getStaticClusterType()), "version2")); + }); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + } + + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); + Cluster& cluster = cluster_manager_->activeClusters().find("cluster_1")->second; + + // Check we only know of the two endpoints from the recent update. + EXPECT_EQ(cluster.info()->endpointStats().membership_total_.value(), 2); + EXPECT_EQ(cluster.prioritySet().crossPriorityHostMap()->size(), 2); + auto& host_sets_vector = cluster.prioritySet().hostSetsPerPriority(); + for (auto& host_set : host_sets_vector) { + EXPECT_EQ(host_set->hosts().size(), 1); + } +} + +// Test that an active deferred cds cluster can get updated after initialization. +TEST_P(StaticClusterTest, ActiveClusterGetsUpdated) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + + { + const std::string static_cds_cluster_yaml_v1 = R"EOF( + name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: cluster_1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60001 + )EOF"; + + EXPECT_LOG_CONTAINS("debug", "Deferring add or update for TLS cluster cluster_1", { + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(static_cds_cluster_yaml_v1, getStaticClusterType()), "version1")); + }); + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); + } + + { + const std::string static_cds_cluster_yaml_v2 = R"EOF( + name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: cluster_1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11001 + priority: 0 + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11002 + priority: 1 + )EOF"; + // Expect this line to fail as we should just inflate as usual. + EXPECT_LOG_CONTAINS("debug", "updating TLS cluster cluster_1", { + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(static_cds_cluster_yaml_v2, getStaticClusterType()), "version2")); + }); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); + } + + Cluster& cluster = cluster_manager_->activeClusters().find("cluster_1")->second; + + // Check we only know of the two endpoints from the recent update. + EXPECT_EQ(cluster.info()->endpointStats().membership_total_.value(), 2); + EXPECT_EQ(cluster.prioritySet().crossPriorityHostMap()->size(), 2); + auto& host_sets_vector = cluster.prioritySet().hostSetsPerPriority(); + for (auto& host_set : host_sets_vector) { + EXPECT_EQ(host_set->hosts().size(), 1); + } +} + +// Test that removed deferred cds clusters have their cluster initialization object removed. +TEST_P(StaticClusterTest, RemoveDeferredCluster) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + + const std::string static_cds_cluster_yaml_v1 = R"EOF( + name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: cluster_1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60001 + )EOF"; + + EXPECT_LOG_CONTAINS("debug", "Deferring add or update for TLS cluster cluster_1", { + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(static_cds_cluster_yaml_v1, getStaticClusterType()), "version1")); + }); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + + cluster_manager_->removeCluster("cluster_1"); + EXPECT_EQ(factory_.stats_.counter("cluster_manager.cluster_removed").value(), 1); + EXPECT_EQ(cluster_manager_->getThreadLocalCluster("cluster_1"), nullptr); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); +} + +class MockConfigSubscriptionFactory : public Config::ConfigSubscriptionFactory { +public: + std::string name() const override { return "envoy.config_subscription.rest"; } + MOCK_METHOD(Config::SubscriptionPtr, create, (SubscriptionData & data), (override)); +}; + +class EdsTest : public DeferredClusterInitializationTest { +protected: + void doOnConfigUpdateVerifyNoThrow( + const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment) { + const auto decoded_resources = + TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + VERBOSE_EXPECT_NO_THROW(callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "")); + } + + void addEndpoint(envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment, + uint32_t port, uint32_t priority = 0) { + auto* endpoints = cluster_load_assignment.add_endpoints(); + endpoints->set_priority(priority); + auto* socket_address = endpoints->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("1.2.3.4"); + socket_address->set_port_value(port); + } + + NiceMock factory_; + Registry::InjectFactory registered_{factory_}; + Config::SubscriptionCallbacks* callbacks_{nullptr}; +}; + +// TODO(kbaichoo): when Eds Cluster supports getting its config via +// custom_cluster_type then we can enable these tests to run with that config as +// well. +INSTANTIATE_TEST_SUITE_P(UseCustomClusterType, EdsTest, testing::ValuesIn({false})); + +// Test that hosts can be added to deferred initialized eds cluster. +TEST_P(EdsTest, ShouldMergeAddingHosts) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + clusters: + - name: eds + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: bootstrap_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + + const std::string eds_cluster_yaml = R"EOF( + name: cluster_1 + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + eds_cluster_config: + service_name: fare + eds_config: + api_config_source: + api_type: REST + transport_api_version: V3 + cluster_names: + - eds + refresh_delay: 1s + )EOF"; + + EXPECT_CALL(factory_, create(_)) + .WillOnce(testing::Invoke([this](Config::ConfigSubscriptionFactory::SubscriptionData& data) { + callbacks_ = &data.callbacks_; + return std::make_unique>(); + })); + + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(eds_cluster_yaml, getEdsClusterType()), "version1")); + + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + for (int i = 1; i < 11; ++i) { + addEndpoint(cluster_load_assignment, 1000 * i); + const auto decoded_resources = + TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + } + + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); + Cluster& cluster = cluster_manager_->activeClusters().find("cluster_1")->second; + EXPECT_EQ(cluster.info()->endpointStats().membership_total_.value(), 10); + EXPECT_TRUE(hostsInHostsVector(cluster.prioritySet().hostSetsPerPriority()[0]->hosts(), + {1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000})); +} + +// Test that removed hosts do not appear when initializing a deferred eds cluster. +TEST_P(EdsTest, ShouldNotHaveRemovedHosts) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + clusters: + - name: eds + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: bootstrap_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + + const std::string eds_cluster_yaml = R"EOF( + name: cluster_1 + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + eds_cluster_config: + service_name: fare + eds_config: + api_config_source: + api_type: REST + transport_api_version: V3 + cluster_names: + - eds + refresh_delay: 1s + )EOF"; + + EXPECT_CALL(factory_, create(_)) + .WillOnce(testing::Invoke([this](Config::ConfigSubscriptionFactory::SubscriptionData& data) { + callbacks_ = &data.callbacks_; + return std::make_unique>(); + })); + + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(eds_cluster_yaml, getEdsClusterType()), "version1")); + + // ClusterLoadAssignment should contain all hosts to be kept for the + // cluster. If a host is not in a subsequent update it is removed. + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + addEndpoint(cluster_load_assignment, 1000); + addEndpoint(cluster_load_assignment, 2000); + addEndpoint(cluster_load_assignment, 3000); + addEndpoint(cluster_load_assignment, 4000); + auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + + cluster_load_assignment.clear_endpoints(); + addEndpoint(cluster_load_assignment, 1000); + addEndpoint(cluster_load_assignment, 2000); + decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); + Cluster& cluster = cluster_manager_->activeClusters().find("cluster_1")->second; + EXPECT_EQ(cluster.info()->endpointStats().membership_total_.value(), 2); + EXPECT_TRUE( + hostsInHostsVector(cluster.prioritySet().hostSetsPerPriority()[0]->hosts(), {1000, 2000})); +} + +// Test that removed hosts that were added again appear when initializing a deferred eds cluster. +TEST_P(EdsTest, ShouldHaveHostThatWasAddedAfterRemoval) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + clusters: + - name: eds + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: bootstrap_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + + const std::string eds_cluster_yaml = R"EOF( + name: cluster_1 + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + eds_cluster_config: + service_name: fare + eds_config: + api_config_source: + api_type: REST + transport_api_version: V3 + cluster_names: + - eds + refresh_delay: 1s + )EOF"; + + EXPECT_CALL(factory_, create(_)) + .WillOnce(testing::Invoke([this](Config::ConfigSubscriptionFactory::SubscriptionData& data) { + callbacks_ = &data.callbacks_; + return std::make_unique>(); + })); + + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(eds_cluster_yaml, getEdsClusterType()), "version1")); + + // ClusterLoadAssignment should contain all hosts to be kept for the + // cluster. If a host is not in a subsequent update it is removed. + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + addEndpoint(cluster_load_assignment, 1000); + addEndpoint(cluster_load_assignment, 2000); + addEndpoint(cluster_load_assignment, 3000); + addEndpoint(cluster_load_assignment, 4000); + auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + + cluster_load_assignment.clear_endpoints(); + addEndpoint(cluster_load_assignment, 1000); + addEndpoint(cluster_load_assignment, 2000); + decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + + addEndpoint(cluster_load_assignment, 3000); + decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); + Cluster& cluster = cluster_manager_->activeClusters().find("cluster_1")->second; + EXPECT_EQ(cluster.info()->endpointStats().membership_total_.value(), 3); + EXPECT_TRUE(hostsInHostsVector(cluster.prioritySet().hostSetsPerPriority()[0]->hosts(), + {1000, 2000, 3000})); +} + +// Test merging multiple priorities for a deferred eds cluster. +TEST_P(EdsTest, MultiplePrioritiesShouldMergeCorrectly) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + clusters: + - name: eds + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: bootstrap_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + + const std::string eds_cluster_yaml = R"EOF( + name: cluster_1 + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + eds_cluster_config: + service_name: fare + eds_config: + api_config_source: + api_type: REST + transport_api_version: V3 + cluster_names: + - eds + refresh_delay: 1s + )EOF"; + + EXPECT_CALL(factory_, create(_)) + .WillOnce(testing::Invoke([this](Config::ConfigSubscriptionFactory::SubscriptionData& data) { + callbacks_ = &data.callbacks_; + return std::make_unique>(); + })); + + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(eds_cluster_yaml, getEdsClusterType()), "version1")); + + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + addEndpoint(cluster_load_assignment, 1000); + addEndpoint(cluster_load_assignment, 2000); + addEndpoint(cluster_load_assignment, 3000, 2); + auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + + cluster_load_assignment.clear_endpoints(); + addEndpoint(cluster_load_assignment, 1000); + addEndpoint(cluster_load_assignment, 4000); + addEndpoint(cluster_load_assignment, 5000, 1); + decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); + Cluster& cluster = cluster_manager_->activeClusters().find("cluster_1")->second; + EXPECT_EQ(cluster.prioritySet().hostSetsPerPriority().size(), 3); + EXPECT_TRUE( + hostsInHostsVector(cluster.prioritySet().hostSetsPerPriority()[0]->hosts(), {1000, 4000})); + EXPECT_TRUE(hostsInHostsVector(cluster.prioritySet().hostSetsPerPriority()[1]->hosts(), {5000})); + EXPECT_TRUE(cluster.prioritySet().hostSetsPerPriority()[2]->hosts().empty()); +} + +// Test updating an initialized deferred eds cluster. +TEST_P(EdsTest, ActiveClusterGetsUpdated) { + const std::string bootstrap_yaml = R"EOF( + static_resources: + clusters: + - name: eds + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: bootstrap_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 60000 + )EOF"; + + auto bootstrap = parseBootstrapFromV3YamlEnableDeferredCluster(bootstrap_yaml); + create(bootstrap); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 0); + + const std::string eds_cluster_yaml = R"EOF( + name: cluster_1 + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + eds_cluster_config: + service_name: fare + eds_config: + api_config_source: + api_type: REST + transport_api_version: V3 + cluster_names: + - eds + refresh_delay: 1s + )EOF"; + + EXPECT_CALL(factory_, create(_)) + .WillOnce(testing::Invoke([this](Config::ConfigSubscriptionFactory::SubscriptionData& data) { + callbacks_ = &data.callbacks_; + return std::make_unique>(); + })); + + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV3Yaml(eds_cluster_yaml, getEdsClusterType()), "version1")); + + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + addEndpoint(cluster_load_assignment, 1000); + addEndpoint(cluster_load_assignment, 2000); + addEndpoint(cluster_load_assignment, 3000); + auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + + EXPECT_LOG_CONTAINS("debug", "initializing TLS cluster cluster_1 inline", + cluster_manager_->getThreadLocalCluster("cluster_1")); + EXPECT_EQ(readGauge("thread_local_cluster_manager.test_thread.clusters_inflated"), 1); + Cluster& cluster = cluster_manager_->activeClusters().find("cluster_1")->second; + EXPECT_TRUE( + hostsInHostsVector(cluster.prioritySet().hostSetsPerPriority()[0]->hosts(), {1000, 2000})); + + cluster_load_assignment.clear_endpoints(); + addEndpoint(cluster_load_assignment, 1000); + addEndpoint(cluster_load_assignment, 4000); + decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + EXPECT_TRUE( + hostsInHostsVector(cluster.prioritySet().hostSetsPerPriority()[0]->hosts(), {1000, 4000})); +} + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/hds_test.cc b/test/common/upstream/hds_test.cc index dd51ad0a6152..e6430bdc9cba 100644 --- a/test/common/upstream/hds_test.cc +++ b/test/common/upstream/hds_test.cc @@ -290,7 +290,9 @@ TEST_F(HdsTest, TestProcessMessageEndpoints) { auto* health_check = message->add_cluster_health_checks(); health_check->set_cluster_name("anna" + std::to_string(i)); for (int j = 0; j < 3; j++) { - auto* address = health_check->add_locality_endpoints()->add_endpoints()->mutable_address(); + auto* locality_endpoints = health_check->add_locality_endpoints(); + locality_endpoints->mutable_locality()->set_zone(std::to_string(j)); + auto* address = locality_endpoints->add_endpoints()->mutable_address(); address->mutable_socket_address()->set_address("127.0.0." + std::to_string(i)); address->mutable_socket_address()->set_port_value(1234 + j); } @@ -1208,7 +1210,9 @@ TEST_F(HdsTest, TestCustomHealthCheckPortWhenCreate) { auto* health_check = message->add_cluster_health_checks(); health_check->set_cluster_name("anna"); for (int i = 0; i < 3; i++) { - auto* endpoint = health_check->add_locality_endpoints()->add_endpoints(); + auto* locality_endpoints = health_check->add_locality_endpoints(); + locality_endpoints->mutable_locality()->set_zone(std::to_string(i)); + auto* endpoint = locality_endpoints->add_endpoints(); endpoint->mutable_health_check_config()->set_port_value(4321 + i); auto* address = endpoint->mutable_address(); address->mutable_socket_address()->set_address("127.0.0.1"); diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc index c620fa879e50..8bf405ca46a9 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/common/upstream/load_balancer_impl_test.cc @@ -7,6 +7,8 @@ #include #include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/core/v3/health_check.pb.h" #include "source/common/network/utility.h" #include "source/common/upstream/load_balancer_impl.h" @@ -989,9 +991,17 @@ TEST_P(RoundRobinLoadBalancerTest, Seed) { } TEST_P(RoundRobinLoadBalancerTest, Locality) { - HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime())})); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + + HostVectorSharedPtr hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)})); HostsPerLocalitySharedPtr hosts_per_locality = makeHostsPerLocality({{(*hosts)[1]}, {(*hosts)[0]}, {(*hosts)[2]}}); hostSet().hosts_ = *hosts; @@ -1019,9 +1029,15 @@ TEST_P(RoundRobinLoadBalancerTest, Locality) { } TEST_P(RoundRobinLoadBalancerTest, DegradedLocality) { - HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:84", simTime())})); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + + HostVectorSharedPtr hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_b)})); HostVectorSharedPtr healthy_hosts(new HostVector({(*hosts)[0]})); HostVectorSharedPtr degraded_hosts(new HostVector({(*hosts)[1], (*hosts)[2]})); HostsPerLocalitySharedPtr hosts_per_locality = @@ -1199,11 +1215,17 @@ TEST_P(RoundRobinLoadBalancerTest, DisablePanicMode) { TEST_P(RoundRobinLoadBalancerTest, HostSelectionWithFilter) { NiceMock context; - HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime())})); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + + HostVectorSharedPtr hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)})); HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}}); hostSet().hosts_ = *hosts; hostSet().healthy_hosts_ = *hosts; @@ -1242,13 +1264,21 @@ TEST_P(RoundRobinLoadBalancerTest, HostSelectionWithFilter) { } TEST_P(RoundRobinLoadBalancerTest, ZoneAwareSmallCluster) { - HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime())})); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + + HostVectorSharedPtr hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)})); HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:82", simTime())}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}}); hostSet().hosts_ = *hosts; hostSet().healthy_hosts_ = *hosts; @@ -1288,16 +1318,24 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareDifferentZoneSize) { if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; } - HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime())})); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + + HostVectorSharedPtr hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)})); HostsPerLocalitySharedPtr upstream_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:82", simTime())}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}}); HostsPerLocalitySharedPtr local_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}}); hostSet().healthy_hosts_ = *hosts; hostSet().hosts_ = *hosts; @@ -1323,13 +1361,20 @@ TEST_P(RoundRobinLoadBalancerTest, ZoneAwareRoutingLargeZoneSwitchOnOff) { if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; } - HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime())})); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + HostVectorSharedPtr hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)})); HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:82", simTime())}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}}); EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) .WillRepeatedly(Return(50)); @@ -1360,28 +1405,34 @@ TEST_P(RoundRobinLoadBalancerTest, ZoneAwareRoutingSmallZone) { if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; } + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); HostVectorSharedPtr upstream_hosts( - new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:83", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:84", simTime())})); + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_c)})); HostVectorSharedPtr local_hosts( - new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:1", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:2", simTime())})); + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_c)})); HostsPerLocalitySharedPtr upstream_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:83", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:84", simTime())}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:83", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_c)}}); HostsPerLocalitySharedPtr local_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:1", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:2", simTime())}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_c)}}); EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) .WillRepeatedly(Return(50)); @@ -1411,6 +1462,10 @@ TEST_P(RoundRobinLoadBalancerTest, LowPrecisionForDistribution) { if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; } + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); // upstream_hosts and local_hosts do not matter, zone aware routing is based on per zone hosts. HostVectorSharedPtr upstream_hosts( new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime())})); @@ -1431,31 +1486,32 @@ TEST_P(RoundRobinLoadBalancerTest, LowPrecisionForDistribution) { // The following host distribution with current precision should lead to the no_capacity_left // situation. - // Reuse the same host in all of the structures below to reduce time test takes and this does - // not impact load balancing logic. - HostSharedPtr host = makeTestHost(info_, "tcp://127.0.0.1:80", simTime()); + // Reuse the same host for each zone in all of the structures below to reduce time test takes and + // this does not impact load balancing logic. + HostSharedPtr host_a = makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a); + HostSharedPtr host_b = makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_b); HostVector current(45000); for (int i = 0; i < 45000; ++i) { - current[i] = host; + current[i] = host_a; } local_hosts_per_locality.push_back(current); current.resize(55000); for (int i = 0; i < 55000; ++i) { - current[i] = host; + current[i] = host_b; } local_hosts_per_locality.push_back(current); current.resize(44999); for (int i = 0; i < 44999; ++i) { - current[i] = host; + current[i] = host_a; } upstream_hosts_per_locality.push_back(current); current.resize(55001); for (int i = 0; i < 55001; ++i) { - current[i] = host; + current[i] = host_b; } upstream_hosts_per_locality.push_back(current); @@ -1490,12 +1546,17 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingOneZone) { } TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingNotHealthy) { - HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.2:80", simTime())})); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostVectorSharedPtr hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.2:80", simTime(), zone_a)})); HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{}, - {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.2:80", simTime())}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.2:80", simTime(), zone_a)}}, + true); hostSet().healthy_hosts_ = *hosts; hostSet().hosts_ = *hosts; @@ -1512,15 +1573,19 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingLocalEmpty) { if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; } + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); HostVectorSharedPtr upstream_hosts( - new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime())})); + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)})); HostVectorSharedPtr local_hosts(new HostVector({}, {})); HostsPerLocalitySharedPtr upstream_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}}); - HostsPerLocalitySharedPtr local_hosts_per_locality = makeHostsPerLocality({{}, {}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}}); + HostsPerLocalitySharedPtr local_hosts_per_locality = makeHostsPerLocality({{{}}, {{}}}); EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) .WillOnce(Return(50)) @@ -1548,15 +1613,21 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingLocalEmptyFailTrafficOnPani if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; } + + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostVectorSharedPtr upstream_hosts( - new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime())})); + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)})); HostVectorSharedPtr local_hosts(new HostVector({}, {})); HostsPerLocalitySharedPtr upstream_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}}); - HostsPerLocalitySharedPtr local_hosts_per_locality = makeHostsPerLocality({{}, {}}); + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}}); + HostsPerLocalitySharedPtr local_hosts_per_locality = makeHostsPerLocality({{{}}, {{}}}); EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) .WillOnce(Return(50)) @@ -1585,14 +1656,18 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingNoLocalLocality) { if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; } + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); HostVectorSharedPtr upstream_hosts( - new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime())})); - HostVectorSharedPtr local_hosts(new HostVector({}, {})); + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)})); + HostVectorSharedPtr local_hosts(new HostVector()); HostsPerLocalitySharedPtr upstream_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}, - {makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}}, + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}}, true); const HostsPerLocalitySharedPtr& local_hosts_per_locality = upstream_hosts_per_locality; diff --git a/test/common/upstream/outlier_detection_impl_test.cc b/test/common/upstream/outlier_detection_impl_test.cc index cc32ab420c6c..c32224a8d683 100644 --- a/test/common/upstream/outlier_detection_impl_test.cc +++ b/test/common/upstream/outlier_detection_impl_test.cc @@ -287,6 +287,7 @@ TEST_F(OutlierDetectorImplTest, DestroyHostInUse) { http codes. (this happens in http router). */ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaHttpCodes) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -357,6 +358,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaHttpCodes) { Tests scenario when active health check clears the FAILED_OUTLIER_CHECK flag. */ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaHttpCodesWithActiveHC) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -483,8 +485,10 @@ TEST_F(OutlierDetectorImplTest, ConnectSuccessWithOptionalHTTP_OK) { * code. */ TEST_F(OutlierDetectorImplTest, ExternalOriginEventsNonSplit) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(60)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); + addHosts({"tcp://127.0.0.2:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create(cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, @@ -510,6 +514,7 @@ TEST_F(OutlierDetectorImplTest, ExternalOriginEventsNonSplit) { } TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaNonHttpCodes) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -588,6 +593,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaNonHttpCodes) { * values. */ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailure) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -680,6 +686,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailure) { * Test passing of optional HTTP code with Result:: LOCAL_ORIGIN_TIMEOUT */ TEST_F(OutlierDetectorImplTest, TimeoutWithHttpCode) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(35)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({ "tcp://127.0.0.1:80", @@ -754,6 +761,7 @@ TEST_F(OutlierDetectorImplTest, TimeoutWithHttpCode) { * should cause the host to be ejected again. */ TEST_F(OutlierDetectorImplTest, LargeNumberOfTimeouts) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({ "tcp://127.0.0.1:80", @@ -811,6 +819,7 @@ TEST_F(OutlierDetectorImplTest, LargeNumberOfTimeouts) { * Set of tests to verify ejecting and unejecting nodes when local/connect failures are reported. */ TEST_F(OutlierDetectorImplTest, BasicFlowLocalOriginFailure) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}, true); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -925,6 +934,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowLocalOriginFailure) { * This will also ensure that the stats counters end up with the expected values. */ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailureAnd5xx) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -1014,6 +1024,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailureAnd5xx) { // Test mapping of Non-Http codes to Http. This happens when split between external and local // origin errors is turned off. TEST_F(OutlierDetectorImplTest, BasicFlowNonHttpCodesExternalOrigin) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -1059,6 +1070,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowNonHttpCodesExternalOrigin) { } TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateExternalOrigin) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({ "tcp://127.0.0.1:80", @@ -1165,6 +1177,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateExternalOrigin) { // Test verifies that EXT_ORIGIN_REQUEST_FAILED and EXT_ORIGIN_REQUEST_SUCCESS cancel // each other in split mode. TEST_F(OutlierDetectorImplTest, ExternalOriginEventsWithSplit) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}, true); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -1191,6 +1204,7 @@ TEST_F(OutlierDetectorImplTest, ExternalOriginEventsWithSplit) { } TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateLocalOrigin) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({ "tcp://127.0.0.1:80", @@ -1303,6 +1317,7 @@ TEST_F(OutlierDetectorImplTest, EmptySuccessRate) { } TEST_F(OutlierDetectorImplTest, BasicFlowFailurePercentageExternalOrigin) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({ "tcp://127.0.0.1:80", @@ -1423,6 +1438,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowFailurePercentageExternalOrigin) { } TEST_F(OutlierDetectorImplTest, BasicFlowFailurePercentageLocalOrigin) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({ "tcp://127.0.0.1:80", @@ -1528,6 +1544,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowFailurePercentageLocalOrigin) { } TEST_F(OutlierDetectorImplTest, RemoveWhileEjected) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -1566,7 +1583,7 @@ TEST_F(OutlierDetectorImplTest, Overflow) { event_logger_, random_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); - ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(1)); + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(60)); loadRq(hosts_[0], 4, 500); @@ -1586,8 +1603,9 @@ TEST_F(OutlierDetectorImplTest, Overflow) { } TEST_F(OutlierDetectorImplTest, NotEnforcing) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(35)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); - addHosts({"tcp://127.0.0.1:80"}); + addHosts({"tcp://127.0.0.1:80", "tcp://127.0.0.1:81", "tcp://127.0.0.1:82"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create(cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, @@ -1637,7 +1655,7 @@ TEST_F(OutlierDetectorImplTest, EjectionActiveValueIsAccountedWithoutMetricStora event_logger_, random_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); - ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(1)); + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(50)); loadRq(hosts_[0], 4, 500); @@ -1718,6 +1736,7 @@ TEST_F(OutlierDetectorImplTest, CrossThreadDestroyRace) { } TEST_F(OutlierDetectorImplTest, CrossThreadFailRace) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -1782,7 +1801,27 @@ max_ejection_time_jitter: 13s EXPECT_FALSE(hosts_[2]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); } +TEST_F(OutlierDetectorImplTest, MaxEjectionPercentageSingleHost) { + // Single host should not be ejected with a max ejection percent <100% . + const std::string yaml = R"EOF( +max_ejection_percent: 90 +max_ejection_time_jitter: 13s + )EOF"; + envoy::config::cluster::v3::OutlierDetection outlier_detection_; + TestUtility::loadFromYaml(yaml, outlier_detection_); + EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); + + addHosts({"tcp://127.0.0.2:80"}); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + std::shared_ptr detector(DetectorImpl::create( + cluster_, outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_, random_)); + + loadRq(hosts_[0], 5, 500); + EXPECT_FALSE(hosts_[0]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); +} + TEST_F(OutlierDetectorImplTest, Consecutive_5xxAlreadyEjected) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -1820,6 +1859,7 @@ TEST_F(OutlierDetectorImplTest, EjectTimeBackoff) { // Setup base ejection time to 10 secs. ON_CALL(runtime_.snapshot_, getInteger(BaseEjectionTimeMsRuntime, _)) .WillByDefault(Return(10000UL)); + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -1992,6 +2032,7 @@ TEST_F(OutlierDetectorImplTest, MaxEjectTime) { // Setup max ejection time to 30 secs. ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionTimeMsRuntime, _)) .WillByDefault(Return(300000UL)); + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -2085,6 +2126,7 @@ TEST_F(OutlierDetectorImplTest, MaxEjectTimeNotAlligned) { .WillByDefault(Return(10000UL)); ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionTimeMsRuntime, _)) .WillByDefault(Return(305000UL)); + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); @@ -2207,6 +2249,7 @@ TEST_F(OutlierDetectorImplTest, EjectionTimeJitterIsInRange) { // Setup max_ejection_time_jitter time to 10 secs. ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionTimeJitterMsRuntime, _)) .WillByDefault(Return(10000UL)); + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); // Add host. EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); @@ -2235,6 +2278,7 @@ TEST_F(OutlierDetectorImplTest, EjectionTimeJitterIsInRange) { // Test verifies that jitter is 0 when the // max_ejection_time_jitter configuration is absent. TEST_F(OutlierDetectorImplTest, EjectionTimeJitterIsZeroWhenNotConfigured) { + ON_CALL(runtime_.snapshot_, getInteger(MaxEjectionPercentRuntime, _)).WillByDefault(Return(100)); // Add host with empty configurations. Max_eject_time_jitter will // default to 0. EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc index 56cd3ccc3d73..2c2d80bd1c9a 100644 --- a/test/common/upstream/subset_lb_test.cc +++ b/test/common/upstream/subset_lb_test.cc @@ -221,23 +221,28 @@ class SubsetLoadBalancerTest : public Event::TestUsingSimulatedTime, void configureWeightedHostSet(const HostURLMetadataMap& first_locality_host_metadata, const HostURLMetadataMap& second_locality_host_metadata, MockHostSet& host_set, LocalityWeights locality_weights) { - HostVector first_locality; HostVector all_hosts; + HostVector first_locality_hosts; + envoy::config::core::v3::Locality first_locality; + first_locality.set_zone("0"); for (const auto& it : first_locality_host_metadata) { - auto host = makeHost(it.first, it.second); - first_locality.emplace_back(host); + auto host = makeHost(it.first, it.second, first_locality); + first_locality_hosts.emplace_back(host); all_hosts.emplace_back(host); } - HostVector second_locality; + envoy::config::core::v3::Locality second_locality; + second_locality.set_zone("1"); + HostVector second_locality_hosts; for (const auto& it : second_locality_host_metadata) { - auto host = makeHost(it.first, it.second); - second_locality.emplace_back(host); + auto host = makeHost(it.first, it.second, second_locality); + second_locality_hosts.emplace_back(host); all_hosts.emplace_back(host); } host_set.hosts_ = all_hosts; - host_set.hosts_per_locality_ = makeHostsPerLocality({first_locality, second_locality}); + host_set.hosts_per_locality_ = + makeHostsPerLocality({first_locality_hosts, second_locality_hosts}); host_set.healthy_hosts_ = host_set.hosts_; host_set.healthy_hosts_per_locality_ = host_set.hosts_per_locality_; host_set.locality_weights_ = std::make_shared(locality_weights); @@ -286,12 +291,22 @@ class SubsetLoadBalancerTest : public Event::TestUsingSimulatedTime, const std::vector& local_host_metadata_per_locality) { EXPECT_CALL(subset_info_, isEnabled()).WillRepeatedly(Return(true)); + std::vector> localities; + for (uint32_t i = 0; i < 10; ++i) { + envoy::config::core::v3::Locality locality; + locality.set_zone(std::to_string(i)); + localities.emplace_back(std::make_shared(locality)); + } + ASSERT(host_metadata_per_locality.size() <= localities.size()); + ASSERT(local_host_metadata_per_locality.size() <= localities.size()); + HostVector hosts; std::vector hosts_per_locality; - for (const auto& host_metadata : host_metadata_per_locality) { + for (uint32_t i = 0; i < host_metadata_per_locality.size(); ++i) { + const auto& host_metadata = host_metadata_per_locality[i]; HostVector locality_hosts; for (const auto& host_entry : host_metadata) { - HostSharedPtr host = makeHost(host_entry.first, host_entry.second); + HostSharedPtr host = makeHost(host_entry.first, host_entry.second, *localities[i]); hosts.emplace_back(host); locality_hosts.emplace_back(host); } @@ -306,10 +321,11 @@ class SubsetLoadBalancerTest : public Event::TestUsingSimulatedTime, local_hosts_ = std::make_shared(); std::vector local_hosts_per_locality_vector; - for (const auto& local_host_metadata : local_host_metadata_per_locality) { + for (uint32_t i = 0; i < local_host_metadata_per_locality.size(); ++i) { + const auto& local_host_metadata = local_host_metadata_per_locality[i]; HostVector local_locality_hosts; for (const auto& host_entry : local_host_metadata) { - HostSharedPtr host = makeHost(host_entry.first, host_entry.second); + HostSharedPtr host = makeHost(host_entry.first, host_entry.second, *localities[i]); local_hosts_->emplace_back(host); local_locality_hosts.emplace_back(host); } @@ -344,6 +360,18 @@ class SubsetLoadBalancerTest : public Event::TestUsingSimulatedTime, return makeTestHost(info_, url, m, simTime()); } + + HostSharedPtr makeHost(const std::string& url, const HostMetadata& metadata, + const envoy::config::core::v3::Locality& locality) { + envoy::config::core::v3::Metadata m; + for (const auto& m_it : metadata) { + Config::Metadata::mutableMetadataValue(m, Config::MetadataFilters::get().ENVOY_LB, m_it.first) + .set_string_value(m_it.second); + } + + return makeTestHost(info_, url, m, locality, simTime()); + } + HostSharedPtr makeHost(const std::string& url, const HostListMetadata& metadata) { envoy::config::core::v3::Metadata m; for (const auto& m_it : metadata) { @@ -1666,11 +1694,14 @@ TEST_P(SubsetLoadBalancerTest, ZoneAwareFallbackAfterUpdate) { EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); - modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}})}, {host_set_.hosts_[0]}, - absl::optional(0)); + envoy::config::core::v3::Locality local_locality; + local_locality.set_zone("0"); + + modifyHosts({makeHost("tcp://127.0.0.1:8000", {{"version", "1.0"}}, local_locality)}, + {host_set_.hosts_[0]}, absl::optional(0)); - modifyLocalHosts({makeHost("tcp://127.0.0.1:9000", {{"version", "1.0"}})}, {local_hosts_->at(0)}, - 0); + modifyLocalHosts({makeHost("tcp://127.0.0.1:9000", {{"version", "1.0"}}, local_locality)}, + {local_hosts_->at(0)}, 0); EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); @@ -1793,11 +1824,14 @@ TEST_P(SubsetLoadBalancerTest, ZoneAwareFallbackDefaultSubsetAfterUpdate) { EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); - modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "default"}})}, {host_set_.hosts_[1]}, - absl::optional(0)); + envoy::config::core::v3::Locality local_locality; + local_locality.set_zone("0"); + + modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "default"}}, local_locality)}, + {host_set_.hosts_[1]}, absl::optional(0)); modifyLocalHosts({local_hosts_->at(1)}, - {makeHost("tcp://127.0.0.1:9001", {{"version", "default"}})}, 0); + {makeHost("tcp://127.0.0.1:9001", {{"version", "default"}}, local_locality)}, 0); EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(nullptr)); @@ -1916,11 +1950,14 @@ TEST_P(SubsetLoadBalancerTest, ZoneAwareBalancesSubsetsAfterUpdate) { EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2)); EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); - modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "1.1"}})}, {host_set_.hosts_[1]}, - absl::optional(0)); + envoy::config::core::v3::Locality local_locality; + local_locality.set_zone("0"); + + modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "1.1"}}, local_locality)}, + {host_set_.hosts_[1]}, absl::optional(0)); - modifyLocalHosts({local_hosts_->at(1)}, {makeHost("tcp://127.0.0.1:9001", {{"version", "1.1"}})}, - 0); + modifyLocalHosts({local_hosts_->at(1)}, + {makeHost("tcp://127.0.0.1:9001", {{"version", "1.1"}}, local_locality)}, 0); EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(100)); EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 17b9f0bfb5fa..73f82b7e6e5f 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -4873,6 +4873,7 @@ class HostsWithLocalityImpl : public Event::TestUsingSimulatedTime, public testi // Validate HostsPerLocalityImpl constructors. TEST_F(HostsWithLocalityImpl, Cons) { + { const HostsPerLocalityImpl hosts_per_locality; EXPECT_FALSE(hosts_per_locality.hasLocalLocality()); @@ -4880,8 +4881,12 @@ TEST_F(HostsWithLocalityImpl, Cons) { } MockClusterMockPrioritySet cluster; - HostSharedPtr host_0 = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), 1); - HostSharedPtr host_1 = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), 1); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostSharedPtr host_0 = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), zone_a, 1); + HostSharedPtr host_1 = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), zone_b, 1); { std::vector locality_hosts = {{host_0}, {host_1}}; @@ -4902,8 +4907,12 @@ TEST_F(HostsWithLocalityImpl, Cons) { TEST_F(HostsWithLocalityImpl, Filter) { MockClusterMockPrioritySet cluster; - HostSharedPtr host_0 = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), 1); - HostSharedPtr host_1 = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), 1); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostSharedPtr host_0 = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), zone_a, 1); + HostSharedPtr host_1 = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), zone_b, 1); { std::vector locality_hosts = {{host_0}, {host_1}}; @@ -4933,12 +4942,6 @@ class HostSetImplLocalityTest : public Event::TestUsingSimulatedTime, public tes LocalityWeightsConstSharedPtr locality_weights_; HostSetImpl host_set_{0, false, kDefaultOverProvisioningFactor}; std::shared_ptr info_{new NiceMock()}; - HostVector hosts_{makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:83", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:84", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:85", simTime())}; }; // When no locality weights belong to the host set, there's an empty pick. @@ -4949,32 +4952,52 @@ TEST_F(HostSetImplLocalityTest, Empty) { // When no hosts are healthy we should fail to select a locality TEST_F(HostSetImplLocalityTest, AllUnhealthy) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + HostVector hosts{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}; + HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{hosts_[0]}, {hosts_[1]}, {hosts_[2]}}); + makeHostsPerLocality({{hosts[0]}, {hosts[1]}, {hosts[2]}}); LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 1, 1}}; - auto hosts = makeHostsFromHostsPerLocality(hosts_per_locality); - host_set_.updateHosts(updateHostsParams(hosts, hosts_per_locality), locality_weights, {}, {}, - absl::nullopt); + auto hosts_const_shared = std::make_shared(hosts); + host_set_.updateHosts(updateHostsParams(hosts_const_shared, hosts_per_locality), locality_weights, + {}, {}, absl::nullopt); EXPECT_FALSE(host_set_.chooseHealthyLocality().has_value()); } // When a locality has endpoints that have not yet been warmed, weight calculation should ignore // these hosts. TEST_F(HostSetImplLocalityTest, NotWarmedHostsLocality) { - // We have two localities with 3 hosts in L1, 2 hosts in L2. Two of the hosts in L1 are not + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostVector hosts{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_b)}; + + // We have two localities with 3 hosts in A, 2 hosts in B. Two of the hosts in A are not // warmed yet, so even though they are unhealthy we should not adjust the locality weight. HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{hosts_[0], hosts_[1], hosts_[2]}, {hosts_[3], hosts_[4]}}); + makeHostsPerLocality({{hosts[0], hosts[1], hosts[2]}, {hosts[3], hosts[4]}}); LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 1}}; - auto hosts = makeHostsFromHostsPerLocality(hosts_per_locality); + auto hosts_const_shared = std::make_shared(hosts); HostsPerLocalitySharedPtr healthy_hosts_per_locality = - makeHostsPerLocality({{hosts_[0]}, {hosts_[3], hosts_[4]}}); + makeHostsPerLocality({{hosts[0]}, {hosts[3], hosts[4]}}); HostsPerLocalitySharedPtr excluded_hosts_per_locality = - makeHostsPerLocality({{hosts_[1], hosts_[2]}, {}}); + makeHostsPerLocality({{hosts[1], hosts[2]}, {}}); host_set_.updateHosts( HostSetImpl::updateHostsParams( - hosts, hosts_per_locality, + hosts_const_shared, hosts_per_locality, makeHostsFromHostsPerLocality(healthy_hosts_per_locality), healthy_hosts_per_locality, std::make_shared(), HostsPerLocalityImpl::empty(), @@ -4990,12 +5013,18 @@ TEST_F(HostSetImplLocalityTest, NotWarmedHostsLocality) { // When a locality has zero hosts, it should be treated as if it has zero healthy. TEST_F(HostSetImplLocalityTest, EmptyLocality) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + HostVector hosts{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_a)}; + HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{hosts_[0], hosts_[1], hosts_[2]}, {}}); + makeHostsPerLocality({{hosts[0], hosts[1], hosts[2]}, {}}); LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 1}}; - auto hosts = makeHostsFromHostsPerLocality(hosts_per_locality); - host_set_.updateHosts(updateHostsParams(hosts, hosts_per_locality, - std::make_shared(*hosts), + auto hosts_const_shared = std::make_shared(hosts); + host_set_.updateHosts(updateHostsParams(hosts_const_shared, hosts_per_locality, + std::make_shared(hosts), hosts_per_locality), locality_weights, {}, {}, absl::nullopt); // Verify that we are not RRing between localities. @@ -5005,11 +5034,18 @@ TEST_F(HostSetImplLocalityTest, EmptyLocality) { // When all locality weights are zero we should fail to select a locality. TEST_F(HostSetImplLocalityTest, AllZeroWeights) { - HostsPerLocalitySharedPtr hosts_per_locality = makeHostsPerLocality({{hosts_[0]}, {hosts_[1]}}); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostVector hosts{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}; + + HostsPerLocalitySharedPtr hosts_per_locality = makeHostsPerLocality({{hosts[0]}, {hosts[1]}}); LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{0, 0}}; - auto hosts = makeHostsFromHostsPerLocality(hosts_per_locality); - host_set_.updateHosts(updateHostsParams(hosts, hosts_per_locality, - std::make_shared(*hosts), + auto hosts_const_shared = std::make_shared(hosts); + host_set_.updateHosts(updateHostsParams(hosts_const_shared, hosts_per_locality, + std::make_shared(hosts), hosts_per_locality), locality_weights, {}, {}); EXPECT_FALSE(host_set_.chooseHealthyLocality().has_value()); @@ -5017,12 +5053,22 @@ TEST_F(HostSetImplLocalityTest, AllZeroWeights) { // When all locality weights are the same we have unweighted RR behavior. TEST_F(HostSetImplLocalityTest, Unweighted) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + HostVector hosts{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}; + HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{hosts_[0]}, {hosts_[1]}, {hosts_[2]}}); + makeHostsPerLocality({{hosts[0]}, {hosts[1]}, {hosts[2]}}); LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 1, 1}}; - auto hosts = makeHostsFromHostsPerLocality(hosts_per_locality); - host_set_.updateHosts(updateHostsParams(hosts, hosts_per_locality, - std::make_shared(*hosts), + auto hosts_const_shared = std::make_shared(hosts); + host_set_.updateHosts(updateHostsParams(hosts_const_shared, hosts_per_locality, + std::make_shared(hosts), hosts_per_locality), locality_weights, {}, {}, absl::nullopt); EXPECT_EQ(0, host_set_.chooseHealthyLocality().value()); @@ -5035,11 +5081,18 @@ TEST_F(HostSetImplLocalityTest, Unweighted) { // When locality weights differ, we have weighted RR behavior. TEST_F(HostSetImplLocalityTest, Weighted) { - HostsPerLocalitySharedPtr hosts_per_locality = makeHostsPerLocality({{hosts_[0]}, {hosts_[1]}}); + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostVector hosts{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}; + + HostsPerLocalitySharedPtr hosts_per_locality = makeHostsPerLocality({{hosts[0]}, {hosts[1]}}); LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 2}}; - auto hosts = makeHostsFromHostsPerLocality(hosts_per_locality); - host_set_.updateHosts(updateHostsParams(hosts, hosts_per_locality, - std::make_shared(*hosts), + auto hosts_const_shared = std::make_shared(hosts); + host_set_.updateHosts(updateHostsParams(hosts_const_shared, hosts_per_locality, + std::make_shared(hosts), hosts_per_locality), locality_weights, {}, {}, absl::nullopt); EXPECT_EQ(1, host_set_.chooseHealthyLocality().value()); @@ -5052,12 +5105,22 @@ TEST_F(HostSetImplLocalityTest, Weighted) { // Localities with no weight assignment are never picked. TEST_F(HostSetImplLocalityTest, MissingWeight) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + HostVector hosts{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}; + HostsPerLocalitySharedPtr hosts_per_locality = - makeHostsPerLocality({{hosts_[0]}, {hosts_[1]}, {hosts_[2]}}); + makeHostsPerLocality({{hosts[0]}, {hosts[1]}, {hosts[2]}}); LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 0, 1}}; - auto hosts = makeHostsFromHostsPerLocality(hosts_per_locality); - host_set_.updateHosts(updateHostsParams(hosts, hosts_per_locality, - std::make_shared(*hosts), + auto hosts_const_shared = std::make_shared(hosts); + host_set_.updateHosts(updateHostsParams(hosts_const_shared, hosts_per_locality, + std::make_shared(hosts), hosts_per_locality), locality_weights, {}, {}, absl::nullopt); EXPECT_EQ(0, host_set_.chooseHealthyLocality().value()); @@ -5070,16 +5133,27 @@ TEST_F(HostSetImplLocalityTest, MissingWeight) { // Gentle failover between localities as health diminishes. TEST_F(HostSetImplLocalityTest, UnhealthyFailover) { - const auto setHealthyHostCount = [this](uint32_t host_count) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostVector hosts{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:85", simTime(), zone_b)}; + + const auto setHealthyHostCount = [this, hosts](uint32_t host_count) { LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 2}}; - HostsPerLocalitySharedPtr hosts_per_locality = makeHostsPerLocality( - {{hosts_[0], hosts_[1], hosts_[2], hosts_[3], hosts_[4]}, {hosts_[5]}}); + HostsPerLocalitySharedPtr hosts_per_locality = + makeHostsPerLocality({{hosts[0], hosts[1], hosts[2], hosts[3], hosts[4]}, {hosts[5]}}); HostVector healthy_hosts; for (uint32_t i = 0; i < host_count; ++i) { - healthy_hosts.emplace_back(hosts_[i]); + healthy_hosts.emplace_back(hosts[i]); } HostsPerLocalitySharedPtr healthy_hosts_per_locality = - makeHostsPerLocality({healthy_hosts, {hosts_[5]}}); + makeHostsPerLocality({healthy_hosts, {hosts[5]}}); auto hosts = makeHostsFromHostsPerLocality(hosts_per_locality); host_set_.updateHosts(updateHostsParams(hosts, hosts_per_locality, @@ -5121,9 +5195,13 @@ TEST(OverProvisioningFactorTest, LocalityPickChanges) { HostSetImpl host_set(0, false, overprovisioning_factor); std::shared_ptr cluster_info{new NiceMock()}; auto time_source = std::make_unique>(); - HostVector hosts{makeTestHost(cluster_info, "tcp://127.0.0.1:80", *time_source), - makeTestHost(cluster_info, "tcp://127.0.0.1:81", *time_source), - makeTestHost(cluster_info, "tcp://127.0.0.1:82", *time_source)}; + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostVector hosts{makeTestHost(cluster_info, "tcp://127.0.0.1:80", *time_source, zone_a), + makeTestHost(cluster_info, "tcp://127.0.0.1:81", *time_source, zone_a), + makeTestHost(cluster_info, "tcp://127.0.0.1:82", *time_source, zone_b)}; LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 1}}; HostsPerLocalitySharedPtr hosts_per_locality = makeHostsPerLocality({{hosts[0], hosts[1]}, {hosts[2]}}); @@ -5163,11 +5241,15 @@ TEST(OverProvisioningFactorTest, LocalityPickChanges) { TEST(HostPartitionTest, PartitionHosts) { std::shared_ptr info{new NiceMock()}; auto time_source = std::make_unique>(); - HostVector hosts{makeTestHost(info, "tcp://127.0.0.1:80", *time_source), - makeTestHost(info, "tcp://127.0.0.1:81", *time_source), - makeTestHost(info, "tcp://127.0.0.1:82", *time_source), - makeTestHost(info, "tcp://127.0.0.1:83", *time_source), - makeTestHost(info, "tcp://127.0.0.1:84", *time_source)}; + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + HostVector hosts{makeTestHost(info, "tcp://127.0.0.1:80", *time_source, zone_a), + makeTestHost(info, "tcp://127.0.0.1:81", *time_source, zone_a), + makeTestHost(info, "tcp://127.0.0.1:82", *time_source, zone_b), + makeTestHost(info, "tcp://127.0.0.1:83", *time_source, zone_b), + makeTestHost(info, "tcp://127.0.0.1:84", *time_source, zone_b)}; hosts[0]->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); hosts[1]->healthFlagSet(Host::HealthFlag::DEGRADED_ACTIVE_HC); @@ -5261,6 +5343,87 @@ TEST_F(ClusterInfoImplTest, FilterChain) { cluster->info()->createFilterChain(manager); } +class PriorityStateManagerTest : public Event::TestUsingSimulatedTime, + public testing::Test, + public UpstreamImplTestBase {}; + +TEST_F(PriorityStateManagerTest, LocalityClusterUpdate) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + + server_context_.local_info_.node_.mutable_locality()->CopyFrom(zone_a); + + // Construct cluster to use for updates + const std::string cluster_yaml = R"EOF( + name: staticcluster + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + endpoints: + - locality: + zone: B + lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 80 + )EOF"; + envoy::config::cluster::v3::Cluster cluster_config = parseClusterFromV3Yaml(cluster_yaml); + + Envoy::Upstream::ClusterFactoryContextImpl factory_context( + server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, + false); + StaticClusterImpl cluster(cluster_config, factory_context); + cluster.initialize([] {}); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); + + // Make priority state manager and fill it with the initial state of the cluster and the added + // hosts + PriorityStateManager priority_state_manager(cluster, server_context_.local_info_, nullptr); + + auto current_hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + HostVector hosts_added{makeTestHost(cluster.info(), "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(cluster.info(), "tcp://127.0.0.1:82", simTime(), zone_a)}; + + envoy::config::endpoint::v3::LocalityLbEndpoints zone_a_endpoints; + zone_a_endpoints.mutable_locality()->CopyFrom(zone_a); + envoy::config::endpoint::v3::LocalityLbEndpoints zone_b_endpoints = + cluster_config.load_assignment().endpoints()[0]; + + priority_state_manager.initializePriorityFor(zone_b_endpoints); + priority_state_manager.registerHostForPriority(hosts_added[0], zone_b_endpoints); + priority_state_manager.registerHostForPriority( + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0], zone_b_endpoints); + + priority_state_manager.initializePriorityFor(zone_a_endpoints); + priority_state_manager.registerHostForPriority(hosts_added[1], zone_a_endpoints); + + // Update the cluster's priority set with the added hosts + priority_state_manager.updateClusterPrioritySet( + 0, std::move(priority_state_manager.priorityState()[0].first), hosts_added, absl::nullopt, + absl::nullopt); + + // Check that the P=0 host set has the added hosts, and the expected HostsPerLocality state + const HostVector& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + const HostsPerLocality& hosts_per_locality = + cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality(); + + EXPECT_EQ(3UL, hosts.size()); + EXPECT_EQ(true, hosts_per_locality.hasLocalLocality()); + EXPECT_EQ(2UL, hosts_per_locality.get().size()); + + EXPECT_EQ(1UL, hosts_per_locality.get()[0].size()); + EXPECT_EQ(zone_a, hosts_per_locality.get()[0][0]->locality()); + + EXPECT_EQ(2UL, hosts_per_locality.get()[1].size()); + EXPECT_EQ(zone_b, hosts_per_locality.get()[1][0]->locality()); + EXPECT_EQ(zone_b, hosts_per_locality.get()[1][1]->locality()); +} + } // namespace } // namespace Upstream } // namespace Envoy diff --git a/test/common/upstream/utility.h b/test/common/upstream/utility.h index 0458f90a15db..29ec75e9c42b 100644 --- a/test/common/upstream/utility.h +++ b/test/common/upstream/utility.h @@ -115,6 +115,17 @@ inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std:: status, time_source); } +inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, + TimeSource& time_source, + envoy::config::core::v3::Locality locality, uint32_t weight = 1, + uint32_t priority = 0, + Host::HealthStatus status = Host::HealthStatus::UNKNOWN) { + return std::make_shared( + cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, locality, + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), priority, + status, time_source); +} + inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, const envoy::config::core::v3::Metadata& metadata, TimeSource& time_source, uint32_t weight = 1) { @@ -126,6 +137,17 @@ inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std:: envoy::config::core::v3::UNKNOWN, time_source); } +inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, + const envoy::config::core::v3::Metadata& metadata, + envoy::config::core::v3::Locality locality, + TimeSource& time_source, uint32_t weight = 1) { + return std::make_shared( + cluster, "", Network::Utility::resolveUrl(url), + std::make_shared(metadata), weight, locality, + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN, time_source); +} + inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config, diff --git a/test/extensions/clusters/aggregate/cluster_integration_test.cc b/test/extensions/clusters/aggregate/cluster_integration_test.cc index aad8f587b5a3..1ada724a520b 100644 --- a/test/extensions/clusters/aggregate/cluster_integration_test.cc +++ b/test/extensions/clusters/aggregate/cluster_integration_test.cc @@ -122,10 +122,13 @@ const std::string& config() { Platform::null_device_path)); } -class AggregateIntegrationTest : public testing::TestWithParam, - public HttpIntegrationTest { +class AggregateIntegrationTest + : public testing::TestWithParam>, + public HttpIntegrationTest { public: - AggregateIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam(), config()) { + AggregateIntegrationTest() + : HttpIntegrationTest(Http::CodecType::HTTP1, std::get<0>(GetParam()), config()), + deferred_cluster_creation_(std::get<1>(GetParam())) { use_lds_ = false; } @@ -137,16 +140,20 @@ class AggregateIntegrationTest : public testing::TestWithParamset_enable_deferred_cluster_creation( + deferred_cluster_creation_); + }); HttpIntegrationTest::initialize(); addFakeUpstream(Http::CodecType::HTTP2); addFakeUpstream(Http::CodecType::HTTP2); cluster1_ = ConfigHelper::buildStaticCluster( FirstClusterName, fake_upstreams_[FirstUpstreamIndex]->localAddress()->ip()->port(), - Network::Test::getLoopbackAddressString(GetParam())); + Network::Test::getLoopbackAddressString(version_)); cluster2_ = ConfigHelper::buildStaticCluster( SecondClusterName, fake_upstreams_[SecondUpstreamIndex]->localAddress()->ip()->port(), - Network::Test::getLoopbackAddressString(GetParam())); + Network::Test::getLoopbackAddressString(version_)); // Let Envoy establish its connection to the CDS server. acceptXdsConnection(); @@ -173,12 +180,14 @@ class AggregateIntegrationTest : public testing::TestWithParamstartGrpcStream(); } + const bool deferred_cluster_creation_; envoy::config::cluster::v3::Cluster cluster1_; envoy::config::cluster::v3::Cluster cluster2_; }; -INSTANTIATE_TEST_SUITE_P(IpVersions, AggregateIntegrationTest, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); +INSTANTIATE_TEST_SUITE_P( + IpVersions, AggregateIntegrationTest, + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::Bool())); TEST_P(AggregateIntegrationTest, ClusterUpDownUp) { // Calls our initialize(), which includes establishing a listener, route, and cluster. diff --git a/test/extensions/clusters/aggregate/cluster_update_test.cc b/test/extensions/clusters/aggregate/cluster_update_test.cc index f82aa0965eb4..08a1d0d47af9 100644 --- a/test/extensions/clusters/aggregate/cluster_update_test.cc +++ b/test/extensions/clusters/aggregate/cluster_update_test.cc @@ -31,7 +31,8 @@ envoy::config::bootstrap::v3::Bootstrap parseBootstrapFromV2Yaml(const std::stri return bootstrap; } -class AggregateClusterUpdateTest : public Event::TestUsingSimulatedTime, public testing::Test { +class AggregateClusterUpdateTest : public Event::TestUsingSimulatedTime, + public testing::TestWithParam { public: AggregateClusterUpdateTest() : http_context_(stats_store_.symbolTable()), grpc_context_(stats_store_.symbolTable()), @@ -39,6 +40,8 @@ class AggregateClusterUpdateTest : public Event::TestUsingSimulatedTime, public void initialize(const std::string& yaml_config) { auto bootstrap = parseBootstrapFromV2Yaml(yaml_config); + const bool use_deferred_cluster = GetParam(); + bootstrap.mutable_cluster_manager()->set_enable_deferred_cluster_creation(use_deferred_cluster); cluster_manager_ = std::make_unique( bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, validation_context_, @@ -77,12 +80,14 @@ class AggregateClusterUpdateTest : public Event::TestUsingSimulatedTime, public )EOF"; }; -TEST_F(AggregateClusterUpdateTest, NoHealthyUpstream) { +INSTANTIATE_TEST_SUITE_P(DeferredClusters, AggregateClusterUpdateTest, testing::Bool()); + +TEST_P(AggregateClusterUpdateTest, NoHealthyUpstream) { initialize(default_yaml_config_); EXPECT_EQ(nullptr, cluster_->loadBalancer().chooseHost(nullptr)); } -TEST_F(AggregateClusterUpdateTest, BasicFlow) { +TEST_P(AggregateClusterUpdateTest, BasicFlow) { initialize(default_yaml_config_); std::unique_ptr callbacks( @@ -132,7 +137,7 @@ TEST_F(AggregateClusterUpdateTest, BasicFlow) { EXPECT_EQ("127.0.0.1:11001", host->address()->asString()); } -TEST_F(AggregateClusterUpdateTest, LoadBalancingTest) { +TEST_P(AggregateClusterUpdateTest, LoadBalancingTest) { initialize(default_yaml_config_); EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(Upstream::defaultStaticCluster("primary"), "")); auto primary = cluster_manager_->getThreadLocalCluster("primary"); @@ -242,7 +247,7 @@ TEST_F(AggregateClusterUpdateTest, LoadBalancingTest) { } } -TEST_F(AggregateClusterUpdateTest, InitializeAggregateClusterAfterOtherClusters) { +TEST_P(AggregateClusterUpdateTest, InitializeAggregateClusterAfterOtherClusters) { const std::string config = R"EOF( static_resources: clusters: diff --git a/test/extensions/clusters/eds/BUILD b/test/extensions/clusters/eds/BUILD index 823ae6b55d79..d9a4b138500a 100644 --- a/test/extensions/clusters/eds/BUILD +++ b/test/extensions/clusters/eds/BUILD @@ -22,6 +22,7 @@ envoy_cc_test( "//test/common/stats:stat_test_utility_lib", "//test/common/upstream:utility_lib", "//test/integration/load_balancers:custom_lb_policy", + "//test/mocks/config:eds_resources_cache_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", @@ -31,6 +32,7 @@ envoy_cc_test( "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:health_checker_mocks", "//test/test_common:simulated_time_system_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/clusters/eds/eds_speed_test.cc b/test/extensions/clusters/eds/eds_speed_test.cc index d30b1e1e1129..c5b10480216f 100644 --- a/test/extensions/clusters/eds/eds_speed_test.cc +++ b/test/extensions/clusters/eds/eds_speed_test.cc @@ -52,23 +52,25 @@ class EdsSpeedTest { auto backoff_strategy = std::make_unique( Config::SubscriptionFactory::RetryInitialDelayMs, Config::SubscriptionFactory::RetryMaxDelayMs, random_); + Config::GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/server_context_.dispatcher_, + /*service_method_=*/ + *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "envoy.service.endpoint.v3.EndpointDiscoveryService.StreamEndpoints"), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/{}, + /*scope_=*/scope_, + /*config_validators_=*/std::move(config_validators_), + /*xds_resources_delegate_=*/Config::XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/Config::XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; if (use_unified_mux_) { - grpc_mux_.reset(new Config::XdsMux::GrpcMuxSotw( - std::unique_ptr(async_client_), server_context_.dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.endpoint.v3.EndpointDiscoveryService.StreamEndpoints"), - scope_, {}, local_info_, true, std::move(config_validators_), std::move(backoff_strategy), - /*xds_config_tracker=*/Config::XdsConfigTrackerOptRef())); + grpc_mux_.reset(new Config::XdsMux::GrpcMuxSotw(grpc_mux_context, true)); } else { - grpc_mux_.reset(new Config::GrpcMuxImpl( - local_info_, std::unique_ptr(async_client_), - server_context_.dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.endpoint.v3.EndpointDiscoveryService.StreamEndpoints"), - scope_, {}, true, std::move(config_validators_), std::move(backoff_strategy), - /*xds_config_tracker=*/Config::XdsConfigTrackerOptRef(), - /*xds_resources_delegate=*/Config::XdsResourcesDelegateOptRef(), - /*target_xds_authority=*/"")); + grpc_mux_.reset(new Config::GrpcMuxImpl(grpc_mux_context, true)); } resetCluster(R"EOF( name: name diff --git a/test/extensions/clusters/eds/eds_test.cc b/test/extensions/clusters/eds/eds_test.cc index 917de1fb1de6..f7f392a79dfa 100644 --- a/test/extensions/clusters/eds/eds_test.cc +++ b/test/extensions/clusters/eds/eds_test.cc @@ -14,6 +14,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/common/upstream/utility.h" +#include "test/mocks/config/eds_resources_cache.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" @@ -23,12 +24,16 @@ #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/health_checker.h" #include "test/test_common/simulated_time_system.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; +using testing::DoAll; +using testing::Return; +using testing::SaveArg; namespace Envoy { namespace Upstream { @@ -1817,6 +1822,55 @@ TEST_F(EdsTest, EndpointLocality) { EXPECT_EQ(nullptr, cluster_->prioritySet().hostSetsPerPriority()[0]->localityWeights()); } +TEST_F(EdsTest, EndpointCombineDuplicateLocalities) { + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + + auto* endpoints1 = cluster_load_assignment.add_endpoints(); + auto* locality1 = endpoints1->mutable_locality(); + locality1->set_region("oceania"); + locality1->set_zone("hello"); + locality1->set_sub_zone("world"); + + auto* endpoints2 = cluster_load_assignment.add_endpoints(); + auto* locality2 = endpoints2->mutable_locality(); + locality2->set_region("oceania"); + locality2->set_zone("hello"); + locality2->set_sub_zone("world"); + + { + auto* endpoint_address = endpoints1->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + endpoint_address->set_address("1.2.3.4"); + endpoint_address->set_port_value(80); + } + { + auto* endpoint_address = endpoints2->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + endpoint_address->set_address("2.3.4.5"); + endpoint_address->set_port_value(80); + } + + initialize(); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + EXPECT_TRUE(initialized_); + + auto& hosts = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 2); + for (int i = 0; i < 2; ++i) { + EXPECT_EQ(0, hosts[i]->priority()); + const auto& locality = hosts[i]->locality(); + EXPECT_EQ("oceania", locality.region()); + EXPECT_EQ("hello", locality.zone()); + EXPECT_EQ("world", locality.sub_zone()); + } + EXPECT_EQ(nullptr, cluster_->prioritySet().hostSetsPerPriority()[0]->localityWeights()); +} + // Validate that onConfigUpdate() updates the endpoint locality of an existing endpoint. TEST_F(EdsTest, EndpointLocalityUpdated) { envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; @@ -2698,9 +2752,7 @@ TEST_F(EdsAssignmentTimeoutTest, AssignmentLeaseExpired) { // Expect the timer to be enabled once. EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); - // Expect the timer to be disabled when stale assignments are removed. - EXPECT_CALL(*interval_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enabled()).Times(2); + EXPECT_CALL(*interval_timer_, enabled()); doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); { auto& hosts = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); @@ -2715,6 +2767,50 @@ TEST_F(EdsAssignmentTimeoutTest, AssignmentLeaseExpired) { } } +// Validates that assignment timeout is disabled when an update arrives. +TEST_F(EdsAssignmentTimeoutTest, AssignmentLeaseUpdateDisablesTimer) { + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + cluster_load_assignment.mutable_policy()->mutable_endpoint_stale_after()->MergeFrom( + Protobuf::util::TimeUtil::SecondsToDuration(1)); + + auto health_checker = std::make_shared(); + EXPECT_CALL(*health_checker, start()); + EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)).Times(2); + cluster_->setHealthChecker(health_checker); + + auto add_endpoint = [&cluster_load_assignment](int port) { + auto* endpoints = cluster_load_assignment.add_endpoints(); + + auto* socket_address = endpoints->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("1.2.3.4"); + socket_address->set_port_value(port); + }; + + // Add two endpoints to the cluster assignment. + add_endpoint(80); + add_endpoint(81); + + // Expect the timer to be enabled once. + EXPECT_CALL(*interval_timer_, enabled()); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); + { + auto& hosts = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 2); + } + + // Update the assignment, expect the old timer to be disabled. + cluster_load_assignment.mutable_endpoints(0)->mutable_load_balancing_weight()->set_value(31); + EXPECT_CALL(*interval_timer_, enabled()); + EXPECT_CALL(*interval_timer_, disableTimer()); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); + doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); +} + // Validate that onConfigUpdate() with a config that contains both LEDS config // source and explicit list of endpoints is rejected. TEST_F(EdsTest, OnConfigUpdateLedsAndEndpoints) { @@ -2738,6 +2834,398 @@ TEST_F(EdsTest, OnConfigUpdateLedsAndEndpoints) { "(resource: xdstp://foo/leds/collection) and a list of endpoints."); } +class EdsCachedAssignmentTest : public testing::Test { +public: + EdsCachedAssignmentTest() { + // TODO(adisuissa): setting the runtime guard is done because the runtime + // guard is false by default. The runtime environment should be removed + // once this guard is removed. + runtime_.mergeValues({{"envoy.restart_features.use_eds_cache_for_ads", "true"}}); + resetCluster(); + } + + void resetCluster() { + resetCluster(R"EOF( + name: name + connect_timeout: 0.25s + type: EDS + lb_policy: ROUND_ROBIN + eds_cluster_config: + service_name: fare + eds_config: + api_config_source: + api_type: REST + cluster_names: + - eds + refresh_delay: 1s + )EOF", + Cluster::InitializePhase::Secondary); + } + + void resetCluster(const std::string& yaml_config, Cluster::InitializePhase initialize_phase) { + server_context_.local_info_.node_.mutable_locality()->set_zone("us-east-1a"); + EXPECT_CALL(server_context_.dispatcher_, createTimer_(_)) + .WillOnce(Invoke([this](Event::TimerCb cb) { + timer_cb_pre_ = cb; + EXPECT_EQ(nullptr, interval_timer_pre_); + interval_timer_pre_ = new Event::MockTimer(); + return interval_timer_pre_; + })) + .WillRepeatedly(Invoke([](Event::TimerCb) { return new Event::MockTimer(); })); + + eds_cluster_ = parseClusterFromV3Yaml(yaml_config); + Envoy::Upstream::ClusterFactoryContextImpl factory_context( + server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, + false); + ON_CALL(server_context_.cluster_manager_, edsResourcesCache()) + .WillByDefault( + Invoke([this]() -> Config::EdsResourcesCacheOptRef { return eds_resources_cache_; })); + cluster_pre_ = std::make_shared(eds_cluster_, factory_context); + EXPECT_EQ(initialize_phase, cluster_pre_->initializePhase()); + eds_callbacks_pre_ = server_context_.cluster_manager_.subscription_factory_.callbacks_; + } + + void initialize() { + EXPECT_CALL(*server_context_.cluster_manager_.subscription_factory_.subscription_, start(_)); + cluster_pre_->initialize([this] { initialized_ = true; }); + } + + void doOnConfigUpdateVerifyNoThrowPre( + const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment) { + const auto decoded_resources = + TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + VERBOSE_EXPECT_NO_THROW(eds_callbacks_pre_->onConfigUpdate(decoded_resources.refvec_, "")); + } + + void doOnConfigUpdateVerifyNoThrowPost( + const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment) { + const auto decoded_resources = + TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); + VERBOSE_EXPECT_NO_THROW(eds_callbacks_post_->onConfigUpdate(decoded_resources.refvec_, "")); + } + // Emulates a CDS update that creates a new cluster object with the same name, + // that waits for EDS response. + void updateCluster() { + EXPECT_CALL(server_context_.dispatcher_, createTimer_(_)) + .WillOnce(Invoke([this](Event::TimerCb cb) { + timer_cb_post_ = cb; + EXPECT_EQ(nullptr, interval_timer_post_); + interval_timer_post_ = new Event::MockTimer(); + return interval_timer_post_; + })) + .WillRepeatedly(Invoke([](Event::TimerCb) { return new Event::MockTimer(); })); + + Envoy::Upstream::ClusterFactoryContextImpl factory_context( + server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, + false); + cluster_post_ = std::make_shared(eds_cluster_, factory_context); + // EXPECT_EQ(initialize_phase, cluster_post_->initializePhase()); + eds_callbacks_post_ = server_context_.cluster_manager_.subscription_factory_.callbacks_; + + EXPECT_CALL(*server_context_.cluster_manager_.subscription_factory_.subscription_, start(_)); + cluster_post_->initialize([this] { initialized_post_ = true; }); + } + + // Used for timeout emulation. + Event::MockTimer* interval_timer_pre_{nullptr}; + Event::TimerCb timer_cb_pre_; + Event::MockTimer* interval_timer_post_{nullptr}; + Event::TimerCb timer_cb_post_; + + NiceMock server_context_; + bool initialized_{}; + bool initialized_post_{}; + Stats::TestUtil::TestStore& stats_ = server_context_.store_; + NiceMock ssl_context_manager_; + envoy::config::cluster::v3::Cluster eds_cluster_; + NiceMock random_; + // TestScopedRuntime runtime_; + NiceMock validation_visitor_; + Config::MockEdsResourcesCache eds_resources_cache_; + TestScopedRuntime runtime_; + + // EDS caching works when a cluster update occurs (a CDS update arrives, a new + // cluster with the same name is created, but the EDS response doesn't + // arrive). We emulate this by having 2 EdsCluster instance cluster_pre_ for + // the initial cluster, and cluster_post_ for the one created after the cluster update. + EdsClusterImplSharedPtr cluster_pre_; + EdsClusterImplSharedPtr cluster_post_; + Config::SubscriptionCallbacks* eds_callbacks_pre_{}; + Config::SubscriptionCallbacks* eds_callbacks_post_{}; +}; + +// Validates that cached assignments are not used if an EDS update for a cluster arrives. +TEST_F(EdsCachedAssignmentTest, ClusterUpdateNotUsingCacheOnEdsUpdate) { + // Set an initial assignment. + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + auto* endpoints = cluster_load_assignment.add_endpoints(); + auto* endpoint = endpoints->add_lb_endpoints(); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address("1.2.3.4"); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value(80); + endpoint->mutable_load_balancing_weight()->set_value(10); + + initialize(); + // No call to the cache to fetch the assignment, as it is being delivered as expected. + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)).Times(0); + EXPECT_CALL(*interval_timer_pre_, enabled()); + doOnConfigUpdateVerifyNoThrowPre(cluster_load_assignment); + EXPECT_TRUE(initialized_); + { + const auto& hosts = cluster_pre_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 10); + } + + // Update the cluster, and send an updated assignment back. + // Still no call to the cache to fetch the assignment, as it is being delivered as expected. + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)).Times(0); + updateCluster(); + { + endpoint->mutable_load_balancing_weight()->set_value(11); + EXPECT_CALL(*interval_timer_post_, enabled()); + doOnConfigUpdateVerifyNoThrowPost(cluster_load_assignment); + + const auto& hosts = cluster_post_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 11); + } +} + +// Validates that cached assignments are used if no EDS update for a cluster arrives +// (i.e., EDS-Timeout). +TEST_F(EdsCachedAssignmentTest, UseCachedAssignmentOnWarmingFailure) { + // Set an initial assignment. + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + auto* endpoints = cluster_load_assignment.add_endpoints(); + auto* endpoint = endpoints->add_lb_endpoints(); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address("1.2.3.4"); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value(80); + endpoint->mutable_load_balancing_weight()->set_value(10); + + // Store in the cache an assignment with a different weight. + envoy::config::endpoint::v3::ClusterLoadAssignment cached_cluster_load_assignment; + cached_cluster_load_assignment.CopyFrom(cluster_load_assignment); + cached_cluster_load_assignment.mutable_endpoints(0) + ->mutable_lb_endpoints(0) + ->mutable_load_balancing_weight() + ->set_value(22); + + initialize(); + // No call to the cache to fetch the assignment, as it is being delivered as expected. + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)).Times(0); + EXPECT_CALL(*interval_timer_pre_, enabled()); + doOnConfigUpdateVerifyNoThrowPre(cluster_load_assignment); + EXPECT_TRUE(initialized_); + { + const auto& hosts = cluster_pre_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 10); + } + + // Update the cluster and emulate a warming failure, and validate + // that the resource is fetched from the cache. + updateCluster(); + { + EnvoyException dummy_ex("dummy exception"); + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)) + .WillOnce(Return(cached_cluster_load_assignment)); + eds_callbacks_post_->onConfigUpdateFailed( + Envoy::Config::ConfigUpdateFailureReason::FetchTimedout, &dummy_ex); + const auto& hosts = cluster_post_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 22); + } + // Removing the cluster on test d'tor will trigger removeCallback. + EXPECT_CALL(eds_resources_cache_, removeCallback("fare", _)); +} + +// Validates that no cached assignments are used if no EDS update for a cluster arrives. +// This test should be deleted once the enable_eds_cache runtime flag is removed. +TEST_F(EdsCachedAssignmentTest, UseCachedAssignmentOnWarmingFailureNoCache) { + // TODO(adisuissa): this test should be removed once the runtime guard is deprecated. + runtime_.mergeValues({{"envoy.restart_features.use_eds_cache_for_ads", "false"}}); + // Set an initial assignment. + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + auto* endpoints = cluster_load_assignment.add_endpoints(); + auto* endpoint = endpoints->add_lb_endpoints(); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address("1.2.3.4"); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value(80); + endpoint->mutable_load_balancing_weight()->set_value(10); + + // Store in the cache an assignment with a different weight. + envoy::config::endpoint::v3::ClusterLoadAssignment cached_cluster_load_assignment; + cached_cluster_load_assignment.CopyFrom(cluster_load_assignment); + cached_cluster_load_assignment.mutable_endpoints(0) + ->mutable_lb_endpoints(0) + ->mutable_load_balancing_weight() + ->set_value(22); + + initialize(); + // No call to the cache to fetch the assignment, as it is being delivered as expected. + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)).Times(0); + EXPECT_CALL(*interval_timer_pre_, enabled()); + doOnConfigUpdateVerifyNoThrowPre(cluster_load_assignment); + EXPECT_TRUE(initialized_); + { + const auto& hosts = cluster_pre_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 10); + } + + // Update the cluster and emulate a warming failure, and validate + // that the resource is not fetched from the cache because caching is disabled. + updateCluster(); + { + EnvoyException dummy_ex("dummy exception"); + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)).Times(0); + eds_callbacks_post_->onConfigUpdateFailed( + Envoy::Config::ConfigUpdateFailureReason::FetchTimedout, &dummy_ex); + const auto& hosts = cluster_post_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 0); + } +} + +// Validates that after using a cached assignment, and receiving an update for it, the +// updated assignment is used. +TEST_F(EdsCachedAssignmentTest, CachedAssignmentUpdate) { + // Set an initial assignment. + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + cluster_load_assignment.mutable_policy()->mutable_endpoint_stale_after()->MergeFrom( + Protobuf::util::TimeUtil::SecondsToDuration(1)); + auto* endpoints = cluster_load_assignment.add_endpoints(); + auto* endpoint = endpoints->add_lb_endpoints(); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address("1.2.3.4"); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value(80); + endpoint->mutable_load_balancing_weight()->set_value(10); + + // Store in the cache an assignment with a different weight. + envoy::config::endpoint::v3::ClusterLoadAssignment cached_cluster_load_assignment; + cached_cluster_load_assignment.CopyFrom(cluster_load_assignment); + cached_cluster_load_assignment.mutable_endpoints(0) + ->mutable_lb_endpoints(0) + ->mutable_load_balancing_weight() + ->set_value(22); + + initialize(); + // Expect the timer to be enabled once. + EXPECT_CALL(*interval_timer_pre_, enableTimer(std::chrono::milliseconds(1000), _)); + EXPECT_CALL(*interval_timer_pre_, enabled()); + EXPECT_CALL(eds_resources_cache_, setExpiryTimer("fare", std::chrono::milliseconds(1000))); + doOnConfigUpdateVerifyNoThrowPre(cluster_load_assignment); + EXPECT_TRUE(initialized_); + { + const auto& hosts = cluster_pre_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 10); + } + + Config::EdsResourceRemovalCallback* removal_cb = nullptr; + // Update the cluster and emulate a warming failure, and validate + // that the resource is fetched from the cache. + updateCluster(); + { + EnvoyException dummy_ex("dummy exception"); + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)) + .WillOnce(DoAll(SaveArg<1>(&removal_cb), Return(cached_cluster_load_assignment))); + eds_callbacks_post_->onConfigUpdateFailed( + Envoy::Config::ConfigUpdateFailureReason::FetchTimedout, &dummy_ex); + const auto& hosts = cluster_post_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 22); + } + + // Send a successful update to the cluster. + // No call for fetching the resource from the cache. + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)).Times(0); + { + endpoint->mutable_load_balancing_weight()->set_value(11); + EXPECT_CALL(eds_resources_cache_, removeCallback("fare", removal_cb)); + // Expect the timer to be enabled once. + EXPECT_CALL(*interval_timer_post_, enableTimer(std::chrono::milliseconds(1000), _)); + // Expect the timer to be disabled when stale assignments are removed. + // EXPECT_CALL(*interval_timer_, disableTimer()); + EXPECT_CALL(*interval_timer_post_, enabled()); + EXPECT_CALL(eds_resources_cache_, setExpiryTimer("fare", std::chrono::milliseconds(1000))); + doOnConfigUpdateVerifyNoThrowPost(cluster_load_assignment); + + const auto& hosts = cluster_post_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 11); + } +} + +// Validates that a used cached assignment that times out invokes the remove callback. +TEST_F(EdsCachedAssignmentTest, CachedAssignmentRemovedOnTimeout) { + // Set an initial assignment. + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("fare"); + cluster_load_assignment.mutable_policy()->mutable_endpoint_stale_after()->MergeFrom( + Protobuf::util::TimeUtil::SecondsToDuration(1)); + auto* endpoints = cluster_load_assignment.add_endpoints(); + auto* endpoint = endpoints->add_lb_endpoints(); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address("1.2.3.4"); + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value(80); + endpoint->mutable_load_balancing_weight()->set_value(10); + + // Store in the cache an assignment with a different weight. + envoy::config::endpoint::v3::ClusterLoadAssignment cached_cluster_load_assignment; + cached_cluster_load_assignment.CopyFrom(cluster_load_assignment); + cached_cluster_load_assignment.mutable_endpoints(0) + ->mutable_lb_endpoints(0) + ->mutable_load_balancing_weight() + ->set_value(22); + + initialize(); + // Expect the timer to be enabled once. + EXPECT_CALL(*interval_timer_pre_, enableTimer(std::chrono::milliseconds(1000), _)); + // Expect the timer to be disabled when stale assignments are removed. + // EXPECT_CALL(*interval_timer_, disableTimer()); + EXPECT_CALL(*interval_timer_pre_, enabled()); + // No call to the cache to fetch the assignment, as it is being delivered as expected. + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)).Times(0); + EXPECT_CALL(eds_resources_cache_, setExpiryTimer("fare", std::chrono::milliseconds(1000))); + doOnConfigUpdateVerifyNoThrowPre(cluster_load_assignment); + EXPECT_TRUE(initialized_); + { + const auto& hosts = cluster_pre_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 10); + } + + Config::EdsResourceRemovalCallback* removal_cb = nullptr; + // Update the cluster and emulate a warming failure, and validate + // that the resource is fetched from the cache. + updateCluster(); + { + EnvoyException dummy_ex("dummy exception"); + EXPECT_CALL(eds_resources_cache_, getResource("fare", _)) + .WillOnce(DoAll(SaveArg<1>(&removal_cb), Return(cached_cluster_load_assignment))); + EXPECT_CALL(*interval_timer_post_, enabled()); + eds_callbacks_post_->onConfigUpdateFailed( + Envoy::Config::ConfigUpdateFailureReason::FetchTimedout, &dummy_ex); + const auto& hosts = cluster_post_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 1); + EXPECT_EQ(hosts[0]->weight(), 22); + } + + // Emulate a timeout. + EXPECT_CALL(eds_resources_cache_, removeResource("fare")); + timer_cb_pre_(); + // Emulate a timer expiration call to removal_cb->onCachedResourceRemoved(). + removal_cb->onCachedResourceRemoved("fare"); + // Test that stale endpoints on the updated cluster are removed. + { + auto& hosts = cluster_post_->prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 0); + } + // Removing the cluster on test d'tor will trigger removeCallback. + EXPECT_CALL(eds_resources_cache_, removeCallback("fare", _)); +} + } // namespace } // namespace Upstream } // namespace Envoy diff --git a/test/extensions/clusters/redis/BUILD b/test/extensions/clusters/redis/BUILD index 9a630a0860ad..e989050baa9a 100644 --- a/test/extensions/clusters/redis/BUILD +++ b/test/extensions/clusters/redis/BUILD @@ -94,9 +94,6 @@ envoy_extension_cc_test( size = "large", srcs = ["redis_cluster_integration_test.cc"], extension_names = ["envoy.clusters.redis"], - # This test takes a while to run specially under tsan. - # Shard it to avoid test timeout. - shard_count = 2, deps = [ "//source/extensions/clusters/redis:redis_cluster", "//source/extensions/clusters/redis:redis_cluster_lb", diff --git a/test/extensions/config_subscription/grpc/BUILD b/test/extensions/config_subscription/grpc/BUILD index 46e75f764c04..f043d00891c0 100644 --- a/test/extensions/config_subscription/grpc/BUILD +++ b/test/extensions/config_subscription/grpc/BUILD @@ -22,6 +22,7 @@ envoy_cc_test( "//test/mocks:common_lib", "//test/mocks/config:config_mocks", "//test/mocks/config:custom_config_validators_mocks", + "//test/mocks/config:eds_resources_cache_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -43,6 +44,7 @@ envoy_cc_test( "//envoy/config:xds_config_tracker_interface", "//envoy/config:xds_resources_delegate_interface", "//source/common/config:api_version_lib", + "//source/common/config:null_grpc_mux_lib", "//source/common/config:protobuf_link_hacks", "//source/common/protobuf", "//source/common/stats:isolated_store_lib", @@ -51,6 +53,7 @@ envoy_cc_test( "//test/mocks:common_lib", "//test/mocks/config:config_mocks", "//test/mocks/config:custom_config_validators_mocks", + "//test/mocks/config:eds_resources_cache_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -76,6 +79,7 @@ envoy_cc_test( "//source/extensions/config_subscription/grpc:grpc_subscription_lib", "//test/mocks:common_lib", "//test/mocks/config:config_mocks", + "//test/mocks/config:eds_resources_cache_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -181,6 +185,7 @@ envoy_cc_test( "//test/mocks:common_lib", "//test/mocks/config:config_mocks", "//test/mocks/config:custom_config_validators_mocks", + "//test/mocks/config:eds_resources_cache_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -228,6 +233,7 @@ envoy_cc_test( "//source/extensions/config_subscription/grpc:watch_map_lib", "//test/mocks/config:config_mocks", "//test/mocks/config:custom_config_validators_mocks", + "//test/mocks/config:eds_resources_cache_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", diff --git a/test/extensions/config_subscription/grpc/delta_subscription_impl_test.cc b/test/extensions/config_subscription/grpc/delta_subscription_impl_test.cc index 2b435f510aac..c74e53cd5b27 100644 --- a/test/extensions/config_subscription/grpc/delta_subscription_impl_test.cc +++ b/test/extensions/config_subscription/grpc/delta_subscription_impl_test.cc @@ -154,18 +154,23 @@ TEST_P(DeltaSubscriptionNoGrpcStreamTest, NoGrpcStream) { auto backoff_strategy = std::make_unique( SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client), + /*dispatcher_=*/dispatcher, + /*service_method_=*/*method_descriptor, + /*local_info_=*/local_info, + /*rate_limit_settings_=*/rate_limit_settings, + /*scope_=*/*stats_store.rootScope(), + /*config_validators_=*/std::make_unique>(), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; if (GetParam() == LegacyOrUnified::Unified) { - xds_context = std::make_shared( - std::unique_ptr(async_client), dispatcher, *method_descriptor, - *stats_store.rootScope(), rate_limit_settings, local_info, false, - std::make_unique>(), std::move(backoff_strategy), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); + xds_context = std::make_shared(grpc_mux_context, false); } else { - xds_context = std::make_shared( - std::unique_ptr(async_client), dispatcher, *method_descriptor, - *stats_store.rootScope(), rate_limit_settings, local_info, - std::make_unique>(), std::move(backoff_strategy), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); + xds_context = std::make_shared(grpc_mux_context); } GrpcSubscriptionImplPtr subscription = std::make_unique( diff --git a/test/extensions/config_subscription/grpc/delta_subscription_test_harness.h b/test/extensions/config_subscription/grpc/delta_subscription_test_harness.h index 77c4310137aa..95ae79c6f042 100644 --- a/test/extensions/config_subscription/grpc/delta_subscription_test_harness.h +++ b/test/extensions/config_subscription/grpc/delta_subscription_test_harness.h @@ -50,18 +50,23 @@ class DeltaSubscriptionTestHarness : public SubscriptionTestHarness { EXPECT_CALL(dispatcher_, createTimer_(_)).Times(2); auto backoff_strategy = std::make_unique( SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/*method_descriptor_, + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/rate_limit_settings_, + /*scope_=*/*stats_store_.rootScope(), + /*config_validators_=*/std::make_unique>(), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; if (should_use_unified_) { - xds_context_ = std::make_shared( - std::unique_ptr(async_client_), dispatcher_, *method_descriptor_, - *stats_store_.rootScope(), rate_limit_settings_, local_info_, false, - std::make_unique>(), std::move(backoff_strategy), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); + xds_context_ = std::make_shared(grpc_mux_context, false); } else { - xds_context_ = std::make_shared( - std::unique_ptr(async_client_), dispatcher_, *method_descriptor_, - *stats_store_.rootScope(), rate_limit_settings_, local_info_, - std::make_unique>(), std::move(backoff_strategy), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); + xds_context_ = std::make_shared(grpc_mux_context); } subscription_ = std::make_unique( xds_context_, callbacks_, resource_decoder_, stats_, diff --git a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc index af24bf376a88..115c91ce1c0a 100644 --- a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc @@ -8,6 +8,7 @@ #include "source/common/common/empty_string.h" #include "source/common/config/api_version.h" +#include "source/common/config/null_grpc_mux_impl.h" #include "source/common/config/protobuf_link_hacks.h" #include "source/common/config/utility.h" #include "source/common/protobuf/protobuf.h" @@ -17,6 +18,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/mocks/common.h" #include "test/mocks/config/custom_config_validators.h" +#include "test/mocks/config/eds_resources_cache.h" #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" @@ -59,30 +61,28 @@ class GrpcMuxImplTestBase : public testing::Test { {} - void setup() { - grpc_mux_ = std::make_unique( - local_info_, std::unique_ptr(async_client_), dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, true, std::move(config_validators_), - std::make_unique( - SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, - random_), - /*xds_config_tracker=*/XdsConfigTrackerOptRef(), - /*xds_resources_delegate=*/XdsResourcesDelegateOptRef(), /*target_xds_authority=*/""); - } + void setup() { setup(rate_limit_settings_); } void setup(const RateLimitSettings& custom_rate_limit_settings) { - grpc_mux_ = std::make_unique( - local_info_, std::unique_ptr(async_client_), dispatcher_, + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/ *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), custom_rate_limit_settings, true, std::move(config_validators_), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/custom_rate_limit_settings, + /*scope_=*/*stats_.rootScope(), + /*config_validators_=*/std::move(config_validators_), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/ std::make_unique( SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_), - /*xds_config_tracker=*/XdsConfigTrackerOptRef(), - /*xds_resources_delegate=*/XdsResourcesDelegateOptRef(), /*target_xds_authority=*/""); + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/std::unique_ptr(eds_resources_cache_)}; + grpc_mux_ = std::make_unique(grpc_mux_context, true); } void expectSendMessage(const std::string& type_url, @@ -123,6 +123,7 @@ class GrpcMuxImplTestBase : public testing::Test { Envoy::Config::RateLimitSettings rate_limit_settings_; Stats::Gauge& control_plane_connected_state_; Stats::Gauge& control_plane_pending_requests_; + MockEdsResourcesCache* eds_resources_cache_{nullptr}; }; class GrpcMuxImplTest : public GrpcMuxImplTestBase { @@ -950,43 +951,363 @@ TEST_F(GrpcMuxImplTest, UnwatchedTypeRejectsResources) { TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyClusterName) { EXPECT_CALL(local_info_, clusterName()).WillOnce(ReturnRef(EMPTY_STRING)); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/ + *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/rate_limit_settings_, + /*scope_=*/*stats_.rootScope(), + /*config_validators_=*/std::make_unique>(), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/ + std::make_unique( + SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; EXPECT_THROW_WITH_MESSAGE( - GrpcMuxImpl( - local_info_, std::unique_ptr(async_client_), dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, true, - std::make_unique>(), - std::make_unique( - SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, - random_), - /*xds_config_tracker=*/XdsConfigTrackerOptRef(), - /*xds_resources_delegate=*/XdsResourcesDelegateOptRef(), - /*target_xds_authority=*/""), - EnvoyException, + GrpcMuxImpl(grpc_mux_context, true), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); } TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyNodeName) { EXPECT_CALL(local_info_, nodeName()).WillOnce(ReturnRef(EMPTY_STRING)); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/ + *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/rate_limit_settings_, + /*scope_=*/*stats_.rootScope(), + /*config_validators_=*/std::make_unique>(), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/ + std::make_unique( + SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; EXPECT_THROW_WITH_MESSAGE( - GrpcMuxImpl( - local_info_, std::unique_ptr(async_client_), dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, true, - std::make_unique>(), - std::make_unique( - SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, - random_), - /*xds_config_tracker=*/XdsConfigTrackerOptRef(), - /*xds_resources_delegate=*/XdsResourcesDelegateOptRef(), /*target_xds_authority=*/""), - EnvoyException, + GrpcMuxImpl(grpc_mux_context, true), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); } +// Validates that the EDS cache getter returns the cache. +TEST_F(GrpcMuxImplTest, EdsResourcesCacheForEds) { + eds_resources_cache_ = new NiceMock(); + setup(); + EXPECT_TRUE(grpc_mux_->edsResourcesCache().has_value()); +} + +// Validates that the EDS cache getter returns empty if there is no cache. +TEST_F(GrpcMuxImplTest, EdsResourcesCacheForEdsNoCache) { + setup(); + EXPECT_FALSE(grpc_mux_->edsResourcesCache().has_value()); +} + +// Validate that an EDS resource is cached if there's a cache. +TEST_F(GrpcMuxImplTest, CacheEdsResource) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + auto eds_sub = grpc_mux_->addWatch(type_url, {"x"}, callbacks_, resource_decoder, {}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Envoy will unsubscribe from all resources. + expectSendMessage(type_url, {}, "1"); + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); +} + +// Validate that an update to an EDS resource watcher is reflected in the cache, +// if there's a cache. +TEST_F(GrpcMuxImplTest, UpdateCacheEdsResource) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + auto eds_sub = grpc_mux_->addWatch(type_url, {"x"}, callbacks_, resource_decoder, {}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Update the cache to another resource. + expectSendMessage(type_url, {}, "1"); + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); + expectSendMessage(type_url, {"y"}, "1"); + eds_sub->update({"y"}); + + // Envoy will unsubscribe from all resources. + expectSendMessage(type_url, {}, "1"); + EXPECT_CALL(*eds_resources_cache_, removeResource("y")); +} + +// Validate that adding and removing watchers reflects on the cache changes, +// if there's a cache. +TEST_F(GrpcMuxImplTest, AddRemoveSubscriptions) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + + { + auto eds_sub = grpc_mux_->addWatch(type_url, {"x"}, callbacks_, resource_decoder, {}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, + const std::string&) { EXPECT_EQ(1, resources.size()); })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"x"}, "1"); // Ack. + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Watcher (eds_sub) going out of scope, the resource should be removed, as well as + // the interest. + expectSendMessage(type_url, {}, "1"); + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); + } + + // Update to a new resource interest. + { + expectSendMessage(type_url, {"y"}, "1"); + auto eds_sub2 = grpc_mux_->addWatch(type_url, {"y"}, callbacks_, resource_decoder, {}); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("2"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("y"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "2")) + .WillOnce(Invoke([](const std::vector& resources, + const std::string&) { EXPECT_EQ(1, resources.size()); })); + EXPECT_CALL(*eds_resources_cache_, setResource("y", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"y"}, "2"); // Ack. + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Watcher (eds_sub2) going out of scope, the resource should be removed, as well as + // the interest. + expectSendMessage(type_url, {}, "2"); + EXPECT_CALL(*eds_resources_cache_, removeResource("y")); + } +} + +// Validate that a cached resource is removed only after the last subscription +// (watch) that needs it is removed. +TEST_F(GrpcMuxImplTest, RemoveCachedResourceOnLastSubscription) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + + NiceMock eds_sub1_callbacks; + auto eds_sub1 = grpc_mux_->addWatch(type_url, {"x"}, eds_sub1_callbacks, resource_decoder, {}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(eds_sub1_callbacks, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"x"}, "1"); // Ack. + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Add another subscription to the same resource. The non-unified SotW + // implementation will send another request for that resource. + expectSendMessage(type_url, {"x"}, "1"); + NiceMock eds_sub2_callbacks; + auto eds_sub2 = grpc_mux_->addWatch(type_url, {"x"}, eds_sub2_callbacks, resource_decoder, {}); + + // The reply with the resource will update the cached resource. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("2"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + // Both subscriptions should receive the update for the resource, and each + // will store the resource in the cache. + EXPECT_CALL(eds_sub2_callbacks, onConfigUpdate(_, "2")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + EXPECT_CALL(eds_sub1_callbacks, onConfigUpdate(_, "2")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"x"}, "2"); // Ack. + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Removing the first subscription will still keep the resource in the cache. + { + // The non-unified SotW implementation will send another request with the + // same resource. + expectSendMessage(type_url, {"x"}, "2"); + EXPECT_CALL(*eds_resources_cache_, removeResource("x")).Times(0); + eds_sub1.reset(); + } + + // Watcher (eds_sub2) going out of scope, the resource should be removed, as well as + // the interest in the resource. + expectSendMessage(type_url, {}, "2"); + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); +} + +/** + * Tests the NullGrpcMuxImpl object to increase code-coverage. + */ +class NullGrpcMuxImplTest : public testing::Test { +public: + NullGrpcMuxImplTest() {} + NullGrpcMuxImpl null_mux_; + NiceMock callbacks_; +}; + +TEST_F(NullGrpcMuxImplTest, StartImplemented) { EXPECT_NO_THROW(null_mux_.start()); } + +TEST_F(NullGrpcMuxImplTest, PauseImplemented) { + ScopedResume scoped; + EXPECT_NO_THROW(scoped = null_mux_.pause("ignored")); +} + +TEST_F(NullGrpcMuxImplTest, PauseMultipleArgsImplemented) { + ScopedResume scoped; + const std::vector params = {"ignored", "another_ignored"}; + EXPECT_NO_THROW(scoped = null_mux_.pause(params)); +} + +TEST_F(NullGrpcMuxImplTest, RequestOnDemandNotImplemented) { + EXPECT_ENVOY_BUG(null_mux_.requestOnDemandUpdate("type_url", {"for_update"}), + "unexpected request for on demand update"); +} + +TEST_F(NullGrpcMuxImplTest, AddWatchRaisesException) { + NiceMock callbacks; + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + + EXPECT_THROW_WITH_REGEX(null_mux_.addWatch("type_url", {}, callbacks, resource_decoder, {}), + EnvoyException, "ADS must be configured to support an ADS config source"); +} + +TEST_F(NullGrpcMuxImplTest, NoEdsResourcesCache) { EXPECT_EQ({}, null_mux_.edsResourcesCache()); } +TEST_F(NullGrpcMuxImplTest, OnWriteableImplemented) { EXPECT_NO_THROW(null_mux_.onWriteable()); } +TEST_F(NullGrpcMuxImplTest, OnStreamEstablishedImplemented) { + EXPECT_NO_THROW(null_mux_.onStreamEstablished()); +} +TEST_F(NullGrpcMuxImplTest, OnEstablishmentFailureImplemented) { + EXPECT_NO_THROW(null_mux_.onEstablishmentFailure()); +} +TEST_F(NullGrpcMuxImplTest, OnDiscoveryResponseImplemented) { + std::unique_ptr response; + Stats::TestUtil::TestStore stats; + ControlPlaneStats cp_stats{Utility::generateControlPlaneStats(*stats.rootScope())}; + EXPECT_NO_THROW(null_mux_.onDiscoveryResponse(std::move(response), cp_stats)); +} + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc index 3712709f9f73..3d978df55080 100644 --- a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc @@ -17,6 +17,7 @@ #include "test/config/v2_link_hacks.h" #include "test/mocks/common.h" #include "test/mocks/config/custom_config_validators.h" +#include "test/mocks/config/eds_resources_cache.h" #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" @@ -62,24 +63,26 @@ class NewGrpcMuxImplTestBase : public testing::TestWithParam { void setup() { auto backoff_strategy = std::make_unique( SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_); - + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/ + *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "envoy.service.discovery.v3.AggregatedDiscoveryService.DeltaAggregatedResources"), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/rate_limit_settings_, + /*scope_=*/*stats_.rootScope(), + /*config_validators_=*/std::move(config_validators_), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/std::move(backoff_strategy), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/std::unique_ptr(eds_resources_cache_)}; if (isUnifiedMuxTest()) { - grpc_mux_ = std::make_unique( - std::unique_ptr(async_client_), dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, local_info_, false, - std::move(config_validators_), std::move(backoff_strategy), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); + grpc_mux_ = std::make_unique(grpc_mux_context, false); return; } - grpc_mux_ = std::make_unique( - std::unique_ptr(async_client_), dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, local_info_, std::move(config_validators_), - std::move(backoff_strategy), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); + grpc_mux_ = std::make_unique(grpc_mux_context); } void expectSendMessage(const std::string& type_url, @@ -172,6 +175,7 @@ class NewGrpcMuxImplTestBase : public testing::TestWithParam { ControlPlaneStats control_plane_stats_; Stats::Gauge& control_plane_connected_state_; bool should_use_unified_; + MockEdsResourcesCache* eds_resources_cache_{nullptr}; }; class NewGrpcMuxImplTest : public NewGrpcMuxImplTestBase { @@ -561,6 +565,195 @@ TEST_P(NewGrpcMuxImplTest, Shutdown) { // There won't be any unsubscribe messages for the legacy mux either for the same reason } +// Validates that the EDS cache getter returns the cache. +TEST_P(NewGrpcMuxImplTest, EdsResourcesCacheForEds) { + eds_resources_cache_ = new NiceMock(); + setup(); + EXPECT_TRUE(grpc_mux_->edsResourcesCache().has_value()); +} + +// Validates that the EDS cache getter returns empty if there is no cache. +TEST_P(NewGrpcMuxImplTest, EdsResourcesCacheForEdsNoCache) { + setup(); + EXPECT_FALSE(grpc_mux_->edsResourcesCache().has_value()); +} + +// Validate that an EDS resource is cached if there's a cache. +TEST_P(NewGrpcMuxImplTest, CacheEdsResource) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + InSequence s; + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + auto watch = grpc_mux_->addWatch(type_url, {"x"}, callbacks_, resource_decoder_, {}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, {}); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_system_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + auto res = response->add_resources(); + res->set_name("x"); + res->set_version("1"); + load_assignment.set_cluster_name("x"); + res->mutable_resource()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) + .WillOnce(Invoke([&load_assignment](const std::vector& added_resources, + const Protobuf::RepeatedPtrField&, + const std::string&) { + EXPECT_EQ(1, added_resources.size()); + EXPECT_TRUE( + TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {}, {}); // Ack. + onDiscoveryResponse(std::move(response)); + } + + // Envoy will unsubscribe from all resources. + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); + expectSendMessage(type_url, {}, {"x"}); +} + +// Validate that an update to an EDS resource watcher is reflected in the cache, +// if there's a cache. +TEST_P(NewGrpcMuxImplTest, UpdateCacheEdsResource) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + InSequence s; + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + auto watch = grpc_mux_->addWatch(type_url, {"x"}, callbacks_, resource_decoder_, {}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, {}); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_system_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + auto res = response->add_resources(); + res->set_name("x"); + res->set_version("1"); + load_assignment.set_cluster_name("x"); + res->mutable_resource()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) + .WillOnce(Invoke([&load_assignment](const std::vector& added_resources, + const Protobuf::RepeatedPtrField&, + const std::string&) { + EXPECT_EQ(1, added_resources.size()); + EXPECT_TRUE( + TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {}, {}); // Ack. + onDiscoveryResponse(std::move(response)); + } + + // Update the cache to another resource. + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); + expectSendMessage(type_url, {"y"}, {"x"}); + watch->update({"y"}); + + // Envoy will unsubscribe from all resources. + EXPECT_CALL(*eds_resources_cache_, removeResource("y")); + expectSendMessage(type_url, {}, {"y"}); +} + +// Validate that adding and removing watchers reflects on the cache changes, +// if there's a cache. +TEST_P(NewGrpcMuxImplTest, AddRemoveSubscriptions) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + InSequence s; + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + + { + auto watch1 = grpc_mux_->addWatch(type_url, {"x"}, callbacks_, resource_decoder_, {}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, {}); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_system_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + auto res = response->add_resources(); + res->set_name("x"); + res->set_version("1"); + load_assignment.set_cluster_name("x"); + res->mutable_resource()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) + .WillOnce(Invoke([&load_assignment]( + const std::vector& added_resources, + const Protobuf::RepeatedPtrField&, const std::string&) { + EXPECT_EQ(1, added_resources.size()); + EXPECT_TRUE( + TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {}, {}); // Ack. + onDiscoveryResponse(std::move(response)); + } + + // Watcher (watch1) going out of scope, the resource should be removed, as well as + // the interest. + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); + expectSendMessage(type_url, {}, {"x"}); + } + + // Update to a new resource interest. + { + expectSendMessage(type_url, {"y"}, {}); + auto watch2 = grpc_mux_->addWatch(type_url, {"y"}, callbacks_, resource_decoder_, {}); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_system_version_info("2"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + auto res = response->add_resources(); + res->set_name("y"); + res->set_version("2"); + load_assignment.set_cluster_name("y"); + res->mutable_resource()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "2")) + .WillOnce(Invoke([&load_assignment]( + const std::vector& added_resources, + const Protobuf::RepeatedPtrField&, const std::string&) { + EXPECT_EQ(1, added_resources.size()); + EXPECT_TRUE( + TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("y", ProtoEq(load_assignment))); + expectSendMessage(type_url, {}, {}); // Ack. + onDiscoveryResponse(std::move(response)); + } + + // Watcher (watch2) going out of scope, the resource should be removed, as well as + // the interest. + EXPECT_CALL(*eds_resources_cache_, removeResource("y")); + expectSendMessage(type_url, {}, {"y"}); + } +} + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/extensions/config_subscription/grpc/watch_map_test.cc b/test/extensions/config_subscription/grpc/watch_map_test.cc index dcdbe9a418ee..771c26cea73c 100644 --- a/test/extensions/config_subscription/grpc/watch_map_test.cc +++ b/test/extensions/config_subscription/grpc/watch_map_test.cc @@ -9,6 +9,7 @@ #include "source/extensions/config_subscription/grpc/watch_map.h" #include "test/mocks/config/custom_config_validators.h" +#include "test/mocks/config/eds_resources_cache.h" #include "test/mocks/config/mocks.h" #include "test/test_common/utility.h" @@ -127,7 +128,7 @@ TEST(WatchMapTest, Basic) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); Watch* watch = watch_map.addWatch(callbacks, resource_decoder); { @@ -201,7 +202,7 @@ TEST(WatchMapTest, Overlap) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); @@ -259,12 +260,99 @@ TEST(WatchMapTest, Overlap) { } } +// Checks that a resource is added to the cache the first time it is received, +// and removed when there's no more interest. +TEST(WatchMapTest, CacheResourceAddResource) { + MockSubscriptionCallbacks callbacks1; + MockSubscriptionCallbacks callbacks2; + TestUtility::TestOpaqueResourceDecoderImpl + resource_decoder("cluster_name"); + NiceMock config_validators; + NiceMock eds_resources_cache; + const std::string eds_type_url = + Config::getTypeUrl(); + WatchMap watch_map(false, eds_type_url, config_validators, + makeOptRef(eds_resources_cache)); + // The test uses 2 watchers to ensure that interest is kept regardless of + // which watcher was the first to add a watch for the assignment. + Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); + Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); + + Protobuf::RepeatedPtrField updated_resources; + envoy::config::endpoint::v3::ClusterLoadAssignment alice; + alice.set_cluster_name("alice"); + updated_resources.Add()->PackFrom(alice); + + // First watch becomes interested. + { + absl::flat_hash_set update_to({"alice", "dummy"}); + // "alice" isn't known - no need to remove from the cache. + EXPECT_CALL(eds_resources_cache, removeResource("alice")).Times(0); + AddedRemoved added_removed = watch_map.updateWatchInterest(watch1, update_to); + EXPECT_EQ(update_to, added_removed.added_); // add to subscription + EXPECT_TRUE(added_removed.removed_.empty()); + watch_map.updateWatchInterest(watch2, {"dummy"}); + + // *Only* first watch receives update. + expectDeltaAndSotwUpdate(callbacks1, {alice}, {}, "version1"); + expectNoUpdate(callbacks2, "version1"); + // A call for SotW and a call for Delta to the cache's setResource method. + EXPECT_CALL(eds_resources_cache, setResource("alice", ProtoEq(alice))).Times(2); + doDeltaAndSotwUpdate(watch_map, updated_resources, {}, "version1"); + } + // Second watch becomes interested. + { + absl::flat_hash_set update_to({"alice", "dummy"}); + // "alice" is known, and there's still interest - no removal. + EXPECT_CALL(eds_resources_cache, removeResource("alice")).Times(0); + AddedRemoved added_removed = watch_map.updateWatchInterest(watch2, update_to); + EXPECT_TRUE(added_removed.added_.empty()); // nothing happens + EXPECT_TRUE(added_removed.removed_.empty()); + + // Both watches receive update. + expectDeltaAndSotwUpdate(callbacks1, {alice}, {}, "version2"); + expectDeltaAndSotwUpdate(callbacks2, {alice}, {}, "version2"); + // A call for SotW and a call for Delta to the cache's setResource method. + EXPECT_CALL(eds_resources_cache, setResource("alice", ProtoEq(alice))).Times(2); + doDeltaAndSotwUpdate(watch_map, updated_resources, {}, "version2"); + } + // First watch loses interest. + { + // "alice" is known, and there's still interest - no removal. + EXPECT_CALL(eds_resources_cache, removeResource("alice")).Times(0); + AddedRemoved added_removed = watch_map.updateWatchInterest(watch1, {"dummy"}); + EXPECT_TRUE(added_removed.added_.empty()); // nothing happens + EXPECT_TRUE(added_removed.removed_.empty()); + + // Both watches receive the update. For watch2, this is obviously desired. + expectDeltaAndSotwUpdate(callbacks2, {alice}, {}, "version3"); + // For watch1, it's more subtle: the WatchMap sees that this update has no + // resources watch1 cares about, but also knows that watch1 previously had + // some resources. So, it must inform watch1 that it now has no resources. + // (SotW only: delta's explicit removals avoid the need for this guessing.) + expectEmptySotwNoDeltaUpdate(callbacks1, "version3"); + // A call for SotW and a call for Delta to the cache's setResource method. + EXPECT_CALL(eds_resources_cache, setResource("alice", ProtoEq(alice))).Times(2); + doDeltaAndSotwUpdate(watch_map, updated_resources, {}, "version3"); + } + // Second watch loses interest. + { + // A call for the cache's removeResource method as there's no more + // interest in "alice". + EXPECT_CALL(eds_resources_cache, removeResource("alice")); + AddedRemoved added_removed = watch_map.updateWatchInterest(watch2, {"dummy"}); + EXPECT_TRUE(added_removed.added_.empty()); + EXPECT_EQ(absl::flat_hash_set({"alice"}), + added_removed.removed_); // remove from subscription + } +} + // These are regression tests for #11877, validate that when two watches point at the same // watched resource, and an update to one of the watches removes one or both of them, that // WatchMap defers deletes and doesn't crash. class SameWatchRemoval : public testing::Test { public: - SameWatchRemoval() : watch_map_(false, "ClusterLoadAssignmentType", config_validators) {} + SameWatchRemoval() : watch_map_(false, "ClusterLoadAssignmentType", config_validators, {}) {} void SetUp() override { envoy::config::endpoint::v3::ClusterLoadAssignment alice; @@ -343,7 +431,7 @@ TEST(WatchMapTest, AddRemoveAdd) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); @@ -400,7 +488,7 @@ TEST(WatchMapTest, UninterestingUpdate) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); Watch* watch = watch_map.addWatch(callbacks, resource_decoder); watch_map.updateWatchInterest(watch, {"alice"}); @@ -445,7 +533,7 @@ TEST(WatchMapTest, WatchingEverything) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); /*Watch* watch1 = */ watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); // watch1 never specifies any names, and so is treated as interested in everything. @@ -482,7 +570,7 @@ TEST(WatchMapTest, DeltaOnConfigUpdate) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); Watch* watch3 = watch_map.addWatch(callbacks3, resource_decoder); @@ -516,7 +604,7 @@ TEST(WatchMapTest, DeltaOnConfigUpdate) { TEST(WatchMapTest, OnConfigUpdateFailed) { NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); // calling on empty map doesn't break watch_map.onConfigUpdateFailed(ConfigUpdateFailureReason::UpdateRejected, nullptr); @@ -538,7 +626,7 @@ TEST(WatchMapTest, OnConfigUpdateXdsTpGlobCollections) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); Watch* watch = watch_map.addWatch(callbacks, resource_decoder); watch_map.updateWatchInterest(watch, {"xdstp://foo/bar/baz/*?some=thing&thing=some"}); @@ -583,7 +671,7 @@ TEST(WatchMapTest, OnConfigUpdateXdsTpSingletons) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators, {}); Watch* watch = watch_map.addWatch(callbacks, resource_decoder); watch_map.updateWatchInterest(watch, {"xdstp://foo/bar/baz?some=thing&thing=some"}); @@ -624,7 +712,7 @@ TEST(WatchMapTest, OnConfigUpdateUsingNamespaces) { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); NiceMock config_validators; - WatchMap watch_map(true, "ClusterLoadAssignmentType", config_validators); + WatchMap watch_map(true, "ClusterLoadAssignmentType", config_validators, {}); Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); Watch* watch3 = watch_map.addWatch(callbacks3, resource_decoder); @@ -677,6 +765,10 @@ TEST(WatchMapTest, OnConfigUpdateUsingNamespaces) { } } +// TODO(adip): Add tests that use the eds cache. +// Needs to test the following function onConfigUpdate (sotw&delta) and +// updateWatchInterest + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc index 8530afb7a64f..9924eaef77cf 100644 --- a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc @@ -17,6 +17,7 @@ #include "test/config/v2_link_hacks.h" #include "test/mocks/common.h" #include "test/mocks/config/custom_config_validators.h" +#include "test/mocks/config/eds_resources_cache.h" #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" @@ -58,29 +59,28 @@ class GrpcMuxImplTestBase : public testing::Test { control_plane_pending_requests_(stats_.gauge("control_plane.pending_requests", Stats::Gauge::ImportMode::NeverImport)) {} - void setup() { - grpc_mux_ = std::make_unique( - std::unique_ptr(async_client_), dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, local_info_, true, std::move(config_validators_), - std::make_unique( - SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, - random_), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); - } + void setup() { setup(rate_limit_settings_); } void setup(const RateLimitSettings& custom_rate_limit_settings) { - grpc_mux_ = std::make_unique( - std::unique_ptr(async_client_), dispatcher_, + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/ *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), custom_rate_limit_settings, local_info_, true, - std::move(config_validators_), + "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/custom_rate_limit_settings, + /*scope_=*/*stats_.rootScope(), + /*config_validators_=*/std::move(config_validators_), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/ std::make_unique( SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/std::unique_ptr(eds_resources_cache_)}; + grpc_mux_ = std::make_unique(grpc_mux_context, true); } void expectSendMessage(const std::string& type_url, @@ -138,6 +138,7 @@ class GrpcMuxImplTestBase : public testing::Test { Envoy::Config::RateLimitSettings rate_limit_settings_; Stats::Gauge& control_plane_connected_state_; Stats::Gauge& control_plane_pending_requests_; + MockEdsResourcesCache* eds_resources_cache_{nullptr}; }; class GrpcMuxImplTest : public GrpcMuxImplTestBase { @@ -897,33 +898,50 @@ TEST_F(GrpcMuxImplTest, UnwatchedTypeAcceptsResources) { TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyClusterName) { EXPECT_CALL(local_info_, clusterName()).WillOnce(ReturnRef(EMPTY_STRING)); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/ + *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/rate_limit_settings_, + /*scope_=*/*stats_.rootScope(), + /*config_validators_=*/std::make_unique>(), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/ + std::make_unique( + SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; EXPECT_THROW_WITH_MESSAGE( - XdsMux::GrpcMuxSotw( - std::unique_ptr(async_client_), dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, local_info_, true, - std::make_unique>(), - std::make_unique( - SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, - random_), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()), - EnvoyException, + XdsMux::GrpcMuxSotw(grpc_mux_context, true), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); } TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyNodeName) { EXPECT_CALL(local_info_, nodeName()).WillOnce(ReturnRef(EMPTY_STRING)); + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(async_client_), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/ + *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/rate_limit_settings_, + /*scope_=*/*stats_.rootScope(), + /*config_validators_=*/std::make_unique>(), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/ + std::make_unique( + SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_), + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; EXPECT_THROW_WITH_MESSAGE( - XdsMux::GrpcMuxSotw( - std::unique_ptr(async_client_), dispatcher_, - *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, local_info_, true, - std::make_unique>(), nullptr, - /*xds_config_tracker=*/XdsConfigTrackerOptRef()), - EnvoyException, + XdsMux::GrpcMuxSotw(grpc_mux_context, true), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); } @@ -1029,22 +1047,201 @@ TEST_F(GrpcMuxImplTest, DynamicContextParameters) { TEST_F(GrpcMuxImplTest, AllMuxesStateTest) { setup(); - auto grpc_mux_1 = std::make_unique( - std::unique_ptr(), dispatcher_, + GrpcMuxContext grpc_mux_context{ + /*async_client_=*/std::unique_ptr(), + /*dispatcher_=*/dispatcher_, + /*service_method_=*/ *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - *stats_.rootScope(), rate_limit_settings_, local_info_, true, - std::make_unique>(), + "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), + /*local_info_=*/local_info_, + /*rate_limit_settings_=*/rate_limit_settings_, + /*scope_=*/*stats_.rootScope(), + /*config_validators_=*/std::make_unique>(), + /*xds_resources_delegate_=*/XdsResourcesDelegateOptRef(), + /*xds_config_tracker_=*/XdsConfigTrackerOptRef(), + /*backoff_strategy_=*/ std::make_unique( SubscriptionFactory::RetryInitialDelayMs, SubscriptionFactory::RetryMaxDelayMs, random_), - /*xds_config_tracker=*/XdsConfigTrackerOptRef()); - + /*target_xds_authority_=*/"", + /*eds_resources_cache_=*/nullptr}; + auto grpc_mux_1 = std::make_unique(grpc_mux_context, true); Config::XdsMux::GrpcMuxSotw::shutdownAll(); EXPECT_TRUE(grpc_mux_->isShutdown()); EXPECT_TRUE(grpc_mux_1->isShutdown()); } +// Validates that the EDS cache getter returns the cache. +TEST_F(GrpcMuxImplTest, EdsResourcesCacheForEds) { + eds_resources_cache_ = new NiceMock(); + setup(); + EXPECT_TRUE(grpc_mux_->edsResourcesCache().has_value()); +} + +// Validates that the EDS cache getter returns empty if there is no cache. +TEST_F(GrpcMuxImplTest, EdsResourcesCacheForEdsNoCache) { + setup(); + EXPECT_FALSE(grpc_mux_->edsResourcesCache().has_value()); +} + +// Validate that an EDS resource is cached if there's a cache. +TEST_F(GrpcMuxImplTest, CacheEdsResource) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + auto eds_sub = makeWatch(type_url, {"x"}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); + } + + // Envoy will unsubscribe from all resources. + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); + expectSendMessage(type_url, {}, "1"); +} + +// Validate that an update to an EDS resource watcher is reflected in the cache, +// if there's a cache. +TEST_F(GrpcMuxImplTest, UpdateCacheEdsResource) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + auto eds_sub = makeWatch(type_url, {"x"}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); + } + + // Update the cache to another resource. + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); + expectSendMessage(type_url, {"y"}, "1"); + eds_sub->update({"y"}); + + // Envoy will unsubscribe from all resources. + EXPECT_CALL(*eds_resources_cache_, removeResource("y")); + expectSendMessage(type_url, {}, "1"); +} + +// Validate that adding and removing watchers reflects on the cache changes, +// if there's a cache. +TEST_F(GrpcMuxImplTest, AddRemoveSubscriptions) { + // Create the cache that will also be passed to the GrpcMux object via setup(). + eds_resources_cache_ = new NiceMock(); + setup(); + + OpaqueResourceDecoderSharedPtr resource_decoder( + std::make_shared>("cluster_name")); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + + { + auto eds_sub = makeWatch(type_url, {"x"}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, + const std::string&) { EXPECT_EQ(1, resources.size()); })); + EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"x"}, "1"); // Ack. + grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); + } + + // Watcher (eds_sub) going out of scope, the resource should be removed, as well as + // the interest. + EXPECT_CALL(*eds_resources_cache_, removeResource("x")); + expectSendMessage(type_url, {}, "1"); + } + + // Update to a new resource interest. + { + expectSendMessage(type_url, {"y"}, "1"); + auto eds_sub2 = makeWatch(type_url, {"y"}); + + // Reply with the resource, it will be added to the cache. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("2"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("y"); + response->add_resources()->PackFrom(load_assignment); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "2")) + .WillOnce(Invoke([](const std::vector& resources, + const std::string&) { EXPECT_EQ(1, resources.size()); })); + EXPECT_CALL(*eds_resources_cache_, setResource("y", ProtoEq(load_assignment))); + expectSendMessage(type_url, {"y"}, "2"); // Ack. + grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); + } + + // Watcher (eds_sub2) going out of scope, the resource should be removed, as well as + // the interest. + EXPECT_CALL(*eds_resources_cache_, removeResource("y")); + expectSendMessage(type_url, {}, "2"); + } +} + class NullGrpcMuxImplTest : public testing::Test { public: NullGrpcMuxImplTest() : null_mux_(std::make_unique()) {} @@ -1080,6 +1277,8 @@ TEST_F(NullGrpcMuxImplTest, AddWatchRaisesException) { EnvoyException, "ADS must be configured to support an ADS config source"); } +TEST_F(NullGrpcMuxImplTest, NoEdsResourcesCache) { EXPECT_EQ({}, null_mux_->edsResourcesCache()); } + } // namespace } // namespace XdsMux } // namespace Config diff --git a/test/extensions/filters/http/buffer/BUILD b/test/extensions/filters/http/buffer/BUILD index ade6084d5580..31250dd10aeb 100644 --- a/test/extensions/filters/http/buffer/BUILD +++ b/test/extensions/filters/http/buffer/BUILD @@ -37,7 +37,7 @@ envoy_extension_cc_test( size = "large", srcs = ["buffer_filter_integration_test.cc"], extension_names = ["envoy.filters.http.buffer"], - shard_count = 16, + shard_count = 4, deps = [ "//source/extensions/filters/http/buffer:config", "//test/config:utility_lib", diff --git a/test/extensions/filters/http/cache/BUILD b/test/extensions/filters/http/cache/BUILD index 78aa59e762db..65930a8771f3 100644 --- a/test/extensions/filters/http/cache/BUILD +++ b/test/extensions/filters/http/cache/BUILD @@ -19,6 +19,14 @@ envoy_cc_test_library( ], ) +envoy_cc_test_library( + name = "mocks", + hdrs = ["mocks.h"], + deps = [ + "//source/extensions/filters/http/cache:http_cache_lib", + ], +) + envoy_extension_cc_test( name = "cache_headers_utils_test", srcs = ["cache_headers_utils_test.cc"], @@ -82,6 +90,7 @@ envoy_extension_cc_test( extension_names = ["envoy.filters.http.cache"], deps = [ ":common", + ":mocks", "//source/extensions/filters/http/cache:cache_filter_lib", "//source/extensions/filters/http/cache:cache_filter_logging_info_lib", "//source/extensions/http/cache/simple_http_cache:config", @@ -125,7 +134,6 @@ envoy_extension_cc_test( "cache_filter_integration_test.cc", ], extension_names = ["envoy.filters.http.cache"], - shard_count = 2, deps = [ "//source/extensions/filters/http/cache:config", "//source/extensions/filters/http/cache:http_cache_lib", diff --git a/test/extensions/filters/http/cache/cache_filter_integration_test.cc b/test/extensions/filters/http/cache/cache_filter_integration_test.cc index 71979472342a..34e318bdd4cc 100644 --- a/test/extensions/filters/http/cache/cache_filter_integration_test.cc +++ b/test/extensions/filters/http/cache/cache_filter_integration_test.cc @@ -76,16 +76,22 @@ class CacheIntegrationTest : public Event::TestUsingSimulatedTime, return response_decoder; } - std::function - simulateUpstreamResponse(const Http::TestResponseHeaderMapImpl& headers, - OptRef body, - OptRef trailers) { + // split_body allows us to test the behavior when encodeData is in more than one part. + std::function simulateUpstreamResponse( + const Http::TestResponseHeaderMapImpl& headers, OptRef body, + OptRef trailers, bool split_body = false) { return [this, headers = std::move(headers), body = std::move(body), - trailers = std::move(trailers)]() { + trailers = std::move(trailers), split_body]() { waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(headers, /*end_stream=*/!body); if (body.has_value()) { - upstream_request_->encodeData(body.ref(), !trailers.has_value()); + if (split_body) { + upstream_request_->encodeData(body.ref().substr(0, body.ref().size() / 2), false); + upstream_request_->encodeData(body.ref().substr(body.ref().size() / 2), + !trailers.has_value()); + } else { + upstream_request_->encodeData(body.ref(), !trailers.has_value()); + } } if (trailers.has_value()) { upstream_request_->encodeTrailers(trailers.ref()); @@ -124,10 +130,11 @@ TEST_P(CacheIntegrationTest, MissInsertHit) { Http::TestResponseHeaderMapImpl response_headers = httpResponseHeadersForBody(response_body); // Send first request, and get response from upstream. + // use split_body to cover multipart body responses. { IntegrationStreamDecoderPtr response_decoder = sendHeaderOnlyRequestAwaitResponse( - request_headers, - simulateUpstreamResponse(response_headers, makeOptRef(response_body), empty_trailers_)); + request_headers, simulateUpstreamResponse(response_headers, makeOptRef(response_body), + empty_trailers_, true)); EXPECT_THAT(response_decoder->headers(), IsSupersetOfHeaders(response_headers)); EXPECT_EQ(response_decoder->headers().get(Http::CustomHeaders::get().Age).size(), 0); EXPECT_EQ(response_decoder->body(), response_body); diff --git a/test/extensions/filters/http/cache/cache_filter_test.cc b/test/extensions/filters/http/cache/cache_filter_test.cc index ff23ccd7f76c..6ca97f0efe72 100644 --- a/test/extensions/filters/http/cache/cache_filter_test.cc +++ b/test/extensions/filters/http/cache/cache_filter_test.cc @@ -6,6 +6,7 @@ #include "source/extensions/http/cache/simple_http_cache/simple_http_cache.h" #include "test/extensions/filters/http/cache/common.h" +#include "test/extensions/filters/http/cache/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/status_utility.h" @@ -22,14 +23,23 @@ namespace Cache { namespace { using ::Envoy::StatusHelpers::IsOkAndHolds; +using ::testing::IsNull; +using ::testing::NotNull; class CacheFilterTest : public ::testing::Test { protected: // The filter has to be created as a shared_ptr to enable shared_from_this() which is used in the // cache callbacks. - CacheFilterSharedPtr makeFilter(OptRef cache) { - auto filter = std::make_shared(config_, /*stats_prefix=*/"", context_.scope(), - context_.timeSource(), cache); + CacheFilterSharedPtr makeFilter(OptRef cache, bool auto_destroy = true) { + std::shared_ptr filter(new CacheFilter(config_, /*stats_prefix=*/"", + context_.scope(), context_.timeSource(), + cache), + [auto_destroy](CacheFilter* f) { + if (auto_destroy) { + f->onDestroy(); + } + delete f; + }); filter_state_ = std::make_shared( StreamInfo::FilterState::LifeSpan::FilterChain); filter->setDecoderFilterCallbacks(decoder_callbacks_); @@ -38,6 +48,7 @@ class CacheFilterTest : public ::testing::Test { } void SetUp() override { + ON_CALL(encoder_callbacks_, dispatcher()).WillByDefault(::testing::ReturnRef(*dispatcher_)); ON_CALL(decoder_callbacks_, dispatcher()).WillByDefault(::testing::ReturnRef(*dispatcher_)); ON_CALL(decoder_callbacks_.stream_info_, filterState()) .WillByDefault(::testing::ReturnRef(filter_state_)); @@ -171,7 +182,7 @@ class CacheFilterTest : public ::testing::Test { }; TEST_F(CacheFilterTest, FilterIsBeingDestroyed) { - CacheFilterSharedPtr filter = makeFilter(simple_cache_); + CacheFilterSharedPtr filter = makeFilter(simple_cache_, false); filter->onDestroy(); // decodeHeaders should do nothing... at least make sure it doesn't crash. filter->decodeHeaders(request_headers_, true); @@ -202,8 +213,6 @@ TEST_F(CacheFilterTest, UncacheableRequest) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::RequestNotCacheable)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::NoInsertRequestNotCacheable)); - - filter->onDestroy(); } } @@ -225,8 +234,6 @@ TEST_F(CacheFilterTest, UncacheableResponse) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::NoInsertResponseNotCacheable)); - - filter->onDestroy(); } } @@ -246,16 +253,15 @@ TEST_F(CacheFilterTest, CacheMiss) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::InsertSucceeded)); - - filter->onDestroy(); } + // Clear events off the dispatcher. + dispatcher_->run(Event::Dispatcher::RunType::Block); } TEST_F(CacheFilterTest, Disabled) { request_headers_.setHost("CacheDisabled"); CacheFilterSharedPtr filter = makeFilter(OptRef{}); EXPECT_EQ(filter->decodeHeaders(request_headers_, true), Http::FilterHeadersStatus::Continue); - filter->onDestroy(); } TEST_F(CacheFilterTest, CacheMissWithTrailers) { @@ -281,9 +287,9 @@ TEST_F(CacheFilterTest, CacheMissWithTrailers) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::InsertSucceeded)); - - filter->onDestroy(); } + // Clear events off the dispatcher. + dispatcher_->run(Event::Dispatcher::RunType::Block); } TEST_F(CacheFilterTest, CacheHitNoBody) { @@ -297,7 +303,6 @@ TEST_F(CacheFilterTest, CacheHitNoBody) { // Encode response headers. EXPECT_EQ(filter->encodeHeaders(response_headers_, true), Http::FilterHeadersStatus::Continue); - filter->onDestroy(); } waitBeforeSecondRequest(); { @@ -309,8 +314,6 @@ TEST_F(CacheFilterTest, CacheHitNoBody) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheHit)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::NoInsertCacheHit)); - - filter->onDestroy(); } } @@ -329,12 +332,13 @@ TEST_F(CacheFilterTest, CacheHitWithBody) { response_headers_.setContentLength(body.size()); EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); EXPECT_EQ(filter->encodeData(buffer, true), Http::FilterDataStatus::Continue); + // The cache insertBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::InsertSucceeded)); - - filter->onDestroy(); } waitBeforeSecondRequest(); { @@ -346,9 +350,301 @@ TEST_F(CacheFilterTest, CacheHitWithBody) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheHit)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::NoInsertCacheHit)); + } +} + +TEST_F(CacheFilterTest, WatermarkEventsAreSentIfCacheBlocksStreamAndLimitExceeded) { + request_headers_.setHost("CacheHitWithBody"); + const std::string body1 = "abcde"; + const std::string body2 = "fghij"; + // Set the buffer limit to 2 bytes to ensure we send watermark events. + EXPECT_CALL(encoder_callbacks_, encoderBufferLimit()).WillRepeatedly(::testing::Return(2)); + MockHttpCache mock_http_cache; + auto mock_lookup_context = std::make_unique(); + auto mock_insert_context = std::make_unique(); + EXPECT_CALL(mock_http_cache, makeLookupContext(_, _)) + .WillOnce([&](LookupRequest&&, + Http::StreamDecoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_lookup_context); + }); + EXPECT_CALL(mock_http_cache, makeInsertContext(_, _)) + .WillOnce([&](LookupContextPtr&&, + Http::StreamEncoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_insert_context); + }); + EXPECT_CALL(*mock_lookup_context, getHeaders(_)).WillOnce([&](LookupHeadersCallback&& cb) { + cb(LookupResult{}); + }); + EXPECT_CALL(*mock_insert_context, insertHeaders(_, _, _, false)) + .WillOnce([&](const Http::ResponseHeaderMap&, const ResponseMetadata&, + InsertCallback insert_complete, bool) { insert_complete(true); }); + InsertCallback captured_insert_body_callback; + // The first time insertBody is called, block until the test is ready to call it. + // For completion chunk, complete immediately. + EXPECT_CALL(*mock_insert_context, insertBody(_, _, false)) + .WillOnce([&](const Buffer::Instance&, InsertCallback ready_for_next_chunk, bool) { + EXPECT_THAT(captured_insert_body_callback, IsNull()); + captured_insert_body_callback = ready_for_next_chunk; + }); + EXPECT_CALL(*mock_insert_context, insertBody(_, _, true)) + .WillOnce([&](const Buffer::Instance&, InsertCallback ready_for_next_chunk, bool) { + ready_for_next_chunk(true); + }); + EXPECT_CALL(*mock_insert_context, onDestroy()); + EXPECT_CALL(*mock_lookup_context, onDestroy()); + { + CacheFilterSharedPtr filter = makeFilter(mock_http_cache); + testDecodeRequestMiss(filter); + + // Encode response. + response_headers_.setContentLength(body1.size() + body2.size()); + EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); + // The insertHeaders callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); + + EXPECT_CALL(encoder_callbacks_, onEncoderFilterAboveWriteBufferHighWatermark()); + // Write the body in two pieces - the first one should exceed the watermark and + // send a high watermark event. + Buffer::OwnedImpl body1buf(body1); + Buffer::OwnedImpl body2buf(body2); + EXPECT_EQ(filter->encodeData(body1buf, false), Http::FilterDataStatus::Continue); + EXPECT_EQ(filter->encodeData(body2buf, true), Http::FilterDataStatus::Continue); + ASSERT_THAT(captured_insert_body_callback, NotNull()); + // When the cache releases, a low watermark event should be sent. + EXPECT_CALL(encoder_callbacks_, onEncoderFilterBelowWriteBufferLowWatermark()); + captured_insert_body_callback(true); + // The cache insertBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); + + filter->onStreamComplete(); + EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); + EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::InsertSucceeded)); + } +} + +TEST_F(CacheFilterTest, FilterDestroyedWhileWatermarkedSendsLowWatermarkEvent) { + request_headers_.setHost("CacheHitWithBody"); + const std::string body1 = "abcde"; + const std::string body2 = "fghij"; + // Set the buffer limit to 2 bytes to ensure we send watermark events. + EXPECT_CALL(encoder_callbacks_, encoderBufferLimit()).WillRepeatedly(::testing::Return(2)); + MockHttpCache mock_http_cache; + auto mock_lookup_context = std::make_unique(); + auto mock_insert_context = std::make_unique(); + EXPECT_CALL(mock_http_cache, makeLookupContext(_, _)) + .WillOnce([&](LookupRequest&&, + Http::StreamDecoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_lookup_context); + }); + EXPECT_CALL(mock_http_cache, makeInsertContext(_, _)) + .WillOnce([&](LookupContextPtr&&, + Http::StreamEncoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_insert_context); + }); + EXPECT_CALL(*mock_lookup_context, getHeaders(_)).WillOnce([&](LookupHeadersCallback&& cb) { + cb(LookupResult{}); + }); + EXPECT_CALL(*mock_insert_context, insertHeaders(_, _, _, false)) + .WillOnce([&](const Http::ResponseHeaderMap&, const ResponseMetadata&, + InsertCallback insert_complete, bool) { insert_complete(true); }); + InsertCallback captured_insert_body_callback; + // The first time insertBody is called, block until the test is ready to call it. + // Cache aborts, so there is no second call. + EXPECT_CALL(*mock_insert_context, insertBody(_, _, false)) + .WillOnce([&](const Buffer::Instance&, InsertCallback ready_for_next_chunk, bool) { + EXPECT_THAT(captured_insert_body_callback, IsNull()); + captured_insert_body_callback = ready_for_next_chunk; + }); + EXPECT_CALL(*mock_insert_context, onDestroy()); + EXPECT_CALL(*mock_lookup_context, onDestroy()); + { + CacheFilterSharedPtr filter = makeFilter(mock_http_cache, false); + + testDecodeRequestMiss(filter); + + // Encode response. + response_headers_.setContentLength(body1.size() + body2.size()); + EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); + // The insertHeaders callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); + + EXPECT_CALL(encoder_callbacks_, onEncoderFilterAboveWriteBufferHighWatermark()); + // Write the body in two pieces - the first one should exceed the watermark and + // send a high watermark event. + Buffer::OwnedImpl body1buf(body1); + Buffer::OwnedImpl body2buf(body2); + EXPECT_EQ(filter->encodeData(body1buf, false), Http::FilterDataStatus::Continue); + EXPECT_EQ(filter->encodeData(body2buf, true), Http::FilterDataStatus::Continue); + ASSERT_THAT(captured_insert_body_callback, NotNull()); + // When the filter is destroyed, a low watermark event should be sent. + EXPECT_CALL(encoder_callbacks_, onEncoderFilterBelowWriteBufferLowWatermark()); filter->onDestroy(); + filter.reset(); + captured_insert_body_callback(false); + // The cache insertBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); + } +} + +TEST_F(CacheFilterTest, CacheInsertAbortedByCache) { + request_headers_.setHost("CacheHitWithBody"); + const std::string body = "abc"; + MockHttpCache mock_http_cache; + auto mock_lookup_context = std::make_unique(); + auto mock_insert_context = std::make_unique(); + EXPECT_CALL(mock_http_cache, makeLookupContext(_, _)) + .WillOnce([&](LookupRequest&&, + Http::StreamDecoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_lookup_context); + }); + EXPECT_CALL(mock_http_cache, makeInsertContext(_, _)) + .WillOnce([&](LookupContextPtr&&, + Http::StreamEncoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_insert_context); + }); + EXPECT_CALL(*mock_lookup_context, getHeaders(_)).WillOnce([&](LookupHeadersCallback&& cb) { + cb(LookupResult{}); + }); + EXPECT_CALL(*mock_insert_context, insertHeaders(_, _, _, false)) + .WillOnce([&](const Http::ResponseHeaderMap&, const ResponseMetadata&, + InsertCallback insert_complete, bool) { insert_complete(true); }); + EXPECT_CALL(*mock_insert_context, insertBody(_, _, true)) + .WillOnce([&](const Buffer::Instance&, InsertCallback ready_for_next_chunk, bool) { + ready_for_next_chunk(false); + }); + EXPECT_CALL(*mock_insert_context, onDestroy()); + EXPECT_CALL(*mock_lookup_context, onDestroy()); + { + // Create filter for request 1. + CacheFilterSharedPtr filter = makeFilter(mock_http_cache); + + testDecodeRequestMiss(filter); + + // Encode response. + Buffer::OwnedImpl buffer(body); + response_headers_.setContentLength(body.size()); + EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter->encodeData(buffer, true), Http::FilterDataStatus::Continue); + // The cache insertBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); + + filter->onStreamComplete(); + EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); + EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::InsertAbortedByCache)); + } +} + +TEST_F(CacheFilterTest, FilterDeletedWhileIncompleteCacheWriteInQueueShouldAbandonWrite) { + request_headers_.setHost("CacheHitWithBody"); + const std::string body = "abc"; + MockHttpCache mock_http_cache; + auto mock_lookup_context = std::make_unique(); + auto mock_insert_context = std::make_unique(); + EXPECT_CALL(mock_http_cache, makeLookupContext(_, _)) + .WillOnce([&](LookupRequest&&, + Http::StreamDecoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_lookup_context); + }); + EXPECT_CALL(mock_http_cache, makeInsertContext(_, _)) + .WillOnce([&](LookupContextPtr&&, + Http::StreamEncoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_insert_context); + }); + EXPECT_CALL(*mock_lookup_context, getHeaders(_)).WillOnce([&](LookupHeadersCallback&& cb) { + cb(LookupResult{}); + }); + InsertCallback captured_insert_header_callback; + EXPECT_CALL(*mock_insert_context, insertHeaders(_, _, _, false)) + .WillOnce([&](const Http::ResponseHeaderMap&, const ResponseMetadata&, + InsertCallback insert_complete, + bool) { captured_insert_header_callback = insert_complete; }); + EXPECT_CALL(*mock_insert_context, onDestroy()); + EXPECT_CALL(*mock_lookup_context, onDestroy()); + { + // Create filter for request 1. + CacheFilterSharedPtr filter = makeFilter(mock_http_cache); + + testDecodeRequestMiss(filter); + + // Encode header of response. + response_headers_.setContentLength(body.size()); + EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); + // Destroy the filter prematurely. } + ASSERT_THAT(captured_insert_header_callback, NotNull()); + captured_insert_header_callback(true); + // The callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked, + // where it should now do nothing due to the filter being destroyed. + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(CacheFilterTest, FilterDeletedWhileCompleteCacheWriteInQueueShouldContinueWrite) { + request_headers_.setHost("CacheHitWithBody"); + const std::string body = "abc"; + MockHttpCache mock_http_cache; + auto mock_lookup_context = std::make_unique(); + auto mock_insert_context = std::make_unique(); + EXPECT_CALL(mock_http_cache, makeLookupContext(_, _)) + .WillOnce([&](LookupRequest&&, + Http::StreamDecoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_lookup_context); + }); + EXPECT_CALL(mock_http_cache, makeInsertContext(_, _)) + .WillOnce([&](LookupContextPtr&&, + Http::StreamEncoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_insert_context); + }); + EXPECT_CALL(*mock_lookup_context, getHeaders(_)).WillOnce([&](LookupHeadersCallback&& cb) { + cb(LookupResult{}); + }); + InsertCallback captured_insert_header_callback; + InsertCallback captured_insert_body_callback; + EXPECT_CALL(*mock_insert_context, insertHeaders(_, _, _, false)) + .WillOnce([&](const Http::ResponseHeaderMap&, const ResponseMetadata&, + InsertCallback insert_complete, + bool) { captured_insert_header_callback = insert_complete; }); + EXPECT_CALL(*mock_insert_context, insertBody(_, _, true)) + .WillOnce([&](const Buffer::Instance&, InsertCallback ready_for_next_chunk, bool) { + captured_insert_body_callback = ready_for_next_chunk; + }); + EXPECT_CALL(*mock_insert_context, onDestroy()); + EXPECT_CALL(*mock_lookup_context, onDestroy()); + { + // Create filter for request 1. + CacheFilterSharedPtr filter = makeFilter(mock_http_cache); + + testDecodeRequestMiss(filter); + + // Encode response. + Buffer::OwnedImpl buffer(body); + response_headers_.setContentLength(body.size()); + EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); + EXPECT_EQ(filter->encodeData(buffer, true), Http::FilterDataStatus::Continue); + } + // Header callback should be captured, body callback should not yet since the + // queue has not reached that chunk. + ASSERT_THAT(captured_insert_header_callback, NotNull()); + ASSERT_THAT(captured_insert_body_callback, IsNull()); + // The callback should be posted to the dispatcher. + captured_insert_header_callback(true); + // Run events on the dispatcher so that the callback is invoked, + // where it should now proceed to write the body chunk, since the + // write is still completable. + dispatcher_->run(Event::Dispatcher::RunType::Block); + // So the mock should now be writing the body. + ASSERT_THAT(captured_insert_body_callback, NotNull()); + captured_insert_body_callback(true); + // The callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked, + // where it should now do nothing due to the filter being destroyed. + dispatcher_->run(Event::Dispatcher::RunType::Block); } TEST_F(CacheFilterTest, SuccessfulValidation) { @@ -371,11 +667,12 @@ TEST_F(CacheFilterTest, SuccessfulValidation) { response_headers_.setContentLength(body.size()); EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); EXPECT_EQ(filter->encodeData(buffer, true), Http::FilterDataStatus::Continue); + // The cache getBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); - - filter->onDestroy(); } waitBeforeSecondRequest(); { @@ -434,8 +731,6 @@ TEST_F(CacheFilterTest, SuccessfulValidation) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::StaleHitWithSuccessfulValidation)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::HeaderUpdate)); - - filter->onDestroy(); } } @@ -459,11 +754,12 @@ TEST_F(CacheFilterTest, UnsuccessfulValidation) { response_headers_.setContentLength(body.size()); EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); EXPECT_EQ(filter->encodeData(buffer, true), Http::FilterDataStatus::Continue); + // The cache getBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); - - filter->onDestroy(); } waitBeforeSecondRequest(); { @@ -507,8 +803,6 @@ TEST_F(CacheFilterTest, UnsuccessfulValidation) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::StaleHitWithFailedValidation)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::InsertSucceeded)); - - filter->onDestroy(); } } @@ -527,11 +821,12 @@ TEST_F(CacheFilterTest, SingleSatisfiableRange) { response_headers_.setContentLength(body.size()); EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); EXPECT_EQ(filter->encodeData(buffer, true), Http::FilterDataStatus::Continue); + // The cache getBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); - - filter->onDestroy(); } waitBeforeSecondRequest(); { @@ -569,8 +864,6 @@ TEST_F(CacheFilterTest, SingleSatisfiableRange) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheHit)); EXPECT_THAT(insertStatus(), IsOkAndHolds(InsertStatus::NoInsertCacheHit)); - - filter->onDestroy(); } } @@ -589,11 +882,12 @@ TEST_F(CacheFilterTest, MultipleSatisfiableRanges) { response_headers_.setContentLength(body.size()); EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); EXPECT_EQ(filter->encodeData(buffer, true), Http::FilterDataStatus::Continue); + // The cache getBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); - - filter->onDestroy(); } waitBeforeSecondRequest(); { @@ -627,8 +921,6 @@ TEST_F(CacheFilterTest, MultipleSatisfiableRanges) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheHit)); - - filter->onDestroy(); } } @@ -647,11 +939,12 @@ TEST_F(CacheFilterTest, NotSatisfiableRange) { response_headers_.setContentLength(body.size()); EXPECT_EQ(filter->encodeHeaders(response_headers_, false), Http::FilterHeadersStatus::Continue); EXPECT_EQ(filter->encodeData(buffer, true), Http::FilterDataStatus::Continue); + // The cache getBody callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); - - filter->onDestroy(); } waitBeforeSecondRequest(); { @@ -691,8 +984,6 @@ TEST_F(CacheFilterTest, NotSatisfiableRange) { ::testing::Mock::VerifyAndClearExpectations(&decoder_callbacks_); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheHit)); - - filter->onDestroy(); } } @@ -713,8 +1004,6 @@ TEST_F(CacheFilterTest, GetRequestWithBodyAndTrailers) { EXPECT_EQ(filter->encodeHeaders(response_headers_, true), Http::FilterHeadersStatus::Continue); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::RequestNotCacheable)); - - filter->onDestroy(); } } @@ -739,8 +1028,6 @@ TEST_F(CacheFilterTest, FilterDeletedBeforePostedCallbackExecuted) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); - - filter->onDestroy(); } { // Create filter for request 2. @@ -752,21 +1039,18 @@ TEST_F(CacheFilterTest, FilterDeletedBeforePostedCallbackExecuted) { Http::FilterHeadersStatus::StopAllIterationAndWatermark); // Destroy the filter - filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::RequestIncomplete)); - filter->onDestroy(); - filter.reset(); + } - // Make sure that onHeaders was not called by making sure no decoder callbacks were made. - EXPECT_CALL(decoder_callbacks_, continueDecoding).Times(0); - EXPECT_CALL(decoder_callbacks_, encodeHeaders_).Times(0); + // Make sure that onHeaders was not called by making sure no decoder callbacks were made. + EXPECT_CALL(decoder_callbacks_, continueDecoding).Times(0); + EXPECT_CALL(decoder_callbacks_, encodeHeaders_).Times(0); - // Run events on the dispatcher so that the callback is invoked after the filter deletion. - dispatcher_->run(Event::Dispatcher::RunType::Block); + // Run events on the dispatcher so that the callback is invoked after the filter deletion. + dispatcher_->run(Event::Dispatcher::RunType::Block); - ::testing::Mock::VerifyAndClearExpectations(&decoder_callbacks_); - } + ::testing::Mock::VerifyAndClearExpectations(&decoder_callbacks_); } TEST_F(CacheFilterTest, LocalReplyDuringLookup) { @@ -782,8 +1066,6 @@ TEST_F(CacheFilterTest, LocalReplyDuringLookup) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); - - filter->onDestroy(); } { // Create filter for request 2. @@ -809,8 +1091,6 @@ TEST_F(CacheFilterTest, LocalReplyDuringLookup) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::RequestIncomplete)); - filter->onDestroy(); - filter.reset(); } } @@ -831,10 +1111,7 @@ TEST_F(CacheFilterDeathTest, StreamTimeoutDuringLookup) { filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::CacheMiss)); - - filter->onDestroy(); } - Envoy::Http::TestResponseHeaderMapImpl local_response_headers{{":status", "408"}}; EXPECT_ENVOY_BUG( { @@ -845,28 +1122,27 @@ TEST_F(CacheFilterDeathTest, StreamTimeoutDuringLookup) { // callback to the dispatcher. EXPECT_EQ(filter->decodeHeaders(request_headers_, true), Http::FilterHeadersStatus::StopAllIterationAndWatermark); - // While the lookup callback is still on the dispatcher, simulate an idle timeout. - EXPECT_EQ(filter->encodeHeaders(local_response_headers, true), - Envoy::Http::FilterHeadersStatus::Continue); // Make sure that the filter doesn't try to encode the cached response after processing // the local reply. EXPECT_CALL(decoder_callbacks_, continueDecoding).Times(0); EXPECT_CALL(decoder_callbacks_, encodeHeaders_).Times(0); - // Run events on the dispatcher so that the lookup callback is invoked after the local - // reply. + // While the lookup callback is still on the dispatcher, simulate an idle timeout. + EXPECT_EQ(filter->encodeHeaders(local_response_headers, true), + Envoy::Http::FilterHeadersStatus::Continue); + // As a death test when ENVOY_BUG crashes, as in debug builds, this will exit here, + // so we must not perform any required cleanup operations below this point in the block. + // When ENVOY_BUG does not crash, we can still validate additional things. dispatcher_->run(Event::Dispatcher::RunType::Block); filter->onStreamComplete(); EXPECT_THAT(lookupStatus(), IsOkAndHolds(LookupStatus::RequestIncomplete)); - - dispatcher_->run(Event::Dispatcher::RunType::Block); - - filter->onDestroy(); - filter.reset(); }, "Request timed out while cache lookup was outstanding."); + + // Clear out captured lookup lambdas from the dispatcher. + dispatcher_->run(Event::Dispatcher::RunType::Block); } class LookupStatusTest diff --git a/test/extensions/filters/http/cache/mocks.h b/test/extensions/filters/http/cache/mocks.h new file mode 100644 index 000000000000..2539dd914e62 --- /dev/null +++ b/test/extensions/filters/http/cache/mocks.h @@ -0,0 +1,46 @@ +#include "source/extensions/filters/http/cache/http_cache.h" + +#include "gmock/gmock.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Cache { + +class MockHttpCache : public HttpCache { +public: + MOCK_METHOD(LookupContextPtr, makeLookupContext, + (LookupRequest && request, Http::StreamDecoderFilterCallbacks& callbacks)); + MOCK_METHOD(InsertContextPtr, makeInsertContext, + (LookupContextPtr && lookup_context, Http::StreamEncoderFilterCallbacks& callbacks)); + MOCK_METHOD(void, updateHeaders, + (const LookupContext& lookup_context, const Http::ResponseHeaderMap& response_headers, + const ResponseMetadata& metadata, std::function on_complete)); + MOCK_METHOD(CacheInfo, cacheInfo, (), (const)); +}; + +class MockLookupContext : public LookupContext { +public: + MOCK_METHOD(void, getHeaders, (LookupHeadersCallback && cb)); + MOCK_METHOD(void, getBody, (const AdjustedByteRange& range, LookupBodyCallback&& cb)); + MOCK_METHOD(void, getTrailers, (LookupTrailersCallback && cb)); + MOCK_METHOD(void, onDestroy, ()); +}; + +class MockInsertContext : public InsertContext { +public: + MOCK_METHOD(void, insertHeaders, + (const Http::ResponseHeaderMap& response_headers, const ResponseMetadata& metadata, + InsertCallback insert_complete, bool end_stream)); + MOCK_METHOD(void, insertBody, + (const Buffer::Instance& fragment, InsertCallback ready_for_next_fragment, + bool end_stream)); + MOCK_METHOD(void, insertTrailers, + (const Http::ResponseTrailerMap& trailers, InsertCallback insert_complete)); + MOCK_METHOD(void, onDestroy, ()); +}; + +} // namespace Cache +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/csrf/BUILD b/test/extensions/filters/http/csrf/BUILD index 6e618a50641c..913a61c7c447 100644 --- a/test/extensions/filters/http/csrf/BUILD +++ b/test/extensions/filters/http/csrf/BUILD @@ -32,9 +32,6 @@ envoy_extension_cc_test( size = "large", srcs = ["csrf_filter_integration_test.cc"], extension_names = ["envoy.filters.http.csrf"], - # TODO(kbaichoo): remove when deferred processing is enabled by default and the - # test is no longer parameterized by it. - shard_count = 4, deps = [ "//source/extensions/filters/http/csrf:config", "//test/config:utility_lib", diff --git a/test/extensions/filters/http/custom_response/BUILD b/test/extensions/filters/http/custom_response/BUILD index 01c7daba1d5c..704c20d197f4 100644 --- a/test/extensions/filters/http/custom_response/BUILD +++ b/test/extensions/filters/http/custom_response/BUILD @@ -81,7 +81,7 @@ envoy_extension_cc_test( "custom_response_integration_test.cc", ], extension_names = ["envoy.filters.http.custom_response"], - shard_count = 4, + shard_count = 2, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc index d632e083e978..df443ae619cc 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -230,11 +230,12 @@ name: envoy.clusters.dynamic_forward_proxy } } - void requestWithUnknownDomainTest(const std::string& typed_dns_resolver_config = "") { + void requestWithUnknownDomainTest(const std::string& typed_dns_resolver_config = "", + const std::string& hostname = "doesnotexist.example.com") { useAccessLog("%RESPONSE_CODE_DETAILS%"); initializeWithArgs(1024, 1024, "", typed_dns_resolver_config); codec_client_ = makeHttpConnection(lookupPort("http")); - default_request_headers_.setHost("doesnotexist.example.com"); + default_request_headers_.setHost(hostname); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); ASSERT_TRUE(response->waitForEndStream()); @@ -350,6 +351,13 @@ TEST_P(ProxyFilterIntegrationTest, RequestWithBodyGetAddrInfoResolver) { // a null address. Make sure this mode fails gracefully. TEST_P(ProxyFilterIntegrationTest, RequestWithUnknownDomain) { requestWithUnknownDomainTest(); } +// TODO(yanavlasov) Enable per #26642 +#ifndef ENVOY_ENABLE_UHV +TEST_P(ProxyFilterIntegrationTest, RequestWithSuspectDomain) { + requestWithUnknownDomainTest("", "\x00\x00.google.com"); +} +#endif + // Do a sanity check using the getaddrinfo() resolver. TEST_P(ProxyFilterIntegrationTest, RequestWithUnknownDomainGetAddrInfoResolver) { requestWithUnknownDomainTest(R"EOF( diff --git a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc index 1881e6766c52..8b9e95f64b0a 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc @@ -374,7 +374,9 @@ class ExtAuthzGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, cleanupUpstreamAndDownstream(); } - const std::string expectedCheckRequest(Http::CodecType downstream_protocol) { + const std::string + expectedCheckRequest(Http::CodecType downstream_protocol, + absl::optional override_expected_size = absl::nullopt) { const std::string expected_downstream_protocol = downstream_protocol == Http::CodecType::HTTP1 ? "HTTP/1.1" : "HTTP/2"; constexpr absl::string_view expected_format = R"EOF( @@ -389,7 +391,9 @@ class ExtAuthzGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, protocol: %s )EOF"; - return absl::StrFormat(expected_format, request_body_.length(), expectedRequestBody(), + uint64_t expected_size = override_expected_size.has_value() ? override_expected_size.value() + : request_body_.length(); + return absl::StrFormat(expected_format, expected_size, expectedRequestBody(), expected_downstream_protocol); } @@ -769,6 +773,59 @@ TEST_P(ExtAuthzGrpcIntegrationTest, DenyAtDisableWithMetadata) { expectFilterDisableCheck(/*deny_at_disable=*/true, /*disable_with_metadata=*/true, "403"); } +TEST_P(ExtAuthzGrpcIntegrationTest, CheckAfterBufferingComplete) { + // Set up ext_authz filter. + initializeConfig(); + + // Use h1, set up the test. + setDownstreamProtocol(Http::CodecType::HTTP1); + HttpIntegrationTest::initialize(); + + // Start a client connection and start request. + Http::TestRequestHeaderMapImpl headers{ + {":method", "POST"}, {":path", "/test"}, + {":scheme", "http"}, {":authority", "host"}, + {"x-duplicate", "one"}, {"x-duplicate", "two"}, + {"x-duplicate", "three"}, {"allowed-prefix-one", "one"}, + {"allowed-prefix-two", "two"}, {"not-allowed", "nope"}, + {"regex-food", "food"}, {"regex-fool", "fool"}}; + + auto conn = makeClientConnection(lookupPort("http")); + codec_client_ = makeHttpConnection(std::move(conn)); + + auto encoder_decoder = codec_client_->startRequest(headers); + response_ = std::move(encoder_decoder.second); + + // Split the request body into two parts + TestUtility::feedBufferWithRandomCharacters(request_body_, 10000); + std::string initial_body = request_body_.toString().substr(0, 2000); + std::string final_body = request_body_.toString().substr(2000, 8000); + + // Send the first part of the request body without ending the stream + codec_client_->sendData(encoder_decoder.first, initial_body, false); + + // Expect that since the buffer is full, the request is sent to the authorization server + waitForExtAuthzRequest(expectedCheckRequest(Http::CodecType::HTTP1, 2000)); + + sendExtAuthzResponse(Headers{}, Headers{}, Headers{}, Http::TestRequestHeaderMapImpl{}, + Http::TestRequestHeaderMapImpl{}, Headers{}, Headers{}); + + // Send the rest of the data and end the stream + codec_client_->sendData(encoder_decoder.first, final_body, true); + + // Expect a 200 OK response + waitForSuccessfulUpstreamResponse("200"); + + const std::string expected_body(response_size_, 'a'); + verifyResponse(std::move(response_), "200", + Http::TestResponseHeaderMapImpl{{":status", "200"}, + {"replaceable", "set-by-upstream"}, + {"set-cookie", "cookie1=snickerdoodle"}}, + expected_body); + + cleanup(); +} + TEST_P(ExtAuthzGrpcIntegrationTest, DownstreamHeadersOnSuccess) { // Set up ext_authz filter. initializeConfig(); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 1f27c85e5498..dbfe4b863432 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -586,6 +586,8 @@ TEST_F(HttpFilterTest, RequestDataWithPartialMessage) { ON_CALL(decoder_filter_callbacks_, connection()) .WillByDefault(Return(OptRef{connection_})); ON_CALL(decoder_filter_callbacks_, decodingBuffer()).WillByDefault(Return(&data_)); + ON_CALL(decoder_filter_callbacks_, addDecodedData(_, _)) + .WillByDefault(Invoke([&](Buffer::Instance& data, bool) { data_.add(data); })); EXPECT_CALL(decoder_filter_callbacks_, setDecoderBufferLimit(_)).Times(0); connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr_); connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr_); @@ -594,17 +596,20 @@ TEST_F(HttpFilterTest, RequestDataWithPartialMessage) { EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); - data_.add("foo"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(data_, false)); + Buffer::OwnedImpl buffer1("foo"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(buffer1, false)); + data_.add(buffer1.toString()); - data_.add("bar"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(data_, false)); + Buffer::OwnedImpl buffer2("bar"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(buffer2, false)); + data_.add(buffer2.toString()); - data_.add("barfoo"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, false)); + Buffer::OwnedImpl buffer3("barfoo"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(buffer3, false)); + data_.add(buffer3.toString()); - data_.add("more data after watermark is set is possible"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, true)); + Buffer::OwnedImpl buffer4("more data after watermark is set is possible"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(buffer4, true)); EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter_->decodeTrailers(request_trailers_)); } @@ -629,6 +634,8 @@ TEST_F(HttpFilterTest, RequestDataWithPartialMessageThenContinueDecoding) { ON_CALL(decoder_filter_callbacks_, connection()) .WillByDefault(Return(OptRef{connection_})); ON_CALL(decoder_filter_callbacks_, decodingBuffer()).WillByDefault(Return(&data_)); + ON_CALL(decoder_filter_callbacks_, addDecodedData(_, _)) + .WillByDefault(Invoke([&](Buffer::Instance& data, bool) { data_.add(data); })); EXPECT_CALL(decoder_filter_callbacks_, setDecoderBufferLimit(_)).Times(0); connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr_); connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr_); @@ -645,24 +652,28 @@ TEST_F(HttpFilterTest, RequestDataWithPartialMessageThenContinueDecoding) { EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); - data_.add("foo"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(data_, false)); + Buffer::OwnedImpl buffer1("foo"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(buffer1, false)); + data_.add(buffer1.toString()); - data_.add("bar"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(data_, false)); + Buffer::OwnedImpl buffer2("bar"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(buffer2, false)); + data_.add(buffer2.toString()); - data_.add("barfoo"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, false)); + Buffer::OwnedImpl buffer3("barfoo"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(buffer3, false)); + // data added by the previous decodeData call. - data_.add("more data after watermark is set is possible"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, false)); + Buffer::OwnedImpl buffer4("more data after watermark is set is possible"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(buffer4, false)); + data_.add(buffer4.toString()); Filters::Common::ExtAuthz::Response response{}; response.status = Filters::Common::ExtAuthz::CheckStatus::OK; request_callbacks_->onComplete(std::make_unique(response)); - data_.add("more data after calling check request"); - EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, true)); + Buffer::OwnedImpl buffer5("more data after calling check request"); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer5, true)); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); } @@ -686,6 +697,8 @@ TEST_F(HttpFilterTest, RequestDataWithSmallBuffer) { ON_CALL(decoder_filter_callbacks_, connection()) .WillByDefault(Return(OptRef{connection_})); ON_CALL(decoder_filter_callbacks_, decodingBuffer()).WillByDefault(Return(&data_)); + ON_CALL(decoder_filter_callbacks_, addDecodedData(_, _)) + .WillByDefault(Invoke([&](Buffer::Instance& data, bool) { data_.add(data); })); EXPECT_CALL(decoder_filter_callbacks_, setDecoderBufferLimit(_)).Times(0); connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr_); connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr_); @@ -694,8 +707,9 @@ TEST_F(HttpFilterTest, RequestDataWithSmallBuffer) { EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); - data_.add("foo"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(data_, false)); + Buffer::OwnedImpl buffer("foo"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(buffer, false)); + data_.add(buffer.toString()); EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter_->decodeTrailers(request_trailers_)); } @@ -713,6 +727,8 @@ TEST_F(HttpFilterTest, AuthWithRequestData) { )EOF"); ON_CALL(decoder_filter_callbacks_, decodingBuffer()).WillByDefault(Return(&data_)); + ON_CALL(decoder_filter_callbacks_, addDecodedData(_, _)) + .WillByDefault(Invoke([&](Buffer::Instance& data, bool) { data_.add(data); })); prepareCheck(); envoy::service::auth::v3::CheckRequest check_request; @@ -726,10 +742,15 @@ TEST_F(HttpFilterTest, AuthWithRequestData) { EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); - data_.add("foo"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(data_, false)); - data_.add("bar"); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, true)); + + Buffer::OwnedImpl buffer1("foo"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(buffer1, false)); + data_.add(buffer1.toString()); + + Buffer::OwnedImpl buffer2("bar"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(buffer2, true)); + // data added by the previous decodeData call. + EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(data_.length(), check_request.attributes().request().http().body().size()); @@ -751,6 +772,8 @@ TEST_F(HttpFilterTest, AuthWithNonUtf8RequestData) { )EOF"); ON_CALL(decoder_filter_callbacks_, decodingBuffer()).WillByDefault(Return(&data_)); + ON_CALL(decoder_filter_callbacks_, addDecodedData(_, _)) + .WillByDefault(Invoke([&](Buffer::Instance& data, bool) { data_.add(data); })); prepareCheck(); envoy::service::auth::v3::CheckRequest check_request; @@ -768,10 +791,13 @@ TEST_F(HttpFilterTest, AuthWithNonUtf8RequestData) { uint8_t raw[1] = {0xc0}; Buffer::OwnedImpl raw_buffer(raw, 1); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(raw_buffer, false)); data_.add(raw_buffer); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(data_, false)); - data_.add(raw_buffer); - EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, true)); + + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, + filter_->decodeData(raw_buffer, true)); + // data added by the previous decodeData call. + EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter_->decodeTrailers(request_trailers_)); EXPECT_EQ(0, check_request.attributes().request().http().body().size()); diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 4f6af31bc0f7..2fe5e47a3763 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -123,6 +123,7 @@ envoy_extension_cc_test( "cpu:3", ], deps = [ + ":logging_test_filter_lib", ":utils_lib", "//source/extensions/filters/http/ext_proc:config", "//test/common/http:common_lib", @@ -272,3 +273,27 @@ envoy_extension_cc_test( "@envoy_api//envoy/service/ext_proc/v3:pkg_cc_proto", ], ) + +envoy_proto_library( + name = "logging_test_filter_proto", + srcs = ["logging_test_filter.proto"], +) + +envoy_extension_cc_test_library( + name = "logging_test_filter_lib", + srcs = [ + "logging_test_filter.cc", + ], + extension_names = ["envoy.filters.http.ext_proc"], + deps = [ + ":logging_test_filter_proto_cc_proto", + "//envoy/http:filter_interface", + "//envoy/registry", + "//envoy/server:filter_config_interface", + "//source/common/router:string_accessor_lib", + "//source/extensions/filters/http/common:factory_base_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "//source/extensions/filters/http/ext_proc", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/filters/http/ext_proc/client_test.cc b/test/extensions/filters/http/ext_proc/client_test.cc index 85f20b837942..1099ae88fc54 100644 --- a/test/extensions/filters/http/ext_proc/client_test.cc +++ b/test/extensions/filters/http/ext_proc/client_test.cc @@ -60,6 +60,7 @@ class ExtProcStreamTest : public testing::Test, public ExternalProcessorCallback void onGrpcError(Grpc::Status::GrpcStatus status) override { grpc_status_ = status; } void onGrpcClose() override { grpc_closed_ = true; } + void logGrpcStreamInfo() override {} std::unique_ptr last_response_; Grpc::Status::GrpcStatus grpc_status_ = Grpc::Status::WellKnownGrpcStatus::Ok; diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 32e7b9ffd059..deea1443f7e0 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -7,6 +7,8 @@ #include "source/extensions/filters/http/ext_proc/config.h" #include "test/common/http/common.h" +#include "test/extensions/filters/http/ext_proc/logging_test_filter.pb.h" +#include "test/extensions/filters/http/ext_proc/logging_test_filter.pb.validate.h" #include "test/extensions/filters/http/ext_proc/utils.h" #include "test/integration/http_integration.h" #include "test/test_common/test_runtime.h" @@ -42,6 +44,11 @@ using Http::LowerCaseString; using namespace std::chrono_literals; +struct ConfigOptions { + bool valid_grpc_server = true; + bool add_logging_filter = false; +}; + // These tests exercise the ext_proc filter through Envoy's integration test // environment by configuring an instance of the Envoy server and driving it // through the mock network stack. @@ -67,14 +74,14 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, cleanupUpstreamAndDownstream(); } - void initializeConfig(bool valid_grpc_server = true) { + void initializeConfig(ConfigOptions config_option = {}) { scoped_runtime_.mergeValues( {{"envoy.reloadable_features.send_header_raw_value", header_raw_value_}}); scoped_runtime_.mergeValues( {{"envoy_reloadable_features_immediate_response_use_filter_mutation_rule", filter_mutation_rule_}}); - config_helper_.addConfigModifier([this, valid_grpc_server]( + config_helper_.addConfigModifier([this, config_option]( envoy::config::bootstrap::v3::Bootstrap& bootstrap) { // Ensure "HTTP2 with no prior knowledge." Necessary for gRPC and for headers ConfigHelper::setHttp2( @@ -89,24 +96,39 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, server_cluster->mutable_load_assignment()->set_cluster_name(cluster_name); } - if (valid_grpc_server) { + const std::string valid_grpc_cluster_name = "ext_proc_server_0"; + if (config_option.valid_grpc_server) { // Load configuration of the server from YAML and use a helper to add a grpc_service // stanza pointing to the cluster that we just made - setGrpcService(*proto_config_.mutable_grpc_service(), "ext_proc_server_0", + setGrpcService(*proto_config_.mutable_grpc_service(), valid_grpc_cluster_name, grpc_upstreams_[0]->localAddress()); } else { // Set up the gRPC service with wrong cluster name and address. setGrpcService(*proto_config_.mutable_grpc_service(), "ext_proc_wrong_server", std::make_shared("127.0.0.1", 1234)); } - // Construct a configuration proto for our filter and then re-write it // to JSON so that we can add it to the overall config envoy::config::listener::v3::Filter ext_proc_filter; - ext_proc_filter.set_name("envoy.filters.http.ext_proc"); + std::string ext_proc_filter_name = "envoy.filters.http.ext_proc"; + ext_proc_filter.set_name(ext_proc_filter_name); ext_proc_filter.mutable_typed_config()->PackFrom(proto_config_); config_helper_.prependFilter(MessageUtil::getJsonStringFromMessageOrError(ext_proc_filter)); + // Add logging test filter only in Envoy gRPC mode. + // gRPC side stream logging is only supported in Envoy gRPC mode at the moment. + if (clientType() == Grpc::ClientType::EnvoyGrpc && config_option.add_logging_filter && + config_option.valid_grpc_server) { + test::integration::filters::LoggingTestFilterConfig logging_filter_config; + logging_filter_config.set_logging_id(ext_proc_filter_name); + logging_filter_config.set_upstream_cluster_name(valid_grpc_cluster_name); + envoy::config::listener::v3::Filter logging_filter; + logging_filter.set_name("logging-test-filter"); + logging_filter.mutable_typed_config()->PackFrom(logging_filter_config); + + config_helper_.prependFilter(MessageUtil::getJsonStringFromMessageOrError(logging_filter)); + } + // Parameterize with defer processing to prevent bit rot as filter made // assumptions of data flow, prior relying on eager processing. config_helper_.addRuntimeOverride(Runtime::defer_processing_backedup_streams, @@ -440,6 +462,23 @@ TEST_P(ExtProcIntegrationTest, GetAndCloseStream) { verifyDownstreamResponse(*response, 200); } +TEST_P(ExtProcIntegrationTest, GetAndCloseStreamWithLogging) { + ConfigOptions config_option = {}; + config_option.add_logging_filter = true; + initializeConfig(config_option); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + + ProcessingRequest request_headers_msg; + waitForFirstMessage(*grpc_upstreams_[0], request_headers_msg); + // Just close the stream without doing anything + processor_stream_->startGrpcStream(); + processor_stream_->finishGrpcStream(Grpc::Status::Ok); + + handleUpstreamRequest(); + verifyDownstreamResponse(*response, 200); +} + // Test the filter using the default configuration by connecting to // an ext_proc server that responds to the request_headers message // by returning a failure before the first stream response can be sent. @@ -455,9 +494,25 @@ TEST_P(ExtProcIntegrationTest, GetAndFailStream) { verifyDownstreamResponse(*response, 500); } +TEST_P(ExtProcIntegrationTest, GetAndFailStreamWithLogging) { + ConfigOptions config_option = {}; + config_option.add_logging_filter = true; + initializeConfig(config_option); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + + ProcessingRequest request_headers_msg; + waitForFirstMessage(*grpc_upstreams_[0], request_headers_msg); + // Fail the stream immediately + processor_stream_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "500"}}, true); + verifyDownstreamResponse(*response, 500); +} + // Test the filter connecting to an invalid ext_proc server that will result in open stream failure. TEST_P(ExtProcIntegrationTest, GetAndFailStreamWithInvalidSever) { - initializeConfig(/*valid_grpc_server=*/false); + ConfigOptions config_option = {}; + config_option.valid_grpc_server = false; + initializeConfig(config_option); HttpIntegrationTest::initialize(); auto response = sendDownstreamRequest(absl::nullopt); ProcessingRequest request_headers_msg; @@ -601,6 +656,42 @@ TEST_P(ExtProcIntegrationTest, GetAndSetHeaders) { verifyDownstreamResponse(*response, 200); } +TEST_P(ExtProcIntegrationTest, GetAndSetHeadersWithLogging) { + ConfigOptions config_option = {}; + config_option.add_logging_filter = true; + initializeConfig(config_option); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest( + [](Http::HeaderMap& headers) { headers.addCopy(LowerCaseString("x-remove-this"), "yes"); }); + + processRequestHeadersMessage( + *grpc_upstreams_[0], true, [](const HttpHeaders&, HeadersResponse& headers_resp) { + auto response_header_mutation = headers_resp.mutable_response()->mutable_header_mutation(); + auto* mut1 = response_header_mutation->add_set_headers(); + mut1->mutable_header()->set_key("x-new-header"); + mut1->mutable_header()->set_value("new"); + return true; + }); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); + + EXPECT_THAT(upstream_request_->headers(), SingleHeaderValueIs("x-new-header", "new")); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request_->encodeData(100, true); + + processResponseHeadersMessage( + *grpc_upstreams_[0], false, [](const HttpHeaders& headers, HeadersResponse&) { + Http::TestRequestHeaderMapImpl expected_response_headers{{":status", "200"}}; + EXPECT_THAT(headers.headers(), HeaderProtosEqual(expected_response_headers)); + return true; + }); + + verifyDownstreamResponse(*response, 200); +} + TEST_P(ExtProcIntegrationTest, GetAndSetHeadersNonUtf8WithValueInString) { initializeConfig(); HttpIntegrationTest::initialize(); @@ -989,7 +1080,8 @@ TEST_P(ExtProcIntegrationTest, GetAndSetBodyAndHeadersOnResponsePartialBuffered) // Should get just one message with the body processResponseBodyMessage( - *grpc_upstreams_[0], false, [](const HttpBody&, BodyResponse& body_resp) { + *grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse& body_resp) { + EXPECT_TRUE(body.end_of_stream()); auto* header_mut = body_resp.mutable_response()->mutable_header_mutation(); auto* header_add = header_mut->add_set_headers(); header_add->mutable_header()->set_key("x-testing-response-header"); @@ -1017,7 +1109,7 @@ TEST_P(ExtProcIntegrationTest, GetAndSetBodyAndHeadersAndTrailersOnResponse) { // Should get just one message with the body processResponseBodyMessage(*grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse&) { - EXPECT_TRUE(body.end_of_stream()); + EXPECT_FALSE(body.end_of_stream()); return true; }); @@ -1194,6 +1286,30 @@ TEST_P(ExtProcIntegrationTest, GetAndRespondImmediately) { EXPECT_THAT(response->headers(), SingleHeaderValueIs("content-type", "application/json")); EXPECT_EQ("{\"reason\": \"Not authorized\"}", response->body()); } +TEST_P(ExtProcIntegrationTest, GetAndRespondImmediatelyWithLogging) { + ConfigOptions config_option = {}; + config_option.add_logging_filter = true; + initializeConfig(config_option); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + + processAndRespondImmediately(*grpc_upstreams_[0], true, [](ImmediateResponse& immediate) { + immediate.mutable_status()->set_code(envoy::type::v3::StatusCode::Unauthorized); + immediate.set_body("{\"reason\": \"Not authorized\"}"); + immediate.set_details("Failed because you are not authorized"); + auto* hdr1 = immediate.mutable_headers()->add_set_headers(); + hdr1->mutable_header()->set_key("x-failure-reason"); + hdr1->mutable_header()->set_value("testing"); + auto* hdr2 = immediate.mutable_headers()->add_set_headers(); + hdr2->mutable_header()->set_key("content-type"); + hdr2->mutable_header()->set_value("application/json"); + }); + + verifyDownstreamResponse(*response, 401); + EXPECT_THAT(response->headers(), SingleHeaderValueIs("x-failure-reason", "testing")); + EXPECT_THAT(response->headers(), SingleHeaderValueIs("content-type", "application/json")); + EXPECT_EQ("{\"reason\": \"Not authorized\"}", response->body()); +} TEST_P(ExtProcIntegrationTest, GetAndRespondImmediatelyWithInvalidCharacter) { initializeConfig(); @@ -1424,7 +1540,8 @@ TEST_P(ExtProcIntegrationTest, GetAndIncorrectlyModifyHeaderOnBody) { auto response = sendDownstreamRequestWithBody("Original body", absl::nullopt); processRequestHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); processRequestBodyMessage( - *grpc_upstreams_[0], false, [](const HttpBody&, BodyResponse& response) { + *grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse& response) { + EXPECT_TRUE(body.end_of_stream()); auto* mut = response.mutable_response()->mutable_header_mutation()->add_set_headers(); mut->mutable_header()->set_key(":scheme"); mut->mutable_header()->set_value("tcp"); @@ -1445,7 +1562,8 @@ TEST_P(ExtProcIntegrationTest, GetAndIncorrectlyModifyHeaderOnBodyPartialBuffer) auto response = sendDownstreamRequestWithBody("Original body", absl::nullopt); processRequestHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); processRequestBodyMessage( - *grpc_upstreams_[0], false, [](const HttpBody&, BodyResponse& response) { + *grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse& response) { + EXPECT_TRUE(body.end_of_stream()); auto* mut = response.mutable_response()->mutable_header_mutation()->add_set_headers(); mut->mutable_header()->set_key(":scheme"); mut->mutable_header()->set_value("tcp"); @@ -1563,6 +1681,25 @@ TEST_P(ExtProcIntegrationTest, RequestMessageTimeout) { verifyDownstreamResponse(*response, 500); } +TEST_P(ExtProcIntegrationTest, RequestMessageTimeoutWithLogging) { + // ensure 200 ms timeout + proto_config_.mutable_message_timeout()->set_nanos(200000000); + ConfigOptions config_option = {}; + config_option.add_logging_filter = true; + initializeConfig(config_option); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + processRequestHeadersMessage(*grpc_upstreams_[0], true, + [this](const HttpHeaders&, HeadersResponse&) { + // Travel forward 400 ms + timeSystem().advanceTimeWaitImpl(400ms); + return false; + }); + + // We should immediately have an error response now + verifyDownstreamResponse(*response, 500); +} + // Same as the previous test but on the response path, since there are separate // timers for each. TEST_P(ExtProcIntegrationTest, ResponseMessageTimeout) { @@ -1584,6 +1721,27 @@ TEST_P(ExtProcIntegrationTest, ResponseMessageTimeout) { verifyDownstreamResponse(*response, 500); } +TEST_P(ExtProcIntegrationTest, ResponseMessageTimeoutWithLogging) { + // ensure 200 ms timeout + proto_config_.mutable_message_timeout()->set_nanos(200000000); + ConfigOptions config_option = {}; + config_option.add_logging_filter = true; + initializeConfig(config_option); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequest(absl::nullopt); + processRequestHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); + handleUpstreamRequest(); + processResponseHeadersMessage(*grpc_upstreams_[0], false, + [this](const HttpHeaders&, HeadersResponse&) { + // Travel forward 400 ms + timeSystem().advanceTimeWaitImpl(400ms); + return false; + }); + + // We should immediately have an error response now + verifyDownstreamResponse(*response, 500); +} + // Send a request, wait longer than the "message timeout" before sending a response // from the external processor, but nothing should happen because we are ignoring // the timeout. @@ -1678,6 +1836,25 @@ TEST_P(ExtProcIntegrationTest, BufferBodyOverridePostWithEmptyBody) { verifyDownstreamResponse(*response, 200); } +TEST_P(ExtProcIntegrationTest, BufferEmptyBodyNotSendingHeader) { + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::BUFFERED); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); + initializeConfig(); + HttpIntegrationTest::initialize(); + auto response = sendDownstreamRequestWithBody("", absl::nullopt); + + // We should get an empty body message this time + processRequestBodyMessage(*grpc_upstreams_[0], true, [](const HttpBody& body, BodyResponse&) { + EXPECT_TRUE(body.end_of_stream()); + EXPECT_EQ(body.body().size(), 0); + return true; + }); + + handleUpstreamRequest(); + verifyDownstreamResponse(*response, 200); +} + // Test how the filter responds when asked to buffer a response body for a POST // request with an empty body. We should get an empty body message because // the Envoy filter stream received the body after all the headers. @@ -1984,6 +2161,18 @@ TEST_P(ExtProcIntegrationTest, PerRouteGrpcService) { setGrpcService(*per_route.mutable_overrides()->mutable_grpc_service(), "ext_proc_server_1", grpc_upstreams_[1]->localAddress()); setPerRouteConfig(route, per_route); + + // Add logging test filter here in place since it has a different GrpcService from route. + if (clientType() == Grpc::ClientType::EnvoyGrpc) { + test::integration::filters::LoggingTestFilterConfig logging_filter_config; + logging_filter_config.set_logging_id("envoy.filters.http.ext_proc"); + logging_filter_config.set_upstream_cluster_name("ext_proc_server_1"); + envoy::config::listener::v3::Filter logging_filter; + logging_filter.set_name("logging-test-filter"); + logging_filter.mutable_typed_config()->PackFrom(logging_filter_config); + + config_helper_.prependFilter(MessageUtil::getJsonStringFromMessageOrError(logging_filter)); + } }); HttpIntegrationTest::initialize(); @@ -2163,9 +2352,9 @@ TEST_P(ExtProcIntegrationTest, RequestMessageNewTimeoutOutOfBounds) { newTimeoutWrongConfigTest(override_message_timeout); } -// Set the ext_proc filter in SKIP header, BUFFERED body mode. -// Send a request with headers and trailers. -TEST_P(ExtProcIntegrationTest, SendHeaderAndTrailerInBufferedMode) { +// Set the ext_proc filter in SKIP header, SEND trailer, and BUFFERED body mode. +// Send a request with headers and trailers. No body is sent to the ext_proc server. +TEST_P(ExtProcIntegrationTest, SkipHeaderSendTrailerInBufferedMode) { proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::BUFFERED); proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); proto_config_.mutable_processing_mode()->set_request_trailer_mode(ProcessingMode::SEND); @@ -2180,13 +2369,37 @@ TEST_P(ExtProcIntegrationTest, SendHeaderAndTrailerInBufferedMode) { IntegrationStreamDecoderPtr response = std::move(encoder_decoder.second); Http::TestRequestTrailerMapImpl request_trailers{{"request", "trailer"}}; codec_client_->sendTrailers(*request_encoder_, request_trailers); - processRequestBodyMessage(*grpc_upstreams_[0], true, absl::nullopt); - processRequestTrailersMessage(*grpc_upstreams_[0], false, absl::nullopt); + processRequestTrailersMessage(*grpc_upstreams_[0], true, absl::nullopt); handleUpstreamRequest(); processResponseHeadersMessage(*grpc_upstreams_[0], false, absl::nullopt); verifyDownstreamResponse(*response, 200); } +// Set the ext_proc filter processing mode to send request header, body and trailer. +// Then have the client send header and trailer. +TEST_P(ExtProcIntegrationTest, ClientSendHeaderTrailerFilterConfigedSendAll) { + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SEND); + proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::BUFFERED); + proto_config_.mutable_processing_mode()->set_request_trailer_mode(ProcessingMode::SEND); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); + initializeConfig(); + HttpIntegrationTest::initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + IntegrationStreamDecoderPtr response = std::move(encoder_decoder.second); + Http::TestRequestTrailerMapImpl request_trailers{{"request", "trailer"}}; + codec_client_->sendTrailers(*request_encoder_, request_trailers); + processRequestHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); + processRequestTrailersMessage(*grpc_upstreams_[0], false, absl::nullopt); + + handleUpstreamRequest(); + verifyDownstreamResponse(*response, 200); +} + // Test the filter with the header allow list set and disallow list empty and // verify only the allowed headers are sent to the ext_proc server. TEST_P(ExtProcIntegrationTest, GetAndSetHeadersAndTrailersWithAllowedHeader) { @@ -2404,7 +2617,8 @@ TEST_P(ExtProcIntegrationTest, GetAndSetBodyOnBothWithClearRouteCache) { }); upstream_request_->encodeData(100, true); processResponseBodyMessage( - *grpc_upstreams_[0], false, [](const HttpBody&, BodyResponse& body_resp) { + *grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse& body_resp) { + EXPECT_TRUE(body.end_of_stream()); auto* header_mut = body_resp.mutable_response()->mutable_header_mutation(); auto* header_add = header_mut->add_set_headers(); header_add->mutable_header()->set_key("x-testing-response-header"); @@ -2462,7 +2676,7 @@ TEST_P(ExtProcIntegrationTest, HeaderMutationCheckPassWithHcmSizeConfig) { }); processRequestBodyMessage( *grpc_upstreams_[0], false, [this](const HttpBody& body, BodyResponse& body_resp) { - EXPECT_TRUE(body.end_of_stream()); + EXPECT_FALSE(body.end_of_stream()); addMutationSetHeaders(20, *body_resp.mutable_response()->mutable_header_mutation()); return true; }); @@ -2480,7 +2694,7 @@ TEST_P(ExtProcIntegrationTest, HeaderMutationCheckPassWithHcmSizeConfig) { }); processResponseBodyMessage( *grpc_upstreams_[0], false, [this](const HttpBody& body, BodyResponse& body_resp) { - EXPECT_TRUE(body.end_of_stream()); + EXPECT_FALSE(body.end_of_stream()); addMutationSetHeaders(20, *body_resp.mutable_response()->mutable_header_mutation()); return true; }); @@ -2558,7 +2772,8 @@ TEST_P(ExtProcIntegrationTest, SetHeaderMutationFailWithRequestBody) { auto response = sendDownstreamRequestWithBody("Original body", absl::nullopt); processRequestHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); processRequestBodyMessage( - *grpc_upstreams_[0], false, [this](const HttpBody&, BodyResponse& body_resp) { + *grpc_upstreams_[0], false, [this](const HttpBody& body, BodyResponse& body_resp) { + EXPECT_TRUE(body.end_of_stream()); addMutationSetHeaders(60, *body_resp.mutable_response()->mutable_header_mutation()); return true; }); @@ -2585,7 +2800,8 @@ TEST_P(ExtProcIntegrationTest, RemoveHeaderMutationFailWithResponseBody) { handleUpstreamRequest(); processResponseHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); processResponseBodyMessage( - *grpc_upstreams_[0], false, [this](const HttpBody&, BodyResponse& body_resp) { + *grpc_upstreams_[0], false, [this](const HttpBody& body, BodyResponse& body_resp) { + EXPECT_TRUE(body.end_of_stream()); addMutationRemoveHeaders(60, *body_resp.mutable_response()->mutable_header_mutation()); return true; }); @@ -2661,7 +2877,11 @@ TEST_P(ExtProcIntegrationTest, HeaderMutationResultSizeFailWithResponseTrailer) {"x-trailer-foo-4", "foo-4"}, {"x-trailer-foo-5", "foo-5"}}; upstream_request_->encodeTrailers(response_trailers); - processResponseBodyMessage(*grpc_upstreams_[0], true, absl::nullopt); + // processResponseBodyMessage(*grpc_upstreams_[0], true, absl::nullopt); + processResponseBodyMessage(*grpc_upstreams_[0], true, [](const HttpBody& body, BodyResponse&) { + EXPECT_FALSE(body.end_of_stream()); + return true; + }); processResponseTrailersMessage( *grpc_upstreams_[0], false, [this](const HttpTrailers&, TrailersResponse& trailer_resp) { // End result header count 46 + 5 > header count limit 50. @@ -2706,4 +2926,29 @@ TEST_P(ExtProcIntegrationTest, ClientNoTrailerProcessingModeSendTrailer) { verifyDownstreamResponse(*response, 200); } +// Test when request trailer is received, it sends out the buffered body to ext_proc server. +TEST_P(ExtProcIntegrationTest, SkipHeaderTrailerSendBodyClientSendAll) { + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::BUFFERED); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); + initializeConfig(); + HttpIntegrationTest::initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + codec_client_->sendData(*request_encoder_, 10, false); + IntegrationStreamDecoderPtr response = std::move(encoder_decoder.second); + Http::TestRequestTrailerMapImpl request_trailers{{"x-trailer-foo", "yes"}}; + codec_client_->sendTrailers(*request_encoder_, request_trailers); + processRequestBodyMessage(*grpc_upstreams_[0], true, [](const HttpBody& body, BodyResponse&) { + EXPECT_FALSE(body.end_of_stream()); + return true; + }); + handleUpstreamRequest(); + verifyDownstreamResponse(*response, 200); +} + } // namespace Envoy diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index f0cc95ff9957..6ce24c000789 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -98,16 +98,6 @@ class HttpFilterTest : public testing::Test { EXPECT_CALL(decoder_callbacks_, route()).WillRepeatedly(Return(route_)); EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); - EXPECT_CALL(async_client_stream_info_, bytesSent()).WillRepeatedly(Return(100)); - EXPECT_CALL(async_client_stream_info_, bytesReceived()).WillRepeatedly(Return(200)); - EXPECT_CALL(async_client_stream_info_, upstreamClusterInfo()); - EXPECT_CALL(testing::Const(async_client_stream_info_), upstreamInfo()); - // Get pointer to MockUpstreamInfo. - std::shared_ptr mock_upstream_info = - std::dynamic_pointer_cast( - async_client_stream_info_.upstreamInfo()); - EXPECT_CALL(testing::Const(*mock_upstream_info), upstreamHost()); - // Pointing dispatcher_.time_system_ to a SimulatedTimeSystem object. test_time_ = new Envoy::Event::SimulatedTimeSystem(); dispatcher_.time_system_.reset(test_time_); @@ -470,8 +460,6 @@ class HttpFilterTest : public testing::Test { filter_config_name); const Envoy::ProtobufWkt::Struct& loggedMetadata = filterState->filterMetadata(); EXPECT_THAT(loggedMetadata, ProtoEq(expected_metadata)); - EXPECT_EQ(filterState->bytesSent(), 100); - EXPECT_EQ(filterState->bytesReceived(), 200); } absl::optional final_expected_grpc_service_; diff --git a/test/extensions/filters/http/ext_proc/logging_test_filter.cc b/test/extensions/filters/http/ext_proc/logging_test_filter.cc new file mode 100644 index 000000000000..30838fd74600 --- /dev/null +++ b/test/extensions/filters/http/ext_proc/logging_test_filter.cc @@ -0,0 +1,69 @@ +#include + +#include "envoy/http/filter.h" +#include "envoy/registry/registry.h" +#include "envoy/server/filter_config.h" + +#include "source/extensions/filters/http/common/factory_base.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" +#include "source/extensions/filters/http/ext_proc/ext_proc.h" + +#include "test/extensions/filters/http/ext_proc/logging_test_filter.pb.h" +#include "test/extensions/filters/http/ext_proc/logging_test_filter.pb.validate.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace ExternalProcessing { + +// A test filter that retrieve the logging info on encodeComplete. +class LoggingTestFilter : public Http::PassThroughFilter { +public: + LoggingTestFilter(const std::string& logging_id, const std::string& cluster_name) + : logging_id_(logging_id), expected_cluster_name_(cluster_name) {} + void encodeComplete() override { + ASSERT(decoder_callbacks_ != nullptr); + const Envoy::StreamInfo::FilterStateSharedPtr& filter_state = + decoder_callbacks_->streamInfo().filterState(); + const ExtProcLoggingInfo* ext_proc_logging_info = + filter_state->getDataReadOnly(logging_id_); + if (ext_proc_logging_info != nullptr) { + EXPECT_NE(ext_proc_logging_info->bytesSent(), 0); + EXPECT_NE(ext_proc_logging_info->bytesReceived(), 0); + ASSERT_TRUE(ext_proc_logging_info->upstreamHost() != nullptr); + EXPECT_EQ(ext_proc_logging_info->upstreamHost()->cluster().name(), expected_cluster_name_); + } + } + +private: + std::string logging_id_; + std::string expected_cluster_name_; +}; + +class LoggingTestFilterFactory : public Extensions::HttpFilters::Common::FactoryBase< + test::integration::filters::LoggingTestFilterConfig> { +public: + LoggingTestFilterFactory() : FactoryBase("logging-test-filter"){}; + + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const test::integration::filters::LoggingTestFilterConfig& proto_config, const std::string&, + Server::Configuration::FactoryContext&) override { + return [=](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared( + proto_config.logging_id(), proto_config.upstream_cluster_name())); + }; + } +}; + +// Perform static registration +static Registry::RegisterFactory + register_; + +} // namespace ExternalProcessing +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/ext_proc/logging_test_filter.proto b/test/extensions/filters/http/ext_proc/logging_test_filter.proto new file mode 100644 index 000000000000..c6e5768b6860 --- /dev/null +++ b/test/extensions/filters/http/ext_proc/logging_test_filter.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package test.integration.filters; + +message LoggingTestFilterConfig { + string logging_id = 1; + string upstream_cluster_name = 2; +} diff --git a/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_corpus/clusterfuzz-testcase-minimized-ext_proc_unit_test_fuzz-5375162511851520 b/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_corpus/clusterfuzz-testcase-minimized-ext_proc_unit_test_fuzz-5375162511851520 new file mode 100644 index 000000000000..1e0c922327c5 --- /dev/null +++ b/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_corpus/clusterfuzz-testcase-minimized-ext_proc_unit_test_fuzz-5375162511851520 @@ -0,0 +1,24 @@ +config { + grpc_service { + envoy_grpc { + cluster_name: "A" + } + } + processing_mode { + request_header_mode: SKIP + request_body_mode: STREAMED + } +} +request { + proto_body { + message { + type_url: ")" + value: "\000\000\000\000\000\000000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\00ig {348 grpc_service {\n envoy_grpc {\n cluster_n\\000\\000\\000\\000\\000\\000\\000\\x00\\000\\000\\000\\000\\000\\000\\000\\000\\000ame: \"P\"\n MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMqMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\363\217\204\235\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////0//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMqMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\363\217\204\235\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230\360\230\230\230MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////0///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// retry_policy {\n }\n }\n initial_metadata {\n key: \"#\"\n }\n }\n failure_mode_allow: true\n processing_mode {\n response_header_mode: SEND\n }\n request_attributes: \"\\177\\t\"\n response_attributes: \"P\"\n stat_prefix: \"2\"\n}\nrequest {\n http_body {\n data: \"\\177\\t\"\n data: \"R\"\n }\n trailers {\n headers {\n key: \"\\000\\000\\000\\000\\177\\177\\177`\"\n v\\000\\00\000\000\000\000alu\000e: \"\\177\\t\"\n }\n }\n}\n" + } + chunk_size: 1 + } +} +response { + request_body { + } +} diff --git a/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc b/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc index 8ca81765cb27..6bb5e89d272f 100644 --- a/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc +++ b/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc @@ -30,6 +30,12 @@ class FuzzerMocks { ON_CALL(encoder_callbacks_, encodingBuffer()).WillByDefault(Return(&buffer_)); ON_CALL(decoder_callbacks_, decoderBufferLimit()).WillByDefault(Return(1024)); ON_CALL(encoder_callbacks_, encoderBufferLimit()).WillByDefault(Return(1024)); + ON_CALL(decoder_callbacks_, injectDecodedDataToFilterChain(_, _)) + .WillByDefault( + Invoke([&](Buffer::Instance& data, bool) -> void { data.drain(data.length()); })); + ON_CALL(encoder_callbacks_, injectEncodedDataToFilterChain(_, _)) + .WillByDefault( + Invoke([&](Buffer::Instance& data, bool) -> void { data.drain(data.length()); })); } NiceMock decoder_callbacks_; diff --git a/test/extensions/filters/http/fault/BUILD b/test/extensions/filters/http/fault/BUILD index fd7a607726a5..a7413da5feaf 100644 --- a/test/extensions/filters/http/fault/BUILD +++ b/test/extensions/filters/http/fault/BUILD @@ -55,7 +55,6 @@ envoy_extension_cc_test( size = "large", srcs = ["fault_filter_integration_test.cc"], extension_names = ["envoy.filters.http.fault"], - shard_count = 2, deps = [ "//source/extensions/filters/http/fault:config", "//test/integration:http_protocol_integration_lib", diff --git a/test/extensions/filters/http/file_system_buffer/BUILD b/test/extensions/filters/http/file_system_buffer/BUILD index 5c46acb03b8d..fb28ba6f0b58 100644 --- a/test/extensions/filters/http/file_system_buffer/BUILD +++ b/test/extensions/filters/http/file_system_buffer/BUILD @@ -47,7 +47,6 @@ envoy_extension_cc_test( "filter_integration_test.cc", ], extension_names = ["envoy.filters.http.file_system_buffer"], - shard_count = 2, tags = [ "cpu:3", "skip_on_windows", diff --git a/test/extensions/filters/http/grpc_field_extraction/BUILD b/test/extensions/filters/http/grpc_field_extraction/BUILD index b3add12547f2..5c606a2ccf86 100644 --- a/test/extensions/filters/http/grpc_field_extraction/BUILD +++ b/test/extensions/filters/http/grpc_field_extraction/BUILD @@ -55,7 +55,6 @@ envoy_extension_cc_test( ], data = ["//test/proto:apikeys_proto_descriptor"], extension_names = ["envoy.filters.http.grpc_field_extraction"], - shard_count = 2, deps = [ "//source/extensions/filters/http/grpc_field_extraction:config", "//test/extensions/filters/http/grpc_field_extraction/message_converter:message_converter_test_lib", diff --git a/test/extensions/filters/http/health_check/BUILD b/test/extensions/filters/http/health_check/BUILD index cb1aee58974e..8d7a622ebf1d 100644 --- a/test/extensions/filters/http/health_check/BUILD +++ b/test/extensions/filters/http/health_check/BUILD @@ -45,7 +45,6 @@ envoy_extension_cc_test( "health_check_integration_test.cc", ], extension_names = ["envoy.filters.http.health_check"], - shard_count = 4, deps = [ "//source/extensions/filters/http/buffer:config", "//source/extensions/filters/http/health_check:config", diff --git a/test/extensions/filters/http/json_to_metadata/BUILD b/test/extensions/filters/http/json_to_metadata/BUILD new file mode 100644 index 000000000000..c679f76c9f14 --- /dev/null +++ b/test/extensions/filters/http/json_to_metadata/BUILD @@ -0,0 +1,45 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "filter_test", + srcs = ["filter_test.cc"], + extension_names = ["envoy.filters.http.json_to_metadata"], + deps = [ + "//source/extensions/filters/http/json_to_metadata:json_to_metadata_lib", + "//test/common/stream_info:test_util", + "//test/mocks/server:server_mocks", + "//test/mocks/upstream:upstream_mocks", + ], +) + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.filters.http.json_to_metadata"], + deps = [ + "//source/extensions/filters/http/json_to_metadata:config", + "//test/mocks/server:server_mocks", + ], +) + +envoy_extension_cc_test( + name = "integration_test", + size = "large", + srcs = ["integration_test.cc"], + extension_names = ["envoy.filters.http.json_to_metadata"], + deps = [ + "//source/extensions/filters/http/json_to_metadata:config", + "//test/integration:http_protocol_integration_lib", + ], +) diff --git a/test/extensions/filters/http/json_to_metadata/config_test.cc b/test/extensions/filters/http/json_to_metadata/config_test.cc new file mode 100644 index 000000000000..36c9929dcc13 --- /dev/null +++ b/test/extensions/filters/http/json_to_metadata/config_test.cc @@ -0,0 +1,112 @@ +#include "source/extensions/filters/http/json_to_metadata/config.h" + +#include "test/mocks/server/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace JsonToMetadata { + +TEST(Factory, Basic) { + const std::string yaml = R"( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + on_missing: + metadata_namespace: envoy.lb + key: version + value: 'unknown' + preserve_existing_metadata_value: true + on_error: + metadata_namespace: envoy.lb + key: version + value: 'error' + preserve_existing_metadata_value: true + )"; + + JsonToMetadataConfig factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + NiceMock context; + + auto callback = factory.createFilterFactoryFromProto(*proto_config, "stats", context); + Http::MockFilterChainFactoryCallbacks filter_callback; + EXPECT_CALL(filter_callback, addStreamFilter(_)); + callback(filter_callback); +} + +TEST(Factory, NoOnPresentOnMissing) { + const std::string yaml = R"( +request_rules: + rules: + - selectors: + - key: version + )"; + + JsonToMetadataConfig factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + NiceMock context; + EXPECT_THROW_WITH_REGEX(factory.createFilterFactoryFromProto(*proto_config, "stats", context), + EnvoyException, + "json to metadata filter: neither `on_present` nor `on_missing` set"); +} + +TEST(Factory, NoValueIntOnMissing) { + const std::string yaml = R"( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + on_missing: + metadata_namespace: envoy.lb + key: version + )"; + + JsonToMetadataConfig factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + NiceMock context; + EXPECT_THROW_WITH_REGEX( + factory.createFilterFactoryFromProto(*proto_config, "stats", context), EnvoyException, + "json to metadata filter: cannot specify on_missing rule with empty value"); +} + +TEST(Factory, NoValueIntOnError) { + const std::string yaml = R"( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + on_error: + metadata_namespace: envoy.lb + key: version + )"; + + JsonToMetadataConfig factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + NiceMock context; + EXPECT_THROW_WITH_REGEX(factory.createFilterFactoryFromProto(*proto_config, "stats", context), + EnvoyException, + "json to metadata filter: cannot specify on_error rule with empty value"); +} + +} // namespace JsonToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/json_to_metadata/filter_test.cc b/test/extensions/filters/http/json_to_metadata/filter_test.cc new file mode 100644 index 000000000000..ac629bc0a786 --- /dev/null +++ b/test/extensions/filters/http/json_to_metadata/filter_test.cc @@ -0,0 +1,1323 @@ +#include + +#include "envoy/http/header_map.h" + +#include "source/common/json/json_loader.h" +#include "source/extensions/filters/http/json_to_metadata/filter.h" + +#include "test/common/stream_info/test_util.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace JsonToMetadata { + +MATCHER_P(MapEq, rhs, "") { + const ProtobufWkt::Struct& obj = arg; + EXPECT_TRUE(!rhs.empty()); + for (auto const& entry : rhs) { + EXPECT_EQ(obj.fields().at(entry.first).string_value(), entry.second); + } + return true; +} + +MATCHER_P2(MapEqType, rhs, getter, "") { + const ProtobufWkt::Struct& obj = arg; + EXPECT_TRUE(!rhs.empty()); + for (auto const& entry : rhs) { + EXPECT_EQ(getter(obj.fields().at(entry.first)), entry.second); + } + return true; +} + +using testing::_; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +class FilterTest : public testing::Test { +public: + FilterTest() = default; + + const std::string request_config_yaml_ = R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + on_missing: + metadata_namespace: envoy.lb + key: version + value: 'unknown' + preserve_existing_metadata_value: true + on_error: + metadata_namespace: envoy.lb + key: version + value: 'error' + preserve_existing_metadata_value: true +)EOF"; + + void initializeFilter(const std::string& yaml) { + envoy::extensions::filters::http::json_to_metadata::v3::JsonToMetadata config; + TestUtility::loadFromYaml(yaml, config); + config_ = std::make_shared(config, *scope_.rootScope()); + filter_ = std::make_shared(config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + } + + void sendData(const std::vector& data_vector) { + for (const auto& data : data_vector) { + Buffer::OwnedImpl buffer(data); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer, false)); + } + } + + uint64_t getCounterValue(const std::string& name) { + const auto counter = TestUtility::findCounter(scope_, name); + return counter != nullptr ? counter->value() : 0; + } + + void setExistingMetadata() { + auto& filter_metadata = *dynamic_metadata_.mutable_filter_metadata(); + + (*filter_metadata["envoy.lb"].mutable_fields())["version"].set_string_value("existing"); + + EXPECT_CALL(stream_info_, dynamicMetadata()).WillOnce(ReturnRef(dynamic_metadata_)); + } + + void + testRequestWithBody(const std::string& body, bool end_stream = true, + Http::FilterDataStatus expected_result = Http::FilterDataStatus::Continue) { + buffer_.add(body); + ON_CALL(decoder_callbacks_, decodingBuffer()).WillByDefault(Return(&buffer_)); + + EXPECT_EQ(expected_result, filter_->decodeData(buffer_, end_stream)); + } + + NiceMock scope_; + NiceMock decoder_callbacks_; + NiceMock stream_info_; + envoy::config::core::v3::Metadata dynamic_metadata_; + std::shared_ptr config_; + std::shared_ptr filter_; + Buffer::OwnedImpl buffer_; + Http::TestRequestHeaderMapImpl incoming_headers_{ + {":path", "/ping"}, {":method", "GET"}, {"Content-Type", "application/json"}}; +}; + +TEST_F(FilterTest, BasicStringMatch) { + initializeFilter(request_config_yaml_); + const std::string request_body = + R"delimiter( + {"version":"1.0.0", + "messages":[ + {"role":"user","content":"content A"}, + {"role":"assistant","content":"content B"}, + {"role":"user","content":"content C"}, + {"role":"assistant","content":"content D"}, + {"role":"user","content":"content E"}], + "stream":true})delimiter"; + const std::map expected = {{"version", "1.0.0"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, BasicBoolMatch) { + initializeFilter(request_config_yaml_); + const std::string request_body = R"delimiter({"version":true})delimiter"; + std::map expected = {{"version", true}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL( + stream_info_, + setDynamicMetadata("envoy.lb", MapEqType(expected, [](const ProtobufWkt::Value& value) { + return value.bool_value(); + }))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, BasicIntegerMatch) { + initializeFilter(request_config_yaml_); + const std::string request_body = R"delimiter({"version":1})delimiter"; + std::map expected = {{"version", 1.0}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL( + stream_info_, + setDynamicMetadata("envoy.lb", MapEqType(expected, [](const ProtobufWkt::Value& value) { + return value.number_value(); + }))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, BasicDoubleMatch) { + initializeFilter(request_config_yaml_); + const std::string request_body = R"delimiter({"version":1.0})delimiter"; + std::map expected = {{"version", 1.0}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL( + stream_info_, + setDynamicMetadata("envoy.lb", MapEqType(expected, [](const ProtobufWkt::Value& value) { + return value.number_value(); + }))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, TrailerSupport) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version +)EOF"); + const std::string request_body = R"delimiter({"version":"good version"})delimiter"; + const std::map expected = {{"version", "good version"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body, false, Http::FilterDataStatus::StopIterationAndBuffer); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + Http::TestRequestTrailerMapImpl trailers{{"some", "trailer"}}; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers)); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, StringToString) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: STRING +)EOF"); + const std::string request_body = R"delimiter({"version":"good version"})delimiter"; + const std::map expected = {{"version", "good version"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, StringToNumber) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: NUMBER +)EOF"); + const std::string request_body = R"delimiter({"version":"123"})delimiter"; + std::map expected = {{"version", 123.0}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL( + stream_info_, + setDynamicMetadata("envoy.lb", MapEqType(expected, [](const ProtobufWkt::Value& value) { + return value.number_value(); + }))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, BadStringToNumber) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: NUMBER + on_missing: + metadata_namespace: envoy.lb + key: version + value: 404 +)EOF"); + const std::string request_body = R"delimiter({"version":"invalid"})delimiter"; + std::map expected = {{"version", 404}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL( + stream_info_, + setDynamicMetadata("envoy.lb", MapEqType(expected, [](const ProtobufWkt::Value& value) { + return value.number_value(); + }))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, NumberToString) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: STRING +)EOF"); + const std::string request_body = R"delimiter({"version":220.0})delimiter"; + const std::map expected = {{"version", "220.000000"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, NumberToNumber) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: NUMBER +)EOF"); + const std::string request_body = R"delimiter({"version":220.0})delimiter"; + std::map expected = {{"version", 220.0}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL( + stream_info_, + setDynamicMetadata("envoy.lb", MapEqType(expected, [](const ProtobufWkt::Value& value) { + return value.number_value(); + }))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, IntegerToString) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: STRING +)EOF"); + const std::string request_body = R"delimiter({"version":220})delimiter"; + const std::map expected = {{"version", "220"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, IntegerToNumber) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: NUMBER +)EOF"); + const std::string request_body = R"delimiter({"version":220})delimiter"; + std::map expected = {{"version", 220.0}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL( + stream_info_, + setDynamicMetadata("envoy.lb", MapEqType(expected, [](const ProtobufWkt::Value& value) { + return value.number_value(); + }))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, BoolToString) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: STRING +)EOF"); + const std::string request_body = R"delimiter({"version":true})delimiter"; + const std::map expected = {{"version", "1"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, BoolToNumber) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: NUMBER +)EOF"); + const std::string request_body = R"delimiter({"version":true})delimiter"; + std::map expected = {{"version", 1}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL( + stream_info_, + setDynamicMetadata("envoy.lb", MapEqType(expected, [](const ProtobufWkt::Value& value) { + return value.number_value(); + }))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, OnPresentWithValueSet) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + value: "present" +)EOF"); + const std::string request_body = R"delimiter({"version":"good version"})delimiter"; + const std::map expected = {{"version", "present"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, NoApplyOnMissingWhenPayloadIsPresent) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_missing: + metadata_namespace: envoy.lb + key: version + value: "foo" + on_error: + metadata_namespace: envoy.lb + key: version + value: "foo" +)EOF"); + const std::string request_body = R"delimiter({"version":"good version"})delimiter"; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + + // No metadata is set from stream info. + EXPECT_CALL(stream_info_, setDynamicMetadata(_, _)).Times(0); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, DefaultNamespaceTest) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + key: version +)EOF"); + const std::string request_body = R"delimiter({"version":"good version"})delimiter"; + const std::map expected = {{"version", "good version"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, + setDynamicMetadata("envoy.filters.http.json_to_metadata", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, DecodeTwoDataStreams) { + initializeFilter(request_config_yaml_); + + const std::string request_body1 = + R"delimiter( + {"version":"1.0.0", + "messages":[ + {"role":"user","content":"content A"}, + {"role":"assis)delimiter"; + const std::string request_body2 = + R"delimiter(tant","content":"content B"}, + {"role":"user","content":"content C"}, + {"role":"assistant","content":"content D"}, + {"role":"user","content":"content E"}], + "stream":true})delimiter"; + const std::map expected = {{"version", "1.0.0"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body1, false, Http::FilterDataStatus::StopIterationAndBuffer); + testRequestWithBody(request_body2); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, SecondLayerMatch) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: foo + - key: bar + on_present: + metadata_namespace: envoy.lb + key: baz +)EOF"); + const std::string request_body = + R"delimiter( + {"foo":{ + "bar":"value" + } + })delimiter"; + const std::map expected = {{"baz", "value"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, OnMissingFirstLayer) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: foo + - key: bar + on_present: + metadata_namespace: envoy.lb + key: baz +)EOF"); + const std::string request_body = + R"delimiter( + {"nah":{ + "bar":"Scotty, beam me up!" + } + })delimiter"; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + + // No metadata is set from stream info. + EXPECT_CALL(stream_info_, setDynamicMetadata(_, _)).Times(0); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, OnMissingSecondLayer) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: foo + - key: bar + on_present: + metadata_namespace: envoy.lb + key: baz +)EOF"); + const std::string request_body = + R"delimiter( + {"foo":{ + "nah":"James, James, Morrison, Morrison, weather-beaten, off-key" + } + })delimiter"; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + + // No metadata is set from stream info. + EXPECT_CALL(stream_info_, setDynamicMetadata(_, _)).Times(0); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, OnMissingForArray) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: foo + - key: bar + on_present: + metadata_namespace: envoy.lb + key: baz +)EOF"); + const std::string request_body = + R"delimiter( + {"foo":[ + { "bar":"Shaken, not stirred for Sean."} + ] + })delimiter"; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + + // No metadata is set from stream info. + EXPECT_CALL(stream_info_, setDynamicMetadata(_, _)).Times(0); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, OnMissingSecondLayerString) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: foo + - key: bar + on_present: + metadata_namespace: envoy.lb + key: baz + on_missing: + metadata_namespace: envoy.lb + key: version + value: "missing" +)EOF"); + const std::string request_body = + R"delimiter( + {"foo": "James, James, Morrison, Morrison, weather-beaten, off-key"})delimiter"; + + const std::map expected = {{"version", "missing"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, NoRequestContentType) { + initializeFilter(request_config_yaml_); + + Http::TestRequestHeaderMapImpl mismatched_incoming_headers{{":path", "/ping"}, + {":method", "GET"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(mismatched_incoming_headers, false)); + testRequestWithBody("{}"); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, MismatchedRequestContentType) { + initializeFilter(request_config_yaml_); + + Http::TestRequestHeaderMapImpl mismatched_incoming_headers{ + {":path", "/ping"}, {":method", "GET"}, {"Content-Type", "application/not-a-json"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_->decodeHeaders(mismatched_incoming_headers, false)); + testRequestWithBody("Peter picked a peck of pickled peppers"); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, NoRequestBody) { + initializeFilter(request_config_yaml_); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(incoming_headers_, true)); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, EmptyPayloadValue) { + initializeFilter(request_config_yaml_); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + testRequestWithBody(""); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, InvalidJsonPayload) { + initializeFilter(request_config_yaml_); + // missing right-most curly brace + const std::string request_body = + R"delimiter( + {"version":"1.0.0", + "messages":[ + {"role":"user","content":"content A"}, + {"role":"assistant","content":"content B"}, + {"role":"user","content":"content C"}, + {"role":"assistant","content":"content D"}, + {"role":"user","content":"content E"}], + "stream":true)delimiter"; + const std::map expected = {{"version", "error"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 1); +} + +TEST_F(FilterTest, OnMissingQuotedString) { + initializeFilter(request_config_yaml_); + const std::string request_body = R"delimiter("")delimiter"; + const std::map expected = {{"version", "unknown"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, OnMissingQuotedJsonObject) { + initializeFilter(request_config_yaml_); + const std::string request_body = + R"delimiter("{\"model\": \"gpt-3.5-turbo\",\"temperature\": 0.2,\"stream\": false}")delimiter"; + const std::map expected = {{"version", "unknown"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, OnMissingPureNumber) { + initializeFilter(request_config_yaml_); + const std::string request_body = R"delimiter(5566)delimiter"; + const std::map expected = {{"version", "unknown"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +// TODO(kuochunghsu): Planned to support trimming. +TEST_F(FilterTest, InvalidJsonForAdditionalPrefixSuffix) { + initializeFilter(request_config_yaml_); + // missing right-most curly brace + const std::string request_body = + R"delimiter(data: {"id":"ID","object":"chat.completion.chunk","created":1686100940,"version":"1.0.0-0301"}\n\ndata: [DONE]\n\n)delimiter"; + const std::map expected = {{"version", "error"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 1); +} + +TEST_F(FilterTest, EmptyStringValue) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: STRING +)EOF"); + + const std::string request_body = R"delimiter({"version":""})delimiter"; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + + // No metadata is set from stream info. + EXPECT_CALL(stream_info_, setDynamicMetadata(_, _)).Times(0); + testRequestWithBody(request_body); + + Buffer::OwnedImpl buffer(request_body); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, PayloadValueTooLong) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + on_missing: + metadata_namespace: envoy.lb + key: version + value: 'unknown' +)EOF"); + + const std::string value(MAX_PAYLOAD_VALUE_LEN + 1, 'z'); + const std::string request_body = + absl::StrCat(R"delimiter({"version":")delimiter", value, R"delimiter("})delimiter"); + + const std::map expected = {{"version", "unknown"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + Buffer::OwnedImpl buffer(request_body); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, PayloadValueTooLongValueTypeString) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + type: STRING + on_missing: + metadata_namespace: envoy.lb + key: version + value: 'unknown' +)EOF"); + + const std::string value(MAX_PAYLOAD_VALUE_LEN + 1, 'z'); + const std::string request_body = + absl::StrCat(R"delimiter({"version":")delimiter", value, R"delimiter("})delimiter"); + + const std::map expected = {{"version", "unknown"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + Buffer::OwnedImpl buffer(request_body); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, MissingMetadataKeyAndFallbackValue) { + initializeFilter(request_config_yaml_); + const std::string request_body = + R"delimiter( + {"messages":[ + {"role":"user","content":"content A"}, + {"role":"assistant","content":"content B"}, + {"role":"user","content":"content C"}, + {"role":"assistant","content":"content D"}, + {"role":"user","content":"content E"}], + "stream":true})delimiter"; + const std::map expected = {{"version", "unknown"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, MissingMetadataKeyWithNoFallbackValue) { + const std::string yaml = R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version +)EOF"; + + initializeFilter(yaml); + const std::string request_body = + R"delimiter( + {"messages":[ + {"role":"user","content":"content A"}, + {"role":"assistant","content":"content B"}, + {"role":"user","content":"content C"}, + {"role":"assistant","content":"content D"}, + {"role":"user","content":"content E"}], + "stream":true})delimiter"; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + // No metadata is set from stream info. + EXPECT_CALL(decoder_callbacks_, streamInfo()).Times(0); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, MissingMetadataKeyWithExistingMetadata) { + initializeFilter(request_config_yaml_); + const std::string request_body = + R"delimiter( + {"messages":[ + {"role":"user","content":"content A"}, + {"role":"assistant","content":"content B"}, + {"role":"user","content":"content C"}, + {"role":"assistant","content":"content D"}, + {"role":"user","content":"content E"}], + "stream":true})delimiter"; + + setExistingMetadata(); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + + // No metadata is set from stream info. + EXPECT_CALL(stream_info_, setDynamicMetadata(_, _)).Times(0); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, MultipleRules) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + - selectors: + - key: id + on_present: + metadata_namespace: envoy.lb + key: id +)EOF"); + const std::string request_body = + R"delimiter({"version":"good version", "id":"beautiful id"})delimiter"; + const std::map expected = {{"version", "good version"}, + {"id", "beautiful id"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, MultipleRulesInSamePath) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + - selectors: + - key: version + on_present: + metadata_namespace: another.namespace + key: version +)EOF"); + const std::string request_body = + R"delimiter({"version":"good version", "id":"beautiful id"})delimiter"; + const std::map expected = {{"version", "good version"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + EXPECT_CALL(stream_info_, setDynamicMetadata("another.namespace", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, MultipleRulesSecondLayer) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + - selectors: + - key: messages + - key: foo + on_present: + metadata_namespace: envoy.lb + key: foo + - selectors: + - key: messages + - key: baz + on_present: + metadata_namespace: envoy.lb + key: baz +)EOF"); + const std::string request_body = R"delimiter({"version":"good version", "messages":{ + "foo":"bar", + "baz":"qux" + } + })delimiter"; + const std::map expected = { + {"version", "good version"}, {"foo", "bar"}, {"baz", "qux"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(incoming_headers_, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, CustomRequestAllowContentTypeAccepted) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + allow_content_types: + - "application/better-json" +)EOF"); + const std::string request_body = R"delimiter({"version":"good version"})delimiter"; + const std::map expected = {{"version", "good version"}}; + + Http::TestRequestHeaderMapImpl matched_incoming_headers{ + {":path", "/ping"}, {":method", "GET"}, {"Content-Type", "application/better-json"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(matched_incoming_headers, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, CustomRequestAllowContentTypeRejected) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + allow_content_types: + - "application/non-json" +)EOF"); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(incoming_headers_, false)); + + testRequestWithBody("{}"); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +TEST_F(FilterTest, RequestAllowEmptyContentType) { + initializeFilter(R"EOF( +request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + allow_empty_content_type: true +)EOF"); + const std::string request_body = R"delimiter({"version":"good version"})delimiter"; + const std::map expected = {{"version", "good version"}}; + + Http::TestRequestHeaderMapImpl matched_incoming_headers{{":path", "/ping"}, {":method", "GET"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(matched_incoming_headers, false)); + + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(stream_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + testRequestWithBody(request_body); + + EXPECT_EQ(getCounterValue("json_to_metadata.rq_success"), 1); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_mismatched_content_type"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_no_body"), 0); + EXPECT_EQ(getCounterValue("json_to_metadata.rq_invalid_json_body"), 0); +} + +} // namespace JsonToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/json_to_metadata/integration_test.cc b/test/extensions/filters/http/json_to_metadata/integration_test.cc new file mode 100644 index 000000000000..3c4cab72157b --- /dev/null +++ b/test/extensions/filters/http/json_to_metadata/integration_test.cc @@ -0,0 +1,174 @@ +#include "test/integration/http_protocol_integration.h" + +namespace Envoy { +namespace { + +class JsonToMetadataIntegrationTest : public Event::TestUsingSimulatedTime, + public HttpProtocolIntegrationTest { +public: + void initializeFilter() { + config_helper_.prependFilter(filter_config_); + initialize(); + } + + void runTest(const Http::RequestHeaderMap& request_headers, const std::string& request_body, + const size_t chunk_size = 5, bool has_trailer = false) { + codec_client_ = makeHttpConnection(lookupPort("http")); + IntegrationStreamDecoderPtr response; + if (request_body.empty()) { + response = codec_client_->makeHeaderOnlyRequest(request_headers); + } else { + auto encoder_decoder = codec_client_->startRequest(request_headers); + request_encoder_ = &encoder_decoder.first; + response = std::move(encoder_decoder.second); + size_t i = 0; + for (; i < request_body.length() / chunk_size; i++) { + Buffer::OwnedImpl buffer(request_body.substr(i * chunk_size, chunk_size)); + codec_client_->sendData(*request_encoder_, buffer, false); + } + // Send the last chunk flagged as end_stream. + Buffer::OwnedImpl buffer( + request_body.substr(i * chunk_size, request_body.length() % chunk_size)); + codec_client_->sendData(*request_encoder_, buffer, !has_trailer); + + if (has_trailer) { + codec_client_->sendTrailers(*request_encoder_, incoming_trailers_); + } + } + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); + + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + + // cleanup + codec_client_->close(); + ASSERT_TRUE(fake_upstream_connection_->close()); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + } + + const std::string filter_config_ = R"EOF( +name: envoy.filters.http.json_to_metadata +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.json_to_metadata.v3.JsonToMetadata + request_rules: + rules: + - selectors: + - key: version + on_present: + metadata_namespace: envoy.lb + key: version + on_missing: + metadata_namespace: envoy.lb + key: version + value: 'unknown' + preserve_existing_metadata_value: true + on_error: + metadata_namespace: envoy.lb + key: version + value: 'error' + preserve_existing_metadata_value: true +)EOF"; + + Http::TestRequestHeaderMapImpl incoming_headers_{{":scheme", "http"}, + {":path", "/ping"}, + {":method", "GET"}, + {":authority", "host"}, + {"Content-Type", "application/json"}}; + Http::TestRequestTrailerMapImpl incoming_trailers_{{"request1", "trailer1"}, + {"request2", "trailer2"}}; + + const std::string request_body_ = + R"delimiter( + {"version":"1.0.0", + "messages":[ + {"role":"user","content":"content A"}, + {"role":"assistant","content":"content B"}, + {"role":"user","content":"content C"}, + {"role":"assistant","content":"content D"}, + {"role":"user","content":"content E"}], + "stream":true})delimiter"; +}; + +// TODO(#26236): Fix test suite for HTTP/3. +INSTANTIATE_TEST_SUITE_P( + Protocols, JsonToMetadataIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParamsWithoutHTTP3()), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +TEST_P(JsonToMetadataIntegrationTest, Basic) { + initializeFilter(); + + runTest(incoming_headers_, request_body_); + + EXPECT_EQ(1UL, test_server_->counter("json_to_metadata.rq_success")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_mismatched_content_type")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_no_body")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_invalid_json_body")->value()); +} + +TEST_P(JsonToMetadataIntegrationTest, BasicOneChunk) { + initializeFilter(); + + runTest(incoming_headers_, request_body_, 1); + + EXPECT_EQ(1UL, test_server_->counter("json_to_metadata.rq_success")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_mismatched_content_type")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_no_body")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_invalid_json_body")->value()); +} + +TEST_P(JsonToMetadataIntegrationTest, Trailer) { + initializeFilter(); + + runTest(incoming_headers_, request_body_, 5, true); + + EXPECT_EQ(1UL, test_server_->counter("json_to_metadata.rq_success")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_mismatched_content_type")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_no_body")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_invalid_json_body")->value()); +} + +TEST_P(JsonToMetadataIntegrationTest, MismatchedContentType) { + initializeFilter(); + + const Http::TestRequestHeaderMapImpl incoming_headers{{":scheme", "http"}, + {":path", "/ping"}, + {":method", "GET"}, + {":authority", "host"}, + {"Content-Type", "application/x-thrift"}}; + + runTest(incoming_headers, request_body_); + + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_success")->value()); + EXPECT_EQ(1UL, test_server_->counter("json_to_metadata.rq_mismatched_content_type")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_no_body")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_invalid_json_body")->value()); +} + +TEST_P(JsonToMetadataIntegrationTest, NoBody) { + initializeFilter(); + + runTest(incoming_headers_, ""); + + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_success")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_mismatched_content_type")->value()); + EXPECT_EQ(1UL, test_server_->counter("json_to_metadata.rq_no_body")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_invalid_json_body")->value()); +} + +TEST_P(JsonToMetadataIntegrationTest, InvalidJson) { + initializeFilter(); + + runTest(incoming_headers_, "it's not a json body"); + + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_success")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_mismatched_content_type")->value()); + EXPECT_EQ(0UL, test_server_->counter("json_to_metadata.rq_no_body")->value()); + EXPECT_EQ(1UL, test_server_->counter("json_to_metadata.rq_invalid_json_body")->value()); +} + +} // namespace +} // namespace Envoy diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index 2e6c1ec110cc..805df65cce86 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -146,7 +146,7 @@ envoy_extension_cc_test( size = "large", srcs = ["filter_integration_test.cc"], extension_names = ["envoy.filters.http.jwt_authn"], - shard_count = 6, + shard_count = 4, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/kill_request/BUILD b/test/extensions/filters/http/kill_request/BUILD index c9d7d33d0d3f..e4d78880db0e 100644 --- a/test/extensions/filters/http/kill_request/BUILD +++ b/test/extensions/filters/http/kill_request/BUILD @@ -56,7 +56,7 @@ envoy_cc_test( name = "crash_integration_test", size = "large", srcs = ["crash_integration_test.cc"], - shard_count = 16, # This is really slow on coverage. + shard_count = 8, deps = [ "//source/extensions/filters/http/kill_request:kill_request_config", "//test/integration:http_protocol_integration_lib", diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index 18317bdb4467..92f3b3eb0067 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -51,7 +51,7 @@ envoy_extension_cc_test( size = "large", srcs = ["rbac_filter_integration_test.cc"], extension_names = ["envoy.filters.http.rbac"], - shard_count = 16, + shard_count = 2, deps = [ "//source/extensions/clusters/dynamic_forward_proxy:cluster", "//source/extensions/filters/http/dynamic_forward_proxy:config", diff --git a/test/extensions/filters/http/tap/BUILD b/test/extensions/filters/http/tap/BUILD index 3c084c91c5fa..86c40954c6a1 100644 --- a/test/extensions/filters/http/tap/BUILD +++ b/test/extensions/filters/http/tap/BUILD @@ -54,7 +54,6 @@ envoy_extension_cc_test( size = "large", srcs = envoy_select_admin_functionality(["tap_filter_integration_test.cc"]), extension_names = ["envoy.filters.http.tap"], - shard_count = 4, deps = [ "//source/extensions/filters/http/tap:config", "//test/extensions/common/tap:common", diff --git a/test/extensions/filters/http/wasm/BUILD b/test/extensions/filters/http/wasm/BUILD index 59e7d75b3352..dfaa4a054170 100644 --- a/test/extensions/filters/http/wasm/BUILD +++ b/test/extensions/filters/http/wasm/BUILD @@ -37,6 +37,7 @@ envoy_extension_cc_test( ]), extension_names = ["envoy.filters.http.wasm"], shard_count = 50, + tags = ["cpu:4"], deps = [ "//source/common/http:message_lib", "//source/extensions/filters/http/wasm:wasm_filter_lib", @@ -57,7 +58,7 @@ envoy_extension_cc_test( "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", ]), extension_names = ["envoy.filters.http.wasm"], - shard_count = 50, + shard_count = 16, deps = [ "//source/common/common:base64_lib", "//source/common/common:hex_lib", diff --git a/test/extensions/filters/network/redis_proxy/BUILD b/test/extensions/filters/network/redis_proxy/BUILD index 3ff6acbfc0e9..d884fbbe00b1 100644 --- a/test/extensions/filters/network/redis_proxy/BUILD +++ b/test/extensions/filters/network/redis_proxy/BUILD @@ -18,9 +18,6 @@ envoy_extension_cc_test( name = "command_splitter_impl_test", srcs = ["command_splitter_impl_test.cc"], extension_names = ["envoy.filters.network.redis_proxy"], - # This test takes a while to run specially under tsan. - # Shard it to avoid test timeout. - shard_count = 2, deps = [ ":redis_mocks", "//source/common/stats:isolated_store_lib", diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index 36c741e36f1f..cd5e3f450aed 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -397,6 +397,31 @@ TEST_F(RedisSingleServerRequestTest, PingSuccess) { EXPECT_EQ(nullptr, handle_); }; +TEST_F(RedisSingleServerRequestTest, Time) { + InSequence s; + + Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; + makeBulkStringArray(*request, {"time"}); + + auto now = dispatcher_.timeSource().systemTime().time_since_epoch(); + auto secs = std::to_string(std::chrono::duration_cast(now).count()); + auto msecs = std::to_string(std::chrono::duration_cast(now).count()); + + Common::Redis::RespValue response; + response.type(Common::Redis::RespType::Array); + std::vector elements(2); + elements[0].type(Common::Redis::RespType::BulkString); + elements[0].asString() = secs; + elements[1].type(Common::Redis::RespType::BulkString); + elements[1].asString() = msecs; + response.asArray().swap(elements); + + EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); + EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&response))); + handle_ = splitter_.makeRequest(std::move(request), callbacks_, dispatcher_); + EXPECT_EQ(nullptr, handle_); +} + TEST_F(RedisSingleServerRequestTest, EvalSuccess) { InSequence s; diff --git a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc index c9ce02d13dc4..fc78353c5964 100644 --- a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc @@ -591,7 +591,12 @@ TEST_F(RedisConnPoolImplTest, NoClusterAtConstruction) { EXPECT_EQ(nullptr, request); // Now add the cluster. Request to the cluster should succeed. - update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_); + { + Upstream::ThreadLocalClusterCommand command = [this]() -> Upstream::ThreadLocalCluster& { + return cm_.thread_local_cluster_; + }; + update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_.info()->name(), command); + } // MurmurHash of "foo" is 9631199822919835226U makeSimpleRequest(true, "foo", 9631199822919835226U); @@ -604,10 +609,20 @@ TEST_F(RedisConnPoolImplTest, NoClusterAtConstruction) { // Add a cluster we don't care about. NiceMock cluster2; cluster2.cluster_.info_->name_ = "cluster2"; - update_callbacks_->onClusterAddOrUpdate(cluster2); + { + Upstream::ThreadLocalClusterCommand command = [&cluster2]() -> Upstream::ThreadLocalCluster& { + return cluster2; + }; + update_callbacks_->onClusterAddOrUpdate(cluster2.cluster_.info()->name(), command); + } // Add the cluster back. Request to the cluster should succeed. - update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_); + { + Upstream::ThreadLocalClusterCommand command = [this]() -> Upstream::ThreadLocalCluster& { + return cm_.thread_local_cluster_; + }; + update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_.info()->name(), command); + } // MurmurHash of "foo" is 9631199822919835226U makeSimpleRequest(true, "foo", 9631199822919835226U); @@ -619,7 +634,12 @@ TEST_F(RedisConnPoolImplTest, NoClusterAtConstruction) { // Update the cluster. This should count as a remove followed by an add. Request to the cluster // should succeed. EXPECT_CALL(*client_, close()); - update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_); + { + Upstream::ThreadLocalClusterCommand command = [this]() -> Upstream::ThreadLocalCluster& { + return cm_.thread_local_cluster_; + }; + update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_.info()->name(), command); + } // MurmurHash of "foo" is 9631199822919835226U makeSimpleRequest(true, "foo", 9631199822919835226U); @@ -654,7 +674,12 @@ TEST_F(RedisConnPoolImplTest, AuthInfoUpdate) { auth_password_ = ""; // Now add the cluster. Request to the cluster should succeed. - update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_); + { + Upstream::ThreadLocalClusterCommand command = [this]() -> Upstream::ThreadLocalCluster& { + return cm_.thread_local_cluster_; + }; + update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_.info()->name(), command); + } // MurmurHash of "foo" is 9631199822919835226U makeSimpleRequest(true, "foo", 9631199822919835226U); @@ -800,7 +825,12 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHost) { // There is no cluster yet, so makeRequestToHost() should fail. EXPECT_EQ(nullptr, conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1)); // Add the cluster now. - update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_); + { + Upstream::ThreadLocalClusterCommand command = [this]() -> Upstream::ThreadLocalCluster& { + return cm_.thread_local_cluster_; + }; + update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_.info()->name(), command); + } EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); EXPECT_CALL(*client1, makeRequest_(Ref(value), Ref(callbacks1))) diff --git a/test/extensions/filters/network/thrift_proxy/BUILD b/test/extensions/filters/network/thrift_proxy/BUILD index 75d745bb8442..1d0ce333508e 100644 --- a/test/extensions/filters/network/thrift_proxy/BUILD +++ b/test/extensions/filters/network/thrift_proxy/BUILD @@ -345,7 +345,6 @@ envoy_extension_cc_test( "//test/extensions/filters/network/thrift_proxy/driver:generate_fixture", ], extension_names = ["envoy.filters.network.thrift_proxy"], - shard_count = 12, tags = ["skip_on_windows"], deps = [ ":integration_lib", @@ -364,7 +363,6 @@ envoy_extension_cc_test( "//test/extensions/filters/network/thrift_proxy/driver:generate_fixture", ], extension_names = ["envoy.filters.network.thrift_proxy"], - shard_count = 4, deps = [ ":integration_lib", ":utility_lib", diff --git a/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc b/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc index 22876780059d..d14f5ec93f7b 100644 --- a/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc +++ b/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc @@ -748,15 +748,27 @@ stat_prefix: foo // Add a cluster that we don't care about. NiceMock other_thread_local_cluster; other_thread_local_cluster.cluster_.info_->name_ = "other_cluster"; - cluster_update_callbacks_->onClusterAddOrUpdate(other_thread_local_cluster); + { + Upstream::ThreadLocalClusterCommand command = + [&other_thread_local_cluster]() -> Upstream::ThreadLocalCluster& { + return other_thread_local_cluster; + }; + cluster_update_callbacks_->onClusterAddOrUpdate(other_thread_local_cluster.info()->name(), + command); + } recvDataFromDownstream("10.0.0.1:1000", "10.0.0.2:80", "hello"); EXPECT_EQ(2, config_->stats().downstream_sess_no_route_.value()); EXPECT_EQ(0, config_->stats().downstream_sess_total_.value()); EXPECT_EQ(0, config_->stats().downstream_sess_active_.value()); // Now add the cluster we care about. - cluster_update_callbacks_->onClusterAddOrUpdate( - factory_context_.cluster_manager_.thread_local_cluster_); + { + Upstream::ThreadLocalClusterCommand command = [this]() -> Upstream::ThreadLocalCluster& { + return factory_context_.cluster_manager_.thread_local_cluster_; + }; + cluster_update_callbacks_->onClusterAddOrUpdate( + factory_context_.cluster_manager_.thread_local_cluster_.info()->name(), command); + } expectSessionCreate(upstream_address_); test_sessions_[0].expectWriteToUpstream("hello", 0, nullptr, true); recvDataFromDownstream("10.0.0.1:1000", "10.0.0.2:80", "hello"); diff --git a/test/extensions/http/header_validators/envoy_default/BUILD b/test/extensions/http/header_validators/envoy_default/BUILD index 2eda8c51e913..4564177ea081 100644 --- a/test/extensions/http/header_validators/envoy_default/BUILD +++ b/test/extensions/http/header_validators/envoy_default/BUILD @@ -15,17 +15,18 @@ licenses(["notice"]) # Apache 2 envoy_package() envoy_extension_cc_test( - name = "header_validator_test", + name = "base_header_validator_test", srcs = [ - "header_validator_test.cc", + "base_header_validator_test.cc", ], extension_names = ["envoy.http.header_validators.envoy_default"], deps = [ - ":header_validator_test_lib", + ":header_validator_utils_lib", "//envoy/http:header_validator_errors", "//source/common/network:utility_lib", "//source/extensions/http/header_validators/envoy_default:character_tables", "//source/extensions/http/header_validators/envoy_default:header_validator_common", + "//test/mocks/http:header_validator_mocks", "//test/test_common:utility_lib", ], ) @@ -37,10 +38,12 @@ envoy_extension_cc_test( ], extension_names = ["envoy.http.header_validators.envoy_default"], deps = [ - ":header_validator_test_lib", + ":header_validator_utils_lib", "//envoy/http:header_validator_errors", + "//source/extensions/http/header_validators/envoy_default:character_tables", "//source/extensions/http/header_validators/envoy_default:http1_header_validator", "//source/extensions/http/header_validators/envoy_default:http2_header_validator", + "//test/mocks/http:header_validator_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], @@ -53,9 +56,10 @@ envoy_extension_cc_test( ], extension_names = ["envoy.http.header_validators.envoy_default"], deps = [ - ":header_validator_test_lib", + ":header_validator_utils_lib", "//envoy/http:header_validator_errors", "//source/extensions/http/header_validators/envoy_default:http1_header_validator", + "//test/mocks/http:header_validator_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], @@ -68,24 +72,29 @@ envoy_extension_cc_test( ], extension_names = ["envoy.http.header_validators.envoy_default"], deps = [ - ":header_validator_test_lib", + ":header_validator_utils_lib", "//envoy/http:header_validator_errors", "//source/extensions/http/header_validators/envoy_default:character_tables", "//source/extensions/http/header_validators/envoy_default:http2_header_validator", + "//test/mocks/http:header_validator_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], ) envoy_extension_cc_test_library( - name = "header_validator_test_lib", + name = "header_validator_utils_lib", + srcs = [ + "header_validator_utils.cc", + ], hdrs = [ - "header_validator_test.h", + "header_validator_utils.h", ], extension_names = ["envoy.http.header_validators.envoy_default"], deps = [ - "//test/mocks/http:header_validator_mocks", - "@envoy_api//envoy/extensions/http/header_validators/envoy_default/v3:pkg_cc_proto", + "//envoy/http:header_validator_interface", + "//source/extensions/http/header_validators/envoy_default:character_tables", + "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/http/header_validators/envoy_default/header_validator_test.cc b/test/extensions/http/header_validators/envoy_default/base_header_validator_test.cc similarity index 97% rename from test/extensions/http/header_validators/envoy_default/header_validator_test.cc rename to test/extensions/http/header_validators/envoy_default/base_header_validator_test.cc index 5b81d78360af..77591cbe1f74 100644 --- a/test/extensions/http/header_validators/envoy_default/header_validator_test.cc +++ b/test/extensions/http/header_validators/envoy_default/base_header_validator_test.cc @@ -1,10 +1,10 @@ -#include "test/extensions/http/header_validators/envoy_default/header_validator_test.h" - #include "envoy/http/header_validator_errors.h" #include "source/extensions/http/header_validators/envoy_default/character_tables.h" #include "source/extensions/http/header_validators/envoy_default/header_validator.h" +#include "test/extensions/http/header_validators/envoy_default/header_validator_utils.h" +#include "test/mocks/http/header_validator.h" #include "test/test_common/utility.h" namespace Envoy { @@ -18,11 +18,9 @@ using ::Envoy::Http::Protocol; using ::Envoy::Http::testCharInTable; using ::Envoy::Http::UhvResponseCodeDetail; -using ServerHeaderValidatorPtr = std::unique_ptr; - -class BaseHeaderValidatorTest : public HeaderValidatorTest, public testing::Test { +class BaseHeaderValidatorTest : public HeaderValidatorUtils, public testing::Test { protected: - ServerHeaderValidatorPtr createBase(absl::string_view config_yaml) { + std::unique_ptr createBase(absl::string_view config_yaml) { envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig typed_config; TestUtility::loadFromYaml(std::string(config_yaml), typed_config); @@ -30,6 +28,7 @@ class BaseHeaderValidatorTest : public HeaderValidatorTest, public testing::Test return std::make_unique(typed_config, Protocol::Http11, stats_, overrides_); } ConfigOverrides overrides_; + ::testing::NiceMock stats_; }; TEST_F(BaseHeaderValidatorTest, ValidateMethodPermissive) { diff --git a/test/extensions/http/header_validators/envoy_default/header_validator_utils.cc b/test/extensions/http/header_validators/envoy_default/header_validator_utils.cc new file mode 100644 index 000000000000..7dc65c0277bc --- /dev/null +++ b/test/extensions/http/header_validators/envoy_default/header_validator_utils.cc @@ -0,0 +1,44 @@ +#include "test/extensions/http/header_validators/envoy_default/header_validator_utils.h" + +#include "source/extensions/http/header_validators/envoy_default/character_tables.h" + +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Http { +namespace HeaderValidators { +namespace EnvoyDefault { + +using ::Envoy::Http::HeaderString; +using ::Envoy::Http::testCharInTable; + +void HeaderValidatorUtils::validateAllCharactersInUrlPath( + ::Envoy::Http::ServerHeaderValidator& validator, absl::string_view path, + absl::string_view additionally_allowed_characters) { + for (uint32_t ascii = 0x0; ascii <= 0xff; ++ascii) { + std::string copy(path); + copy[12] = static_cast(ascii); + HeaderString invalid_value{}; + setHeaderStringUnvalidated(invalid_value, copy); + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, {":authority", "envoy.com"}, {":method", "GET"}}; + headers.addViaMove(HeaderString(absl::string_view(":path")), std::move(invalid_value)); + if (::Envoy::Http::testCharInTable(kPathHeaderCharTable, static_cast(ascii)) || + absl::StrContains(additionally_allowed_characters, static_cast(ascii))) { + EXPECT_ACCEPT(validator.validateRequestHeaders(headers)) + << " for character " << static_cast(ascii) << " " << ascii; + } else { + auto result = validator.validateRequestHeaders(headers); + EXPECT_REJECT(result) << " for character " << static_cast(ascii) << " " << ascii; + EXPECT_EQ(result.details(), "uhv.invalid_url") + << " for character " << static_cast(ascii) << " " << ascii; + } + } +} + +} // namespace EnvoyDefault +} // namespace HeaderValidators +} // namespace Http +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/http/header_validators/envoy_default/header_validator_test.h b/test/extensions/http/header_validators/envoy_default/header_validator_utils.h similarity index 90% rename from test/extensions/http/header_validators/envoy_default/header_validator_test.h rename to test/extensions/http/header_validators/envoy_default/header_validator_utils.h index 1448e585f11c..30430006261c 100644 --- a/test/extensions/http/header_validators/envoy_default/header_validator_test.h +++ b/test/extensions/http/header_validators/envoy_default/header_validator_utils.h @@ -1,6 +1,6 @@ -#include "envoy/extensions/http/header_validators/envoy_default/v3/header_validator.pb.h" +#pragma once -#include "test/mocks/http/header_validator.h" +#include "envoy/http/header_validator.h" #include "gtest/gtest.h" @@ -21,14 +21,16 @@ namespace Http { namespace HeaderValidators { namespace EnvoyDefault { -class HeaderValidatorTest { +class HeaderValidatorUtils { protected: void setHeaderStringUnvalidated(Envoy::Http::HeaderString& header_string, absl::string_view value) { header_string.setCopyUnvalidatedForTestOnly(value); } - ::testing::NiceMock stats_; + void validateAllCharactersInUrlPath(::Envoy::Http::ServerHeaderValidator& validator, + absl::string_view path, + absl::string_view additionally_allowed_characters); static constexpr absl::string_view empty_config = "{}"; diff --git a/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc b/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc index 5304fccf127b..33a881681479 100644 --- a/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc +++ b/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc @@ -2,7 +2,8 @@ #include "source/extensions/http/header_validators/envoy_default/http1_header_validator.h" -#include "test/extensions/http/header_validators/envoy_default/header_validator_test.h" +#include "test/extensions/http/header_validators/envoy_default/header_validator_utils.h" +#include "test/mocks/http/header_validator.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" @@ -21,15 +22,16 @@ using ::Envoy::Http::TestRequestTrailerMapImpl; using ::Envoy::Http::TestResponseHeaderMapImpl; using ::Envoy::Http::UhvResponseCodeDetail; -class Http1HeaderValidatorTest : public HeaderValidatorTest, public testing::Test { +class Http1HeaderValidatorTest : public HeaderValidatorUtils, public testing::Test { protected: ServerHttp1HeaderValidatorPtr createH1(absl::string_view config_yaml) { envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig typed_config; TestUtility::loadFromYaml(std::string(config_yaml), typed_config); + ConfigOverrides overrides(scoped_runtime_.loader().snapshot()); return std::make_unique(typed_config, Protocol::Http11, stats_, - overrides_); + overrides); } ClientHttp1HeaderValidatorPtr createH1Client(absl::string_view config_yaml) { @@ -37,8 +39,9 @@ class Http1HeaderValidatorTest : public HeaderValidatorTest, public testing::Tes typed_config; TestUtility::loadFromYaml(std::string(config_yaml), typed_config); + ConfigOverrides overrides(scoped_runtime_.loader().snapshot()); return std::make_unique(typed_config, Protocol::Http11, stats_, - overrides_); + overrides); } TestRequestHeaderMapImpl makeGoodRequestHeaders() { @@ -50,8 +53,8 @@ class Http1HeaderValidatorTest : public HeaderValidatorTest, public testing::Tes return TestResponseHeaderMapImpl{{":status", "200"}}; } + ::testing::NiceMock stats_; TestScopedRuntime scoped_runtime_; - ConfigOverrides overrides_; }; TEST_F(Http1HeaderValidatorTest, GoodHeadersAccepted) { @@ -758,30 +761,78 @@ TEST_F(Http1HeaderValidatorTest, InvalidResponseHeaderBeforeSendingDownstream) { EXPECT_EQ(result.details(), "uhv.invalid_value_characters"); } -TEST_F(Http1HeaderValidatorTest, BackslashInPathIsTranslatedToSlash) { - scoped_runtime_.mergeValues( - {{"envoy.reloadable_features.uhv_translate_backslash_to_slash", "true"}}); - ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, - {":path", "/path\\with/back\\/slash%5C"}, - {":authority", "envoy.com"}, - {":method", "GET"}}; - auto uhv = createH1(empty_config); +namespace { +constexpr absl::string_view AdditionallyAllowedCharacters = R"str("<>[]^`{}\|)str"; +} // namespace + +// Validate that H/1 UHV allows additional characters "<>[]^`{}\| and encodes +// them when path normalization is enabled. +TEST_F(Http1HeaderValidatorTest, AdditionalCharactersInPathAllowed) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + auto uhv = createH1(fragment_in_path_allowed); + validateAllCharactersInUrlPath(*uhv, "/path/with/additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharacters)); + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with", AdditionallyAllowedCharacters)}}; EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); - EXPECT_EQ(headers.path(), "/path/with/back/slash%5C"); + // Note that \ is translated to / and [] remain unencoded + EXPECT_EQ(headers.path(), "/path/with%22%3C%3E[]%5E%60%7B%7D/%7C"); } -TEST_F(Http1HeaderValidatorTest, BackslashInPathIsRejectedWithOverride) { - scoped_runtime_.mergeValues( - {{"envoy.reloadable_features.uhv_translate_backslash_to_slash", "false"}}); - ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, - {":path", "/path\\with/back\\/slash%5c"}, - {":authority", "envoy.com"}, - {":method", "GET"}}; - auto uhv = createH1(empty_config); +// Validate that without path normalization additional characters remain untouched +TEST_F(Http1HeaderValidatorTest, AdditionalCharactersInPathAllowedWithoutPathNormalization) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + auto uhv = createH1(no_path_normalization); - EXPECT_REJECT_WITH_DETAILS(uhv->validateRequestHeaders(headers), "uhv.invalid_url"); + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with", AdditionallyAllowedCharacters)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + EXPECT_EQ(headers.path(), R"str(/path/with"<>[]^`{}\|)str"); +} + +TEST_F(Http1HeaderValidatorTest, AdditionalCharactersInQueryAllowed) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + auto uhv = createH1(fragment_in_path_allowed); + + validateAllCharactersInUrlPath(*uhv, "/query?key=additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharacters)); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with?value=", AdditionallyAllowedCharacters)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + // Additional characters in query always remain untouched + EXPECT_EQ(headers.path(), R"str(/path/with?value="<>[]^`{}\|)str"); +} + +TEST_F(Http1HeaderValidatorTest, AdditionalCharactersInFragmentAllowed) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + auto uhv = createH1(fragment_in_path_allowed); + + validateAllCharactersInUrlPath(*uhv, "/q?k=v#fragment/additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharacters)); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with?value=aaa#", AdditionallyAllowedCharacters)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + // UHV strips fragment from URL path + EXPECT_EQ(headers.path(), "/path/with?value=aaa"); } } // namespace diff --git a/test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc b/test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc index 84192aba13d7..83ada3244c69 100644 --- a/test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc +++ b/test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc @@ -3,7 +3,8 @@ #include "source/extensions/http/header_validators/envoy_default/character_tables.h" #include "source/extensions/http/header_validators/envoy_default/http2_header_validator.h" -#include "test/extensions/http/header_validators/envoy_default/header_validator_test.h" +#include "test/extensions/http/header_validators/envoy_default/header_validator_utils.h" +#include "test/mocks/http/header_validator.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" @@ -23,15 +24,15 @@ using ::Envoy::Http::TestRequestTrailerMapImpl; using ::Envoy::Http::TestResponseHeaderMapImpl; using ::Envoy::Http::UhvResponseCodeDetail; -class Http2HeaderValidatorTest : public HeaderValidatorTest, public testing::Test { +class Http2HeaderValidatorTest : public HeaderValidatorUtils, public testing::Test { protected: ::Envoy::Http::ServerHeaderValidatorPtr createH2ServerUhv(absl::string_view config_yaml) { envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig typed_config; TestUtility::loadFromYaml(std::string(config_yaml), typed_config); - return std::make_unique(typed_config, Protocol::Http2, stats_, - overrides_); + ConfigOverrides overrides(scoped_runtime_.loader().snapshot()); + return std::make_unique(typed_config, protocol_, stats_, overrides); } ::Envoy::Http::ClientHeaderValidatorPtr createH2ClientUhv(absl::string_view config_yaml) { @@ -39,8 +40,8 @@ class Http2HeaderValidatorTest : public HeaderValidatorTest, public testing::Tes typed_config; TestUtility::loadFromYaml(std::string(config_yaml), typed_config); - return std::make_unique(typed_config, Protocol::Http2, stats_, - overrides_); + ConfigOverrides overrides(scoped_runtime_.loader().snapshot()); + return std::make_unique(typed_config, protocol_, stats_, overrides); } std::unique_ptr createH2BaseUhv(absl::string_view config_yaml) { @@ -48,8 +49,8 @@ class Http2HeaderValidatorTest : public HeaderValidatorTest, public testing::Tes typed_config; TestUtility::loadFromYaml(std::string(config_yaml), typed_config); - return std::make_unique(typed_config, Protocol::Http2, stats_, - overrides_); + ConfigOverrides overrides(scoped_runtime_.loader().snapshot()); + return std::make_unique(typed_config, protocol_, stats_, overrides); } TestRequestHeaderMapImpl makeGoodRequestHeaders() { @@ -61,8 +62,9 @@ class Http2HeaderValidatorTest : public HeaderValidatorTest, public testing::Tes return TestResponseHeaderMapImpl{{":status", "200"}}; } + ::testing::NiceMock stats_; TestScopedRuntime scoped_runtime_; - ConfigOverrides overrides_; + Protocol protocol_{Protocol::Http2}; }; TEST_F(Http2HeaderValidatorTest, GoodHeadersAccepted) { @@ -461,7 +463,7 @@ TEST_F(Http2HeaderValidatorTest, ValidateGenericHeaderNameRejectConnectionHeader TEST_F(Http2HeaderValidatorTest, ValidateRequestHeaderPath) { auto uhv = createH2ServerUhv(empty_config); TestRequestHeaderMapImpl request_headers = makeGoodRequestHeaders(); - request_headers.setPath("/ bad path"); + request_headers.setPath("/ bad\x7Fpath"); EXPECT_REJECT_WITH_DETAILS(uhv->validateRequestHeaders(request_headers), UhvResponseCodeDetail::get().InvalidUrl); @@ -787,30 +789,177 @@ TEST_F(Http2HeaderValidatorTest, ValidateInvalidValueResponseTrailerMapServerCod EXPECT_EQ(result.details(), "uhv.invalid_value_characters"); } -TEST_F(Http2HeaderValidatorTest, BackslashInPathIsTranslatedToSlash) { - scoped_runtime_.mergeValues( - {{"envoy.reloadable_features.uhv_translate_backslash_to_slash", "true"}}); - ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, - {":path", "/path\\with/back\\/slash%5C"}, - {":authority", "envoy.com"}, - {":method", "GET"}}; - auto uhv = createH2ServerUhv(empty_config); +namespace { +std::string generateExtendedAsciiString() { + std::string extended_ascii_string; + extended_ascii_string.reserve(0x80); + for (uint32_t ascii = 0x80; ascii <= 0xff; ++ascii) { + extended_ascii_string.push_back(static_cast(ascii)); + } + return extended_ascii_string; +} +std::string generatePercentEncodedExtendedAscii() { + std::string encoded_extended_ascii_string; + encoded_extended_ascii_string.reserve(0x80 * 3); + for (uint32_t ascii = 0x80; ascii <= 0xff; ++ascii) { + encoded_extended_ascii_string.append(fmt::format("%{:02X}", static_cast(ascii))); + } + return encoded_extended_ascii_string; +} + +// H/3 also allows "<>[]^`{}\| space, TAB in :path +static const std::string AdditionallyAllowedCharactersHttp3 = "\t \"<>[]^`{}\\|"; + +// H/2 in addition to H/3 also allows extended ASCII in :path +static const std::string AdditionallyAllowedCharactersHttp2 = + absl::StrCat(AdditionallyAllowedCharactersHttp3, generateExtendedAsciiString()); +} // namespace + +// Validate that H/2 UHV allows additional characters "<>[]^`{}\| space TAB and extended +// ASCII and encodes them when path normalization is enabled. +TEST_F(Http2HeaderValidatorTest, AdditionalCharactersInPathAllowedHttp2) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + auto uhv = createH2ServerUhv(fragment_in_path_allowed); + validateAllCharactersInUrlPath(*uhv, "/path/with/additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharactersHttp2)); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with", AdditionallyAllowedCharactersHttp2)}}; EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); - EXPECT_EQ(headers.path(), "/path/with/back/slash%5C"); + // Note that \ is translated to / and [] remain unencoded + EXPECT_EQ(headers.path(), absl::StrCat("/path/with%09%20%22%3C%3E[]%5E%60%7B%7D/%7C", + generatePercentEncodedExtendedAscii())); } -TEST_F(Http2HeaderValidatorTest, BackslashInPathIsRejectedWithOverride) { - scoped_runtime_.mergeValues( - {{"envoy.reloadable_features.uhv_translate_backslash_to_slash", "false"}}); - ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, - {":path", "/path\\with/back\\/slash%5c"}, - {":authority", "envoy.com"}, - {":method", "GET"}}; - auto uhv = createH2ServerUhv(empty_config); +// Validate that H/3 UHV allows additional characters "<>[]^`{}\| space TAB +// and encodes them when path normalization is enabled. +TEST_F(Http2HeaderValidatorTest, AdditionalCharactersInPathAllowedHttp3) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + protocol_ = Protocol::Http3; + auto uhv = createH2ServerUhv(fragment_in_path_allowed); + validateAllCharactersInUrlPath(*uhv, "/path/with/additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharactersHttp3)); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with", AdditionallyAllowedCharactersHttp3)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + // Note that \ is translated to / and [] remain unencoded + EXPECT_EQ(headers.path(), "/path/with%09%20%22%3C%3E[]%5E%60%7B%7D/%7C"); +} + +// Validate that without path normalization additional characters remain untouched +TEST_F(Http2HeaderValidatorTest, AdditionalCharactersInPathAllowedWithoutPathNormalizationHttp2) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + auto uhv = createH2ServerUhv(no_path_normalization); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with", AdditionallyAllowedCharactersHttp2)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + EXPECT_EQ(headers.path(), + absl::StrCat("/path/with\t \"<>[]^`{}\\|", generateExtendedAsciiString())); +} + +TEST_F(Http2HeaderValidatorTest, AdditionalCharactersInPathAllowedWithoutPathNormalizationHttp3) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + protocol_ = Protocol::Http3; + auto uhv = createH2ServerUhv(no_path_normalization); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with", AdditionallyAllowedCharactersHttp3)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + EXPECT_EQ(headers.path(), "/path/with\t \"<>[]^`{}\\|"); +} + +TEST_F(Http2HeaderValidatorTest, AdditionalCharactersInQueryAllowedHttp2) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + auto uhv = createH2ServerUhv(fragment_in_path_allowed); + + validateAllCharactersInUrlPath(*uhv, "/query?key=additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharactersHttp2)); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with?value=", AdditionallyAllowedCharactersHttp2)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + // Additional characters in query always remain untouched + EXPECT_EQ(headers.path(), + absl::StrCat("/path/with?value=\t \"<>[]^`{}\\|", generateExtendedAsciiString())); +} + +TEST_F(Http2HeaderValidatorTest, AdditionalCharactersInQueryAllowedHttp3) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + protocol_ = Protocol::Http3; + auto uhv = createH2ServerUhv(fragment_in_path_allowed); + + validateAllCharactersInUrlPath(*uhv, "/query?key=additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharactersHttp3)); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with?value=", AdditionallyAllowedCharactersHttp3)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + // Additional characters in query always remain untouched + EXPECT_EQ(headers.path(), "/path/with?value=\t \"<>[]^`{}\\|"); +} + +TEST_F(Http2HeaderValidatorTest, AdditionalCharactersInFragmentAllowedHttp2) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + auto uhv = createH2ServerUhv(fragment_in_path_allowed); - EXPECT_REJECT_WITH_DETAILS(uhv->validateRequestHeaders(headers), "uhv.invalid_url"); + validateAllCharactersInUrlPath(*uhv, "/q?k=v#fragment/additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharactersHttp2)); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with?value=aaa#", AdditionallyAllowedCharactersHttp2)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + // UHV strips fragment from URL path + EXPECT_EQ(headers.path(), "/path/with?value=aaa"); +} + +TEST_F(Http2HeaderValidatorTest, AdditionalCharactersInFragmentAllowedHttp3) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + protocol_ = Protocol::Http3; + auto uhv = createH2ServerUhv(fragment_in_path_allowed); + + validateAllCharactersInUrlPath(*uhv, "/q?k=v#fragment/additional/characters", + absl::StrCat("?#", AdditionallyAllowedCharactersHttp3)); + + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, + {":authority", "envoy.com"}, + {":method", "GET"}, + {":path", absl::StrCat("/path/with?value=aaa#", AdditionallyAllowedCharactersHttp3)}}; + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + // UHV strips fragment from URL path + EXPECT_EQ(headers.path(), "/path/with?value=aaa"); } } // namespace diff --git a/test/extensions/http/header_validators/envoy_default/http_common_validation_test.cc b/test/extensions/http/header_validators/envoy_default/http_common_validation_test.cc index 94055c9da12a..059772d6c316 100644 --- a/test/extensions/http/header_validators/envoy_default/http_common_validation_test.cc +++ b/test/extensions/http/header_validators/envoy_default/http_common_validation_test.cc @@ -1,9 +1,11 @@ #include "envoy/http/header_validator_errors.h" +#include "source/extensions/http/header_validators/envoy_default/character_tables.h" #include "source/extensions/http/header_validators/envoy_default/http1_header_validator.h" #include "source/extensions/http/header_validators/envoy_default/http2_header_validator.h" -#include "test/extensions/http/header_validators/envoy_default/header_validator_test.h" +#include "test/extensions/http/header_validators/envoy_default/header_validator_utils.h" +#include "test/mocks/http/header_validator.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" @@ -14,12 +16,13 @@ namespace HeaderValidators { namespace EnvoyDefault { namespace { +using ::Envoy::Http::HeaderString; using ::Envoy::Http::Protocol; using ::Envoy::Http::TestRequestHeaderMapImpl; using ::Envoy::Http::UhvResponseCodeDetail; // This test suite runs the same tests against both H/1 and H/2 header validators. -class HttpCommonValidationTest : public HeaderValidatorTest, +class HttpCommonValidationTest : public HeaderValidatorUtils, public testing::TestWithParam { protected: ::Envoy::Http::ServerHeaderValidatorPtr createUhv(absl::string_view config_yaml) { @@ -27,16 +30,17 @@ class HttpCommonValidationTest : public HeaderValidatorTest, typed_config; TestUtility::loadFromYaml(std::string(config_yaml), typed_config); + ConfigOverrides overrides(scoped_runtime_.loader().snapshot()); if (GetParam() == Protocol::Http11) { return std::make_unique(typed_config, Protocol::Http11, stats_, - overrides_); + overrides); } return std::make_unique(typed_config, GetParam(), stats_, - overrides_); + overrides); } + ::testing::NiceMock stats_; TestScopedRuntime scoped_runtime_; - ConfigOverrides overrides_; }; std::string protocolTestParamsToString(const ::testing::TestParamInfo& params) { @@ -76,6 +80,54 @@ TEST_P(HttpCommonValidationTest, MalformedUrlEncodingRejectedWithOverride) { EXPECT_REJECT_WITH_DETAILS(uhv->transformRequestHeaders(headers), "uhv.invalid_url"); } +TEST_P(HttpCommonValidationTest, BackslashInPathIsTranslatedToSlash) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "true"}}); + ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, + {":path", "/path\\with/back\\/slash%5C"}, + {":authority", "envoy.com"}, + {":method", "GET"}}; + auto uhv = createUhv(empty_config); + + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + EXPECT_EQ(headers.path(), "/path/with/back/slash%5C"); +} + +TEST_P(HttpCommonValidationTest, BackslashInPathIsRejectedWithOverride) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "false"}}); + ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, + {":path", "/path\\with/back\\/slash%5c"}, + {":authority", "envoy.com"}, + {":method", "GET"}}; + auto uhv = createUhv(empty_config); + + EXPECT_REJECT_WITH_DETAILS(uhv->validateRequestHeaders(headers), "uhv.invalid_url"); +} + +// With the allow_non_compliant_characters_in_path set to false a request with URL path containing +// characters not allowed in https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 RFC is +// rejected. +TEST_P(HttpCommonValidationTest, PathCharacterSetValidation) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "false"}}); + auto uhv = createUhv(fragment_in_path_allowed); + // ? and # start query and fragment + // validateAllCharacters modifies 12th character in the `path` parameter + validateAllCharactersInUrlPath(*uhv, "/path/with/additional/characters", "?#"); +} + +TEST_P(HttpCommonValidationTest, QueryCharacterSetValidation) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "false"}}); + // # starts fragment + auto uhv = createUhv(fragment_in_path_allowed); + validateAllCharactersInUrlPath(*uhv, "/query?key=additional/characters", "?#"); +} + +TEST_P(HttpCommonValidationTest, FragmentCharacterSetValidation) { + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "false"}}); + auto uhv = createUhv(fragment_in_path_allowed); + validateAllCharactersInUrlPath(*uhv, "/q?k=v#fragment/additional/characters", "?"); +} + TEST_P(HttpCommonValidationTest, PathWithFragmentRejectedByDefault) { TestRequestHeaderMapImpl headers{{":scheme", "https"}, {":path", "/path/with?query=and#fragment"}, @@ -167,6 +219,28 @@ TEST_P(HttpCommonValidationTest, RejectEncodedSlashes) { "uhv.escaped_slashes_in_url_path"); } +TEST_P(HttpCommonValidationTest, RejectPercent00ByDefault) { + ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, + {":method", "GET"}, + {":path", "/dir1%00dir2"}, + {":authority", "envoy.com"}}; + auto uhv = createUhv(empty_config); + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_REJECT_WITH_DETAILS(uhv->transformRequestHeaders(headers), "uhv.percent_00_in_url_path"); +} + +TEST_P(HttpCommonValidationTest, AllowPercent00WithOverride) { + scoped_runtime_.mergeValues({{"envoy.uhv.reject_percent_00", "false"}}); + ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, + {":method", "GET"}, + {":path", "/dir1%00dir2"}, + {":authority", "envoy.com"}}; + auto uhv = createUhv(empty_config); + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + EXPECT_EQ(headers.path(), "/dir1%00dir2"); +} + } // namespace } // namespace EnvoyDefault } // namespace HeaderValidators diff --git a/test/extensions/http/header_validators/envoy_default/path_normalizer_test.cc b/test/extensions/http/header_validators/envoy_default/path_normalizer_test.cc index 17ebacd55908..e2cafabed358 100644 --- a/test/extensions/http/header_validators/envoy_default/path_normalizer_test.cc +++ b/test/extensions/http/header_validators/envoy_default/path_normalizer_test.cc @@ -482,24 +482,21 @@ TEST_F(PathNormalizerTest, NormalizePathUriAsteriskFormNotOptions) { EXPECT_EQ(result.details(), UhvResponseCodeDetail::get().InvalidUrl); } -TEST_F(PathNormalizerTest, BackslashTranslatedToSlash) { - scoped_runtime_.mergeValues({{"envoy.uhv.preserve_url_encoded_case", "false"}}); - scoped_runtime_.mergeValues( - {{"envoy.reloadable_features.uhv_translate_backslash_to_slash", "true"}}); - ::Envoy::Http::TestRequestHeaderMapImpl headers{{":path", "/path\\with/back\\/slash%5c"}}; +TEST_F(PathNormalizerTest, BackslashTranslatedToSlashByDefault) { + ::Envoy::Http::TestRequestHeaderMapImpl headers{ + {":path", "/path\\with/back\\/slash%5c?key=val\\ue"}}; auto normalizer = create(empty_config); auto result = normalizer->normalizePathUri(headers); EXPECT_EQ(result.action(), PathNormalizer::PathNormalizationResult::Action::Accept); - EXPECT_EQ(headers.path(), "/path/with/back/slash%5C"); + // In query backslash is untouched + EXPECT_EQ(headers.path(), "/path/with/back/slash%5c?key=val\\ue"); } TEST_F(PathNormalizerTest, BackslashPreservedWithOverride) { - scoped_runtime_.mergeValues({{"envoy.uhv.preserve_url_encoded_case", "false"}}); - scoped_runtime_.mergeValues( - {{"envoy.reloadable_features.uhv_translate_backslash_to_slash", "false"}}); - ::Envoy::Http::TestRequestHeaderMapImpl headers{{":path", "/path\\with/back\\/slash%5c"}}; + scoped_runtime_.mergeValues({{"envoy.uhv.allow_non_compliant_characters_in_path", "false"}}); + ::Envoy::Http::TestRequestHeaderMapImpl headers{{":path", "/path\\with/back\\/slash%5C"}}; auto normalizer = create(empty_config); auto result = normalizer->normalizePathUri(headers); diff --git a/test/extensions/listener_managers/listener_manager/BUILD b/test/extensions/listener_managers/listener_manager/BUILD index a0dda9032276..11bf849371e1 100644 --- a/test/extensions/listener_managers/listener_manager/BUILD +++ b/test/extensions/listener_managers/listener_manager/BUILD @@ -50,7 +50,6 @@ envoy_cc_test_library( envoy_cc_test( name = "listener_manager_impl_test", srcs = ["listener_manager_impl_test.cc"], - shard_count = 4, deps = [ ":listener_manager_impl_test_lib", "//source/common/api:os_sys_calls_lib", diff --git a/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc b/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc index 5de22a6d181e..e0cf7e3f06c8 100644 --- a/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc +++ b/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc @@ -370,8 +370,13 @@ TEST_P(MaglevLoadBalancerTest, Weighted) { // Locality weighted sanity test when localities have the same weights. Host weights for hosts in // different localities shouldn't matter. TEST_P(MaglevLoadBalancerTest, LocalityWeightedSameLocalityWeights) { - host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), 1), - makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), 2)}; + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), zone_a, 1), + makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), zone_b, 2)}; host_set_.healthy_hosts_ = host_set_.hosts_; host_set_.hosts_per_locality_ = makeHostsPerLocality({{host_set_.hosts_[0]}, {host_set_.hosts_[1]}}); @@ -413,9 +418,16 @@ TEST_P(MaglevLoadBalancerTest, LocalityWeightedSameLocalityWeights) { // Locality weighted sanity test when localities have different weights. Host weights for hosts in // different localities shouldn't matter. TEST_P(MaglevLoadBalancerTest, LocalityWeightedDifferentLocalityWeights) { - host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), 1), - makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), 2), - makeTestHost(info_, "tcp://127.0.0.1:92", simTime(), 3)}; + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), zone_a, 1), + makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), zone_c, 2), + makeTestHost(info_, "tcp://127.0.0.1:92", simTime(), zone_b, 3)}; host_set_.healthy_hosts_ = host_set_.hosts_; host_set_.hosts_per_locality_ = makeHostsPerLocality({{host_set_.hosts_[0]}, {host_set_.hosts_[2]}, {host_set_.hosts_[1]}}); @@ -472,8 +484,13 @@ TEST_P(MaglevLoadBalancerTest, LocalityWeightedAllZeroLocalityWeights) { // Validate that when we are in global panic and have localities, we get sane // results (fall back to non-healthy hosts). TEST_P(MaglevLoadBalancerTest, LocalityWeightedGlobalPanic) { - host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), 1), - makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), 2)}; + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), zone_a, 1), + makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), zone_b, 2)}; host_set_.healthy_hosts_ = {}; host_set_.hosts_per_locality_ = makeHostsPerLocality({{host_set_.hosts_[0]}, {host_set_.hosts_[1]}}); @@ -515,10 +532,16 @@ TEST_P(MaglevLoadBalancerTest, LocalityWeightedGlobalPanic) { // Given extremely lopsided locality weights, and a table that isn't large enough to fit all hosts, // expect that the least-weighted hosts appear once, and the most-weighted host fills the remainder. TEST_P(MaglevLoadBalancerTest, LocalityWeightedLopsided) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + host_set_.hosts_.clear(); HostVector heavy_but_sparse, light_but_dense; for (uint32_t i = 0; i < 1024; ++i) { - auto host(makeTestHost(info_, fmt::format("tcp://127.0.0.1:{}", i), simTime())); + auto host_locality = i == 0 ? zone_a : zone_b; + auto host(makeTestHost(info_, fmt::format("tcp://127.0.0.1:{}", i), simTime(), host_locality)); host_set_.hosts_.push_back(host); (i == 0 ? heavy_but_sparse : light_but_dense).push_back(host); } diff --git a/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc b/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc index 911bbc51e2b7..97f37b70d9be 100644 --- a/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc +++ b/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc @@ -639,8 +639,13 @@ TEST_P(RingHashLoadBalancerTest, HostWeightedLargeRing) { // Given locality weights all 0, expect the same behavior as if no hosts were provided at all. TEST_P(RingHashLoadBalancerTest, ZeroLocalityWeights) { - hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:91", simTime())}; + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + + hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), zone_b)}; hostSet().healthy_hosts_ = hostSet().hosts_; hostSet().hosts_per_locality_ = makeHostsPerLocality({{hostSet().hosts_[0]}, {hostSet().hosts_[1]}}); @@ -655,10 +660,19 @@ TEST_P(RingHashLoadBalancerTest, ZeroLocalityWeights) { // Given localities with weights 1, 2, 3 and 0, and a ring size of exactly 6, expect the correct // number of hashes for each host. TEST_P(RingHashLoadBalancerTest, LocalityWeightedTinyRing) { - hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:91", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:92", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:93", simTime())}; + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + envoy::config::core::v3::Locality zone_d; + zone_d.set_zone("D"); + + hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:92", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:93", simTime(), zone_d)}; hostSet().healthy_hosts_ = hostSet().hosts_; hostSet().hosts_per_locality_ = makeHostsPerLocality( {{hostSet().hosts_[0]}, {hostSet().hosts_[1]}, {hostSet().hosts_[2]}, {hostSet().hosts_[3]}}); @@ -690,10 +704,19 @@ TEST_P(RingHashLoadBalancerTest, LocalityWeightedTinyRing) { // Given localities with weights 1, 2, 3 and 0, and a sufficiently large ring, expect that requests // will distribute to the hosts with approximately the right proportion. TEST_P(RingHashLoadBalancerTest, LocalityWeightedLargeRing) { - hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:91", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:92", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:93", simTime())}; + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + envoy::config::core::v3::Locality zone_d; + zone_d.set_zone("D"); + + hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:92", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:93", simTime(), zone_d)}; hostSet().healthy_hosts_ = hostSet().hosts_; hostSet().hosts_per_locality_ = makeHostsPerLocality( {{hostSet().hosts_[0]}, {hostSet().hosts_[1]}, {hostSet().hosts_[2]}, {hostSet().hosts_[3]}}); @@ -725,12 +748,17 @@ TEST_P(RingHashLoadBalancerTest, LocalityWeightedLargeRing) { // Given both host weights and locality weights, expect the correct number of hashes for each host. TEST_P(RingHashLoadBalancerTest, HostAndLocalityWeightedTinyRing) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + // :90 and :91 have a 1:2 ratio within the first locality, :92 and :93 have a 1:2 ratio within the // second locality, and the two localities have a 1:2 ratio overall. - hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), 1), - makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), 2), - makeTestHost(info_, "tcp://127.0.0.1:92", simTime(), 1), - makeTestHost(info_, "tcp://127.0.0.1:93", simTime(), 2)}; + hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), zone_a, 1), + makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), zone_a, 2), + makeTestHost(info_, "tcp://127.0.0.1:92", simTime(), zone_b, 1), + makeTestHost(info_, "tcp://127.0.0.1:93", simTime(), zone_b, 2)}; hostSet().healthy_hosts_ = hostSet().hosts_; hostSet().hosts_per_locality_ = makeHostsPerLocality( {{hostSet().hosts_[0], hostSet().hosts_[1]}, {hostSet().hosts_[2], hostSet().hosts_[3]}}); @@ -763,12 +791,17 @@ TEST_P(RingHashLoadBalancerTest, HostAndLocalityWeightedTinyRing) { // Given both host weights and locality weights, and a sufficiently large ring, expect that requests // will distribute to the hosts with approximately the right proportion. TEST_P(RingHashLoadBalancerTest, HostAndLocalityWeightedLargeRing) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + // :90 and :91 have a 1:2 ratio within the first locality, :92 and :93 have a 1:2 ratio within the // second locality, and the two localities have a 1:2 ratio overall. - hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), 1), - makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), 2), - makeTestHost(info_, "tcp://127.0.0.1:92", simTime(), 1), - makeTestHost(info_, "tcp://127.0.0.1:93", simTime(), 2)}; + hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", simTime(), zone_a, 1), + makeTestHost(info_, "tcp://127.0.0.1:91", simTime(), zone_a, 2), + makeTestHost(info_, "tcp://127.0.0.1:92", simTime(), zone_b, 1), + makeTestHost(info_, "tcp://127.0.0.1:93", simTime(), zone_b, 2)}; hostSet().healthy_hosts_ = hostSet().hosts_; hostSet().hosts_per_locality_ = makeHostsPerLocality( {{hostSet().hosts_[0], hostSet().hosts_[1]}, {hostSet().hosts_[2], hostSet().hosts_[3]}}); @@ -872,10 +905,16 @@ TEST_P(RingHashLoadBalancerTest, LargeFractionalScale) { // Given extremely lopsided locality weights, and a ring that isn't large enough to fit all hosts, // expect that the correct proportion of hosts will be present in the ring. TEST_P(RingHashLoadBalancerTest, LopsidedWeightSmallScale) { + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + hostSet().hosts_.clear(); HostVector heavy_but_sparse, light_but_dense; for (uint32_t i = 0; i < 1024; ++i) { - auto host(makeTestHost(info_, fmt::format("tcp://127.0.0.1:{}", i), simTime())); + auto host_locality = i == 0 ? zone_a : zone_b; + auto host(makeTestHost(info_, fmt::format("tcp://127.0.0.1:{}", i), simTime(), host_locality)); hostSet().hosts_.push_back(host); (i == 0 ? heavy_but_sparse : light_but_dense).push_back(host); } diff --git a/test/extensions/quic/connection_id_generator/BUILD b/test/extensions/quic/connection_id_generator/BUILD new file mode 100644 index 000000000000..f2902af1e9e6 --- /dev/null +++ b/test/extensions/quic/connection_id_generator/BUILD @@ -0,0 +1,31 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test_library( + name = "matchers", + hdrs = ["matchers.h"], + deps = ["//test/mocks/network:network_mocks"], +) + +envoy_extension_cc_test( + name = "envoy_deterministic_connection_id_generator_test", + srcs = ["envoy_deterministic_connection_id_generator_test.cc"], + extension_names = ["envoy.quic.deterministic_connection_id_generator"], + tags = ["nofips"], + deps = [ + ":matchers", + "//source/extensions/quic/connection_id_generator:envoy_deterministic_connection_id_generator_lib", + "@com_github_google_quiche//:quic_test_tools_test_utils_lib", + ], +) diff --git a/test/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator_test.cc b/test/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator_test.cc new file mode 100644 index 000000000000..5e8571d2420e --- /dev/null +++ b/test/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator_test.cc @@ -0,0 +1,123 @@ +#include "source/extensions/quic/connection_id_generator/envoy_deterministic_connection_id_generator.h" + +#include "test/extensions/quic/connection_id_generator/matchers.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +namespace Envoy { +namespace Quic { + +using Matcher::FactoryFunctions; +using Matcher::GivenPacket; +using ::quic::QuicConnectionId; +using ::quic::test::QuicTest; +using ::quic::test::TestConnectionId; +using ::quic::test::TestConnectionIdNineBytesLong; +using ::testing::ElementsAre; + +class EnvoyDeterministicConnectionIdGeneratorTest : public QuicTest { +public: + EnvoyDeterministicConnectionIdGeneratorTest() + : connection_id_length_(12), + generator_(EnvoyDeterministicConnectionIdGenerator(connection_id_length_)) {} + +protected: + int connection_id_length_; + EnvoyDeterministicConnectionIdGenerator generator_; +}; + +TEST_F(EnvoyDeterministicConnectionIdGeneratorTest, NextConnectionIdIsDeterministic) { + // Verify that two equal connection IDs get the same replacement. + QuicConnectionId connection_id64a = TestConnectionId(33); + QuicConnectionId connection_id64b = TestConnectionId(33); + EXPECT_EQ(connection_id64a, connection_id64b); + EXPECT_EQ(*generator_.GenerateNextConnectionId(connection_id64a), + *generator_.GenerateNextConnectionId(connection_id64b)); + QuicConnectionId connection_id72a = TestConnectionIdNineBytesLong(42); + QuicConnectionId connection_id72b = TestConnectionIdNineBytesLong(42); + EXPECT_EQ(connection_id72a, connection_id72b); + EXPECT_EQ(*generator_.GenerateNextConnectionId(connection_id72a), + *generator_.GenerateNextConnectionId(connection_id72b)); +} + +TEST_F(EnvoyDeterministicConnectionIdGeneratorTest, NextConnectionIdLengthIsCorrect) { + // Verify that all generated IDs are of the correct length. + const char connection_id_bytes[255] = {}; + for (uint8_t i = 0; i < sizeof(connection_id_bytes) - 1; ++i) { + QuicConnectionId connection_id(connection_id_bytes, i); + absl::optional replacement_connection_id = + generator_.GenerateNextConnectionId(connection_id); + ASSERT_TRUE(replacement_connection_id.has_value()); + EXPECT_EQ(connection_id_length_, replacement_connection_id->length()); + } +} + +TEST_F(EnvoyDeterministicConnectionIdGeneratorTest, NextConnectionIdHasEntropy) { + // Make sure all these test connection IDs have different replacements. + for (uint64_t i = 0; i < 256; ++i) { + QuicConnectionId connection_id_i = TestConnectionId(i); + absl::optional new_i = generator_.GenerateNextConnectionId(connection_id_i); + ASSERT_TRUE(new_i.has_value()); + EXPECT_NE(connection_id_i, *new_i); + for (uint64_t j = i + 1; j <= 256; ++j) { + QuicConnectionId connection_id_j = TestConnectionId(j); + EXPECT_NE(connection_id_i, connection_id_j); + absl::optional new_j = generator_.GenerateNextConnectionId(connection_id_j); + ASSERT_TRUE(new_j.has_value()); + EXPECT_NE(*new_i, *new_j); + } + } +} + +static uint32_t workerIdFromConnId(QuicConnectionId& id) { + return absl::little_endian::Load32(id.data()); +} + +TEST_F(EnvoyDeterministicConnectionIdGeneratorTest, NextConnectionIdPersistsWorkerThreadBytes) { + for (uint64_t i = 0; i < 256; ++i) { + QuicConnectionId id = TestConnectionId(i << 16); + auto next_id = generator_.GenerateNextConnectionId(id); + ASSERT_TRUE(next_id.has_value()); + EXPECT_EQ(workerIdFromConnId(next_id.value()), workerIdFromConnId(id)) + << "next_id = " << next_id.value() << ", id = " << id; + } +} + +class EnvoyDeterministicConnectionIdGeneratorFactoryTest : public ::testing::Test { +protected: + EnvoyDeterministicConnectionIdGeneratorFactory factory_; +}; + +TEST_F(EnvoyDeterministicConnectionIdGeneratorFactoryTest, + ConnectionIdWorkerSelectorReturnsCurrentWorkerForShortHeaderPacketsTooShort) { + Buffer::OwnedImpl buffer("aaaaaa"); + EXPECT_THAT(FactoryFunctions(factory_, 256), GivenPacket(buffer).ReturnsDefaultWorkerId()); + EXPECT_THAT(FactoryFunctions(factory_, 65536), GivenPacket(buffer).ReturnsDefaultWorkerId()); +} + +TEST_F(EnvoyDeterministicConnectionIdGeneratorFactoryTest, + ConnectionIdWorkerSelectorReturnsCurrentWorkerForLongHeaderPacketsTooShort) { + Buffer::OwnedImpl buffer("\x80xxxxxxxxxxx"); + EXPECT_THAT(FactoryFunctions(factory_, 256), GivenPacket(buffer).ReturnsDefaultWorkerId()); + EXPECT_THAT(FactoryFunctions(factory_, 65536), GivenPacket(buffer).ReturnsDefaultWorkerId()); +} + +TEST_F(EnvoyDeterministicConnectionIdGeneratorFactoryTest, + ConnectionIdWorkerSelectorReturnsBytesOneToFourModConcurrencyForShortPackets) { + Buffer::OwnedImpl buffer("x\x12\x34\x56\x78xxxxxxxxx"); + EXPECT_THAT(FactoryFunctions(factory_, 256), GivenPacket(buffer).ReturnsWorkerId(0x78)); + EXPECT_THAT(FactoryFunctions(factory_, 65536), GivenPacket(buffer).ReturnsWorkerId(0x5678)); +} + +TEST_F(EnvoyDeterministicConnectionIdGeneratorFactoryTest, + ConnectionIdWorkerSelectorReturnsBytesSixToNineModConcurrencyForLongPackets) { + Buffer::OwnedImpl buffer("\x80xxxxx\x12\x34\x56\x78xxxxxxxxx"); + EXPECT_THAT(FactoryFunctions(factory_, 256), GivenPacket(buffer).ReturnsWorkerId(0x78)); + EXPECT_THAT(FactoryFunctions(factory_, 65536), GivenPacket(buffer).ReturnsWorkerId(0x5678)); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic/connection_id_generator/matchers.h b/test/extensions/quic/connection_id_generator/matchers.h new file mode 100644 index 000000000000..9c4a658e2a0e --- /dev/null +++ b/test/extensions/quic/connection_id_generator/matchers.h @@ -0,0 +1,99 @@ +#pragma once + +#include + +#include "source/common/quic/envoy_quic_connection_id_generator_factory.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#if defined(SO_ATTACH_REUSEPORT_CBPF) && defined(__linux__) +#include +#include +#include "test/mocks/network/mocks.h" +#endif + +namespace Envoy { +namespace Quic { +namespace Matcher { + +#if defined(SO_ATTACH_REUSEPORT_CBPF) && defined(__linux__) && \ + defined(TODO_TEST_FOR_BPF_PROG_RUN_SUPPORT) +#define SUPPORTS_TESTING_BPF_PROG +#endif + +// A matcher for connection id factories to test both the bpf filter and worker selector +// functions. Recommended usage is like: +// EXPECT_THAT(FactoryFunctions(factory, concurrency), GivenPacket(buffer).ReturnsWorkerId(7)) +// +// The matcher always validates the worker selector function, and, if bpf is available, +// also validates that the bpf program returns the same value. +// +// Wrapping in FactoryFunctions is necessary because calling the factory functions directly is +// not const. + +class FactoryFunctions { +public: + FactoryFunctions(EnvoyQuicConnectionIdGeneratorFactory& factory, uint32_t concurrency) + : worker_selector_(factory.getCompatibleConnectionIdWorkerSelector(concurrency)) { +#ifdef SUPPORTS_TESTING_BPF_PROG + Network::Socket::OptionConstSharedPtr sockopt = + factory.createCompatibleLinuxBpfSocketOption(concurrency); + // Using a mock socket to capture the socket option which otherwise cannot be + // extracted from the private field in the Socket::Option. + Network::MockListenSocket mock_socket; + const sock_fprog* bpf_prog; + EXPECT_CALL(mock_socket, setSocketOption(testing::_, testing::_, testing::_, testing::_)) + .WillOnce([this](int, int, const void* optval, socklen_t) { + bpf_prog_ = static_cast(optval); + return Api::SysCallIntResult{0, 0}; + }); + sockopt->setOption(mock_socket, envoy::config::core::v3::SocketOption::STATE_BOUND); +#endif + } + const QuicConnectionIdWorkerSelector worker_selector_; +#ifdef SUPPORTS_TESTING_BPF_PROG + const sock_fprog* bpf_prog_; +#endif +}; + +MATCHER_P2(FactoryFunctionsReturnWorkerId, packet, expected_id, "") { + uint32_t id = arg.worker_selector_(*packet, 0xffffffff); + // TODO: run the bpf program, something like the below. The headers we have in the docker + // container don't seem to provide `bpf()` or `bpf_prog_run()`. +#ifdef SUPPORTS_TESTING_BPF_PROG + std::string bytes = packet->toString(); + uint32_t bpf_id = bpf_prog_run(arg.bpf_prog_, bytes.c_str()); +#else + uint32_t bpf_id = expected_id; + std::cout << "warning: not verifying bpf program due to lacking system support" << std::endl; +#endif + if (id != expected_id || bpf_id != expected_id) { + *result_listener << "\nworker selector function returned " << id; +#if defined(SO_ATTACH_REUSEPORT_CBPF) && defined(__linux__) + *result_listener << "\nbpf function returned " << bpf_id; +#endif + return false; + } + return true; +} + +class GivenPacket { +public: + GivenPacket(const Buffer::Instance& packet) : packet_(packet) {} + testing::Matcher ReturnsWorkerId(uint32_t id) { + return FactoryFunctionsReturnWorkerId(&packet_, id); + } + testing::Matcher ReturnsDefaultWorkerId() { + return FactoryFunctionsReturnWorkerId(&packet_, 0xffffffff); + } + +private: + const Buffer::Instance& packet_; +}; + +#undef SUPPORTS_TESTING_BPF_PROG + +} // namespace Matcher +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/tracers/datadog/agent_http_client_test.cc b/test/extensions/tracers/datadog/agent_http_client_test.cc index f4d59eb8b0dc..98e60c971578 100644 --- a/test/extensions/tracers/datadog/agent_http_client_test.cc +++ b/test/extensions/tracers/datadog/agent_http_client_test.cc @@ -499,8 +499,10 @@ TEST_F(DatadogAgentHttpClientTest, SkipReportIfCollectorClusterHasBeenRemoved) { // Simulate addition of an irrelevant cluster. NiceMock unrelated_cluster; unrelated_cluster.cluster_.info_->name_ = "unrelated_cluster"; - cluster_update_callbacks->onClusterAddOrUpdate(unrelated_cluster); - + Upstream::ThreadLocalClusterCommand command = + [&unrelated_cluster]() -> Upstream::ThreadLocalCluster& { return unrelated_cluster; }; + cluster_update_callbacks->onClusterAddOrUpdate(unrelated_cluster.cluster_.info_->name_, + command); // Verify that no report will be sent. EXPECT_CALL(cm.thread_local_cluster_, httpAsyncClient()).Times(0); EXPECT_CALL(cm.thread_local_cluster_.async_client_, send_(_, _, _)).Times(0); @@ -519,7 +521,11 @@ TEST_F(DatadogAgentHttpClientTest, SkipReportIfCollectorClusterHasBeenRemoved) { { // Simulate addition of the relevant cluster. - cluster_update_callbacks->onClusterAddOrUpdate(cm.thread_local_cluster_); + Upstream::ThreadLocalClusterCommand command = [&cm]() -> Upstream::ThreadLocalCluster& { + return cm.thread_local_cluster_; + }; + cluster_update_callbacks->onClusterAddOrUpdate(cm.thread_local_cluster_.info()->name(), + command); // Verify that report will be sent. EXPECT_CALL(cm.thread_local_cluster_, httpAsyncClient()) diff --git a/test/extensions/tracers/dynamic_ot/BUILD b/test/extensions/tracers/dynamic_ot/BUILD index b982d22efa6a..793c9884499b 100644 --- a/test/extensions/tracers/dynamic_ot/BUILD +++ b/test/extensions/tracers/dynamic_ot/BUILD @@ -13,6 +13,7 @@ envoy_package() envoy_extension_cc_test( name = "dynamic_opentracing_driver_impl_test", + size = "large", srcs = [ "dynamic_opentracing_driver_impl_test.cc", ], @@ -35,6 +36,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "config_test", + size = "large", srcs = ["config_test.cc"], data = [ "@io_opentracing_cpp//mocktracer:libmocktracer_plugin.so", diff --git a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc index 6dd8a6b1b093..b77dd99abb5d 100644 --- a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc +++ b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc @@ -316,7 +316,10 @@ TEST_F(ZipkinDriverTest, SkipReportIfCollectorClusterHasBeenRemoved) { // Simulate addition of an irrelevant cluster. NiceMock unrelated_cluster; unrelated_cluster.cluster_.info_->name_ = "unrelated_cluster"; - cluster_update_callbacks->onClusterAddOrUpdate(unrelated_cluster); + Upstream::ThreadLocalClusterCommand command = + [&unrelated_cluster]() -> Upstream::ThreadLocalCluster& { return unrelated_cluster; }; + cluster_update_callbacks->onClusterAddOrUpdate(unrelated_cluster.cluster_.info_->name_, + command); // Verify that no report will be sent. EXPECT_CALL(cm_.thread_local_cluster_, httpAsyncClient()).Times(0); @@ -338,7 +341,11 @@ TEST_F(ZipkinDriverTest, SkipReportIfCollectorClusterHasBeenRemoved) { { // Simulate addition of the relevant cluster. - cluster_update_callbacks->onClusterAddOrUpdate(cm_.thread_local_cluster_); + Upstream::ThreadLocalClusterCommand command = [this]() -> Upstream::ThreadLocalCluster& { + return cm_.thread_local_cluster_; + }; + cluster_update_callbacks->onClusterAddOrUpdate(cm_.thread_local_cluster_.info()->name(), + command); // Verify that report will be sent. EXPECT_CALL(cm_.thread_local_cluster_, httpAsyncClient()) diff --git a/test/extensions/transport_sockets/tls/BUILD b/test/extensions/transport_sockets/tls/BUILD index ccc4a1cdb7fa..2324f740dba9 100644 --- a/test/extensions/transport_sockets/tls/BUILD +++ b/test/extensions/transport_sockets/tls/BUILD @@ -26,7 +26,6 @@ envoy_cc_test( "//test/extensions/transport_sockets/tls/test_data:certs", ], external_deps = ["ssl"], - shard_count = 4, deps = [ ":test_private_key_method_provider_test_lib", "//envoy/network:transport_socket_interface", diff --git a/test/integration/BUILD b/test/integration/BUILD index 3c333952dc90..add276c9a8c1 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -53,7 +53,7 @@ envoy_cc_test_library( envoy_cc_test( name = "ads_integration_test", - size = "enormous", + size = "large", srcs = envoy_select_admin_functionality( ["ads_integration_test.cc"], ), @@ -327,7 +327,6 @@ envoy_cc_test( srcs = envoy_select_admin_functionality([ "drain_close_integration_test.cc", ]), - shard_count = 2, tags = [ "cpu:3", ], @@ -356,7 +355,7 @@ envoy_cc_test_binary( envoy_sh_test( name = "hotrestart_test", - size = "enormous", + size = "large", srcs = select({ "//bazel:disable_hot_restart_or_admin": [], "//conditions:default": ["hotrestart_test.sh"], @@ -464,7 +463,7 @@ envoy_cc_test( srcs = [ "http2_flood_integration_test.cc", ], - shard_count = 8, + shard_count = 6, tags = [ "cpu:3", ], @@ -492,7 +491,7 @@ envoy_cc_test( srcs = [ "multiplexed_integration_test.cc", ], - shard_count = 16, + shard_count = 8, tags = [ "cpu:3", ], @@ -570,7 +569,6 @@ envoy_cc_test( srcs = [ "buffer_accounting_integration_test.cc", ], - shard_count = 4, tags = [ "cpu:3", ], @@ -699,7 +697,7 @@ envoy_cc_test( srcs = envoy_select_admin_functionality([ "filter_integration_test.cc", ]), - shard_count = 16, + shard_count = 8, tags = [ "cpu:3", ], @@ -770,13 +768,13 @@ envoy_cc_test_library( envoy_cc_test( name = "protocol_integration_test", - size = "large", + size = "enormous", srcs = [ "instantiate_protocol_integration_test.cc", ], # As this test has many H1/H2/v4/v6 tests it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. - shard_count = 48, + shard_count = 8, tags = [ "cpu:3", ], @@ -802,7 +800,6 @@ envoy_cc_test( srcs = [ "multiplexed_upstream_integration_test.cc", ], - shard_count = 4, tags = [ "cpu:3", ], @@ -925,7 +922,7 @@ envoy_cc_test( srcs = ["idle_timeout_integration_test.cc"], # As this test has many pauses for idle timeouts, it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. - shard_count = 8, + shard_count = 4, tags = [ "cpu:3", ], @@ -1255,7 +1252,6 @@ envoy_cc_test( "integration_test.cc", "integration_test.h", ], - shard_count = 2, tags = [ "cpu:3", ], @@ -1289,7 +1285,7 @@ envoy_cc_test( srcs = [ "redirect_integration_test.cc", ], - shard_count = 8, + shard_count = 2, tags = [ "cpu:3", "nofips", @@ -1323,7 +1319,6 @@ envoy_cc_test( "websocket_integration_test.cc", "websocket_integration_test.h", ], - shard_count = 4, tags = [ "cpu:3", ], @@ -1357,8 +1352,6 @@ envoy_cc_test( "//test/config/integration/certs", ], # The symbol table cluster memory tests take a while to run specially under tsan. - # Shard it to avoid test timeout. - shard_count = 2, deps = [ ":integration_lib", "//source/common/memory:stats_lib", @@ -1399,7 +1392,6 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 2, tags = [ "cpu:3", ], @@ -1472,7 +1464,7 @@ envoy_cc_test( name = "overload_integration_test", size = "large", srcs = ["overload_integration_test.cc"], - shard_count = 8, + shard_count = 4, tags = [ "cpu:3", ], @@ -1672,7 +1664,6 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 2, tags = [ "cpu:3", ], @@ -1709,7 +1700,6 @@ envoy_cc_test( srcs = [ "tcp_proxy_many_connections_test.cc", ], - shard_count = 1, tags = [ "cpu:3", ], @@ -1742,7 +1732,7 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 16, + shard_count = 8, tags = [ "cpu:3", ], @@ -1768,7 +1758,6 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 16, deps = [ ":http_integration_lib", ":http_protocol_integration_lib", @@ -2190,7 +2179,6 @@ envoy_cc_test( srcs = [ "local_reply_integration_test.cc", ], - shard_count = 2, tags = [ "cpu:2", ], @@ -2258,14 +2246,14 @@ envoy_cc_test( # selects. envoy_cc_test( name = "quic_protocol_integration_test", - size = "large", + size = "enormous", srcs = select({ "//bazel:disable_http3": [], "//bazel:disable_admin_functionality": [], "//conditions:default": ["quic_protocol_integration_test.cc"], }), data = ["//test/config/integration/certs"], - shard_count = 24, + shard_count = 16, tags = [ "cpu:4", "nofips", @@ -2296,7 +2284,6 @@ envoy_cc_test( "//conditions:default": ["quic_http_integration_test.cc"], }), data = ["//test/config/integration/certs"], - shard_count = 6, # TODO(envoyproxy/windows-dev): Diagnose failure shown only on clang-cl build, see: # https://gist.github.com/wrowe/a152cb1d12c2f751916122aed39d8517 # TODO(envoyproxy/windows-dev): Diagnose timeout, why opt build test under Windows GCP RBE @@ -2480,15 +2467,18 @@ envoy_cc_test( srcs = [ "default_header_validator_integration_test.cc", ], + shard_count = 8, tags = [ "cpu:3", ], deps = [ ":http_protocol_integration_lib", + "//source/common/http:character_set_validation_lib", "//source/common/http:header_map_lib", "//source/extensions/clusters/logical_dns:logical_dns_cluster_lib", "//source/extensions/clusters/strict_dns:strict_dns_cluster_lib", "//source/extensions/filters/http/buffer:config", + "//source/extensions/http/header_validators/envoy_default:character_tables", "//test/test_common:logging_lib", "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", diff --git a/test/integration/admin_html/histograms_test.js b/test/integration/admin_html/histograms_test.js index c9df555e0db7..b0056f59c61d 100644 --- a/test/integration/admin_html/histograms_test.js +++ b/test/integration/admin_html/histograms_test.js @@ -62,11 +62,12 @@ async function testRenderHistogram(iframe) { // associated annotations. buckets[0].dispatchEvent(new Event('mouseenter')); assertEq('visible', getComputedStyle(popup).visibility); - assertEq(4, popup.children.length); + assertEq(5, popup.children.length); assertEq('[200, 210)', popup.children[0].textContent); - assertEq('P0: 200', popup.children[1].textContent); - assertEq('Interval [200, 210): 1', popup.children[2].textContent); - assertEq('P25: 207.5', popup.children[3].textContent); + assertEq('count=1', popup.children[1].textContent); + assertEq('P0: 200', popup.children[2].textContent); + assertEq('Interval [200, 210): 1', popup.children[3].textContent); + assertEq('P25: 207.5', popup.children[4].textContent); // 2 seconds after the mouse leaves, that area, the popup will be made invisible. buckets[0].dispatchEvent(new Event('mouseleave')); @@ -76,15 +77,16 @@ async function testRenderHistogram(iframe) { // Now enter the other bucket. Just check the 1st 2 of 10 buckets. buckets[1].dispatchEvent(new Event('mouseenter')); assertEq('visible', getComputedStyle(popup).visibility); - assertEq(10, popup.children.length); + assertEq(11, popup.children.length); assertEq('[300, 310)', popup.children[0].textContent); - assertEq('Interval [300, 310): 2', popup.children[1].textContent); + assertEq('count=2', popup.children[1].textContent); + assertEq('Interval [300, 310): 2', popup.children[2].textContent); buckets[1].dispatchEvent(new Event('mouseleave')); // Re-enter the first bucket. The popup will immediately move to that with no delay. buckets[0].dispatchEvent(new Event('mouseenter')); assertEq('visible', getComputedStyle(popup).visibility); - assertEq(4, popup.children.length); + assertEq(5, popup.children.length); assertEq('[200, 210)', popup.children[0].textContent); buckets[1].dispatchEvent(new Event('mouseleave')); @@ -150,5 +152,435 @@ async function testManyBuckets(iframe) { assertEq(20, await renderManyBucketsCountingLabels(iframe, 1000)); } +async function testDense(iframe) { + const stats = {"stats":[{"histograms":{ + "details":[ + {"totals":[ + {"lower_bound":0,"count":472398,"width":0}, + {"width":0.1,"lower_bound":1,"count":557096}, + {"count":278689,"lower_bound":2,"width":0.1}, + {"count":47606,"lower_bound":3,"width":0.1}, + {"width":0.1,"lower_bound":4,"count":100633}, + {"lower_bound":5,"count":1573048,"width":0.1}, + {"lower_bound":6,"width":0.1,"count":8730442}, + {"width":0.1,"count":12997002,"lower_bound":7}, + {"count":10621890,"lower_bound":8,"width":0.1}, + {"count":8376248,"width":0.1,"lower_bound":9}, + {"lower_bound":10,"width":1,"count":4849572}, + {"count":2762795,"lower_bound":11,"width":1}, + {"width":1,"lower_bound":12,"count":1950010}, + {"lower_bound":13,"count":1627025,"width":1}, + {"count":1300345,"lower_bound":14,"width":1}, + {"width":1,"lower_bound":15,"count":1004985}, + {"width":1,"lower_bound":16,"count":775198}, + {"count":618252,"lower_bound":17,"width":1}, + {"width":1,"count":511487,"lower_bound":18}, + {"count":444584,"lower_bound":19,"width":1}, + {"count":404819,"lower_bound":20,"width":1}, + {"lower_bound":21,"width":1,"count":374287}, + {"count":338304,"lower_bound":22,"width":1}, + {"lower_bound":23,"width":1,"count":305750}, + {"lower_bound":24,"width":1,"count":299297}, + {"width":1,"count":334025,"lower_bound":25}, + {"width":1,"lower_bound":26,"count":415369}, + {"lower_bound":27,"count":541668,"width":1}, + {"width":1,"count":703182,"lower_bound":28}, + {"lower_bound":29,"width":1,"count":891888}, + {"lower_bound":30,"count":1106515,"width":1}, + {"count":1321004,"width":1,"lower_bound":31}, + {"count":1508785,"lower_bound":32,"width":1}, + {"lower_bound":33,"width":1,"count":1634176}, + {"lower_bound":34,"width":1,"count":1684453}, + {"lower_bound":35,"width":1,"count":1656202}, + {"width":1,"lower_bound":36,"count":1569391}, + {"width":1,"count":1448427,"lower_bound":37}, + {"count":1319784,"lower_bound":38,"width":1}, + {"count":1197179,"width":1,"lower_bound":39}, + {"lower_bound":40,"count":1086568,"width":1}, + {"lower_bound":41,"count":989413,"width":1}, + {"lower_bound":42,"width":1,"count":902388}, + {"lower_bound":43,"width":1,"count":823711}, + {"lower_bound":44,"width":1,"count":756634}, + {"lower_bound":45,"count":698663,"width":1}, + {"count":646086,"lower_bound":46,"width":1}, + {"width":1,"count":599957,"lower_bound":47}, + {"count":558024,"lower_bound":48,"width":1}, + {"count":519213,"width":1,"lower_bound":49}, + {"width":1,"lower_bound":50,"count":485635}, + {"width":1,"lower_bound":51,"count":464436}, + {"count":456691,"width":1,"lower_bound":52}, + {"count":467842,"lower_bound":53,"width":1}, + {"lower_bound":54,"width":1,"count":502266}, + {"count":557909,"width":1,"lower_bound":55}, + {"lower_bound":56,"width":1,"count":626889}, + {"count":707540,"width":1,"lower_bound":57}, + {"lower_bound":58,"width":1,"count":795978}, + {"lower_bound":59,"count":886280,"width":1}, + {"count":975926,"lower_bound":60,"width":1}, + {"count":1064726,"width":1,"lower_bound":61}, + {"lower_bound":62,"width":1,"count":1145957}, + {"lower_bound":63,"width":1,"count":1225469}, + {"count":1301871,"lower_bound":64,"width":1}, + {"lower_bound":65,"width":1,"count":1368685}, + {"width":1,"count":1429951,"lower_bound":66}, + {"width":1,"lower_bound":67,"count":1481437}, + {"width":1,"count":1531808,"lower_bound":68}, + {"width":1,"lower_bound":69,"count":1570730}, + {"width":1,"count":1612173,"lower_bound":70}, + {"width":1,"count":1644396,"lower_bound":71}, + {"width":1,"lower_bound":72,"count":1674847}, + {"count":1696110,"lower_bound":73,"width":1}, + {"count":1711967,"width":1,"lower_bound":74}, + {"count":1717062,"width":1,"lower_bound":75}, + {"count":1714020,"width":1,"lower_bound":76}, + {"width":1,"lower_bound":77,"count":1703362}, + {"lower_bound":78,"width":1,"count":1688992}, + {"count":1662752,"lower_bound":79,"width":1}, + {"count":1628202,"width":1,"lower_bound":80}, + {"count":1586145,"width":1,"lower_bound":81}, + {"lower_bound":82,"count":1539446,"width":1}, + {"width":1,"lower_bound":83,"count":1487187}, + {"lower_bound":84,"count":1436273,"width":1}, + {"width":1,"lower_bound":85,"count":1385643}, + {"lower_bound":86,"width":1,"count":1335497}, + {"count":1282595,"lower_bound":87,"width":1}, + {"width":1,"count":1235372,"lower_bound":88}, + {"width":1,"count":1189702,"lower_bound":89}, + {"lower_bound":90,"count":1143775,"width":1}, + {"count":1102952,"lower_bound":91,"width":1}, + {"count":1062247,"lower_bound":92,"width":1}, + {"count":1024297,"lower_bound":93,"width":1}, + {"lower_bound":94,"width":1,"count":992991}, + {"lower_bound":95,"width":1,"count":962374}, + {"count":936389,"width":1,"lower_bound":96}, + {"lower_bound":97,"count":913206,"width":1}, + {"count":893620,"lower_bound":98,"width":1}, + {"lower_bound":99,"width":1,"count":873168}, + {"lower_bound":100,"width":10,"count":7445829}, + {"count":5120274,"lower_bound":110.00000000000001,"width":10}, + {"lower_bound":120,"count":3689227,"width":10}, + {"width":10,"count":2942282,"lower_bound":130}, + {"lower_bound":140,"count":2459659,"width":10}, + {"width":10,"count":2063279,"lower_bound":150}, + {"width":10,"count":1863891,"lower_bound":160}, + {"width":10,"lower_bound":170,"count":1700979}, + {"count":1511092,"width":10,"lower_bound":180}, + {"lower_bound":190,"width":10,"count":1316476}, + {"count":1125849,"lower_bound":200,"width":10}, + {"width":10,"count":947157,"lower_bound":210}, + {"count":813968,"lower_bound":220.00000000000003,"width":10}, + {"lower_bound":229.99999999999997,"count":720115,"width":10}, + {"lower_bound":240,"count":637523,"width":10}, + {"width":10,"lower_bound":250,"count":564598}, + {"lower_bound":260,"count":494081,"width":10}, + {"width":10,"count":431764,"lower_bound":270}, + {"count":375778,"width":10,"lower_bound":280}, + {"count":325478,"width":10,"lower_bound":290}, + {"width":10,"lower_bound":300,"count":283433}, + {"count":245364,"lower_bound":310,"width":10}, + {"width":10,"count":215046,"lower_bound":320}, + {"lower_bound":330,"count":189997,"width":10}, + {"count":169817,"width":10,"lower_bound":340}, + {"lower_bound":350,"width":10,"count":152299}, + {"width":10,"count":135490,"lower_bound":360}, + {"count":121187,"width":10,"lower_bound":370}, + {"count":108488,"lower_bound":380,"width":10}, + {"lower_bound":390,"width":10,"count":97233}, + {"count":86670,"width":10,"lower_bound":400}, + {"width":10,"count":76717,"lower_bound":409.99999999999994}, + {"lower_bound":420,"width":10,"count":68278}, + {"count":60168,"lower_bound":430,"width":10}, + {"lower_bound":440.00000000000006,"width":10,"count":54734}, + {"width":10,"lower_bound":450,"count":49024}, + {"width":10,"lower_bound":459.99999999999994,"count":43996}, + {"width":10,"count":39318,"lower_bound":470}, + {"width":10,"lower_bound":480,"count":35407}, + {"width":10,"lower_bound":490.00000000000006,"count":32452}, + {"width":10,"lower_bound":500,"count":29409}, + {"width":10,"lower_bound":509.99999999999994,"count":26340}, + {"width":10,"lower_bound":520,"count":23798}, + {"width":10,"lower_bound":530,"count":21378}, + {"width":10,"count":19753,"lower_bound":540}, + {"count":17707,"width":10,"lower_bound":550}, + {"count":16130,"width":10,"lower_bound":560}, + {"lower_bound":570,"width":10,"count":14384}, + {"width":10,"lower_bound":580,"count":13241}, + {"width":10,"lower_bound":590,"count":11986}, + {"count":11013,"width":10,"lower_bound":600}, + {"width":10,"count":10058,"lower_bound":610}, + {"count":9116,"lower_bound":620,"width":10}, + {"width":10,"count":8389,"lower_bound":630}, + {"width":10,"count":7663,"lower_bound":640}, + {"lower_bound":650,"width":10,"count":7016}, + {"lower_bound":660,"count":6395,"width":10}, + {"lower_bound":670,"count":5871,"width":10}, + {"count":5394,"width":10,"lower_bound":680}, + {"count":4991,"lower_bound":690,"width":10}, + {"lower_bound":700,"width":10,"count":4446}, + {"lower_bound":710,"width":10,"count":4240}, + {"width":10,"lower_bound":720,"count":3740}, + {"count":3514,"lower_bound":730,"width":10}, + {"count":3308,"lower_bound":740,"width":10}, + {"count":3003,"width":10,"lower_bound":750}, + {"count":2715,"lower_bound":760,"width":10}, + {"width":10,"lower_bound":770,"count":2510}, + {"width":10,"lower_bound":780,"count":2354}, + {"lower_bound":790,"width":10,"count":2229}, + {"width":10,"count":1901,"lower_bound":800}, + {"width":10,"lower_bound":810,"count":1799}, + {"width":10,"count":1662,"lower_bound":819.99999999999989}, + {"width":10,"count":1543,"lower_bound":830.00000000000011}, + {"width":10,"lower_bound":840,"count":1503}, + {"width":10,"lower_bound":850,"count":1292}, + {"width":10,"lower_bound":860,"count":1221}, + {"width":10,"count":1205,"lower_bound":869.99999999999989}, + {"lower_bound":880.00000000000011,"width":10,"count":1034}, + {"count":944,"width":10,"lower_bound":890}, + {"lower_bound":900,"count":898,"width":10}, + {"lower_bound":910,"count":800,"width":10}, + {"count":776,"width":10,"lower_bound":919.99999999999989}, + {"width":10,"lower_bound":930.00000000000011,"count":705}, + {"width":10,"lower_bound":940,"count":692}, + {"lower_bound":950,"count":616,"width":10}, + {"lower_bound":960,"count":559,"width":10}, + {"width":10,"lower_bound":969.99999999999989,"count":531}, + {"lower_bound":980.00000000000011,"width":10,"count":505}, + {"lower_bound":990,"count":432,"width":10}, + {"count":3141,"width":100,"lower_bound":1000}, + {"width":100,"count":1510,"lower_bound":1100}, + {"lower_bound":1200,"width":100,"count":829}, + {"lower_bound":1300,"width":100,"count":424}, + {"count":234,"width":100,"lower_bound":1400}, + {"width":100,"lower_bound":1500,"count":137}, + {"count":86,"lower_bound":1600,"width":100}, + {"lower_bound":1700,"count":63,"width":100}, + {"lower_bound":1800,"width":100,"count":41}, + {"lower_bound":1900,"width":100,"count":19}, + {"count":20,"lower_bound":2000,"width":100}, + {"count":18,"lower_bound":2100,"width":100}, + {"lower_bound":2200,"width":100,"count":13}, + {"count":13,"lower_bound":2300,"width":100}, + {"lower_bound":2400,"count":10,"width":100}, + {"count":8,"lower_bound":2500,"width":100}, + {"count":5,"width":100,"lower_bound":2600}, + {"width":100,"lower_bound":2700,"count":4}, + {"width":100,"lower_bound":2900,"count":5}, + {"width":100,"count":6,"lower_bound":3000}, + {"count":2,"width":100,"lower_bound":3100}, + {"width":100,"count":1,"lower_bound":3200}, + {"lower_bound":3300,"count":2,"width":100}, + {"count":2,"width":100,"lower_bound":3400}, + {"count":1,"width":100,"lower_bound":3500}, + {"lower_bound":3600,"width":100,"count":2}, + {"width":100,"count":2,"lower_bound":3800}, + {"count":1,"width":100,"lower_bound":3900}, + {"count":2,"width":100,"lower_bound":4000}, + {"width":100,"lower_bound":4300,"count":1}, + {"count":1,"width":100,"lower_bound":4400}, + {"count":1,"lower_bound":4700,"width":100}, + {"width":100,"lower_bound":5300,"count":1}, + {"width":1000,"lower_bound":12000,"count":1}, + {"width":1000,"lower_bound":15000,"count":1} + ], + "percentiles":[ + {"cumulative":0,"interval":0}, + {"cumulative":10.52802494735618,"interval":11.74527027027027}, + {"cumulative":60.456552033658291,"interval":66.074074074074076}, + {"cumulative":92.45778241783691,"interval":100.59340074507716}, + {"cumulative":145.77243512210438,"interval":159.47899159663874}, + {"cumulative":198.23464765024195,"interval":216.33467741935479}, + {"interval":353.465384615384,"cumulative":329.90518865731076}, + {"interval":409.505000000001,"cumulative":394.83355033784852}, + {"interval":574.57428571428215,"cumulative":563.5139566026005}, + {"cumulative":16000,"interval":1200}], + "intervals":[ + {"lower_bound":0,"count":105,"width":0}, + {"count":134,"width":0.1,"lower_bound":1}, + {"lower_bound":2,"width":0.1,"count":60}, + {"width":0.1,"count":19,"lower_bound":3}, + {"count":13,"width":0.1,"lower_bound":4}, + {"count":227,"width":0.1,"lower_bound":5}, + {"width":0.1,"count":1352,"lower_bound":6}, + {"lower_bound":7,"width":0.1,"count":2304}, + {"lower_bound":8,"width":0.1,"count":1977}, + {"lower_bound":9,"width":0.1,"count":1745}, + {"count":1212,"lower_bound":10,"width":1}, + {"lower_bound":11,"count":740,"width":1}, + {"width":1,"lower_bound":12,"count":538}, + {"count":403,"width":1,"lower_bound":13}, + {"width":1,"lower_bound":14,"count":278}, + {"width":1,"lower_bound":15,"count":232}, + {"width":1,"lower_bound":16,"count":149}, + {"lower_bound":17,"width":1,"count":153}, + {"count":121,"width":1,"lower_bound":18}, + {"count":129,"lower_bound":19,"width":1}, + {"lower_bound":20,"count":88,"width":1}, + {"width":1,"lower_bound":21,"count":91}, + {"count":68,"lower_bound":22,"width":1}, + {"count":71,"lower_bound":23,"width":1}, + {"width":1,"lower_bound":24,"count":68}, + {"width":1,"lower_bound":25,"count":59}, + {"count":70,"width":1,"lower_bound":26}, + {"lower_bound":27,"width":1,"count":93}, + {"count":106,"lower_bound":28,"width":1}, + {"count":144,"lower_bound":29,"width":1}, + {"lower_bound":30,"count":176,"width":1}, + {"lower_bound":31,"count":208,"width":1}, + {"width":1,"lower_bound":32,"count":231}, + {"width":1,"count":311,"lower_bound":33}, + {"lower_bound":34,"count":295,"width":1}, + {"lower_bound":35,"width":1,"count":347}, + {"count":313,"lower_bound":36,"width":1}, + {"lower_bound":37,"count":306,"width":1}, + {"width":1,"lower_bound":38,"count":254}, + {"width":1,"count":268,"lower_bound":39}, + {"count":265,"lower_bound":40,"width":1}, + {"lower_bound":41,"width":1,"count":221}, + {"width":1,"count":208,"lower_bound":42}, + {"lower_bound":43,"width":1,"count":182}, + {"lower_bound":44,"width":1,"count":180}, + {"width":1,"lower_bound":45,"count":185}, + {"width":1,"count":157,"lower_bound":46}, + {"width":1,"count":133,"lower_bound":47}, + {"width":1,"count":159,"lower_bound":48}, + {"width":1,"count":139,"lower_bound":49}, + {"lower_bound":50,"width":1,"count":121}, + {"count":99,"lower_bound":51,"width":1}, + {"lower_bound":52,"count":97,"width":1}, + {"width":1,"count":87,"lower_bound":53}, + {"lower_bound":54,"count":93,"width":1}, + {"width":1,"lower_bound":55,"count":92}, + {"count":98,"width":1,"lower_bound":56}, + {"lower_bound":57,"count":119,"width":1}, + {"width":1,"lower_bound":58,"count":128}, + {"lower_bound":59,"width":1,"count":118}, + {"lower_bound":60,"count":157,"width":1}, + {"lower_bound":61,"count":153,"width":1}, + {"lower_bound":62,"count":157,"width":1}, + {"width":1,"lower_bound":63,"count":171}, + {"count":193,"width":1,"lower_bound":64}, + {"width":1,"lower_bound":65,"count":213}, + {"lower_bound":66,"count":216,"width":1}, + {"count":253,"width":1,"lower_bound":67}, + {"width":1,"count":250,"lower_bound":68}, + {"count":241,"lower_bound":69,"width":1}, + {"lower_bound":70,"count":262,"width":1}, + {"width":1,"lower_bound":71,"count":280}, + {"count":305,"width":1,"lower_bound":72}, + {"count":297,"width":1,"lower_bound":73}, + {"width":1,"count":292,"lower_bound":74}, + {"count":331,"lower_bound":75,"width":1}, + {"count":330,"lower_bound":76,"width":1}, + {"lower_bound":77,"width":1,"count":316}, + {"width":1,"count":318,"lower_bound":78}, + {"lower_bound":79,"count":330,"width":1}, + {"lower_bound":80,"width":1,"count":300}, + {"lower_bound":81,"width":1,"count":329}, + {"width":1,"lower_bound":82,"count":331}, + {"width":1,"lower_bound":83,"count":331}, + {"lower_bound":84,"count":312,"width":1}, + {"width":1,"count":350,"lower_bound":85}, + {"count":308,"width":1,"lower_bound":86}, + {"count":287,"lower_bound":87,"width":1}, + {"lower_bound":88,"count":283,"width":1}, + {"lower_bound":89,"width":1,"count":275}, + {"width":1,"count":286,"lower_bound":90}, + {"width":1,"lower_bound":91,"count":296}, + {"lower_bound":92,"width":1,"count":280}, + {"lower_bound":93,"width":1,"count":235}, + {"count":259,"lower_bound":94,"width":1}, + {"width":1,"lower_bound":95,"count":216}, + {"lower_bound":96,"count":253,"width":1}, + {"count":213,"lower_bound":97,"width":1}, + {"count":232,"width":1,"lower_bound":98}, + {"lower_bound":99,"count":207,"width":1}, + {"count":1879,"width":10,"lower_bound":100}, + {"count":1400,"width":10,"lower_bound":110.00000000000001}, + {"width":10,"count":961,"lower_bound":120}, + {"lower_bound":130,"count":667,"width":10}, + {"width":10,"lower_bound":140,"count":573}, + {"width":10,"lower_bound":150,"count":476}, + {"lower_bound":160,"count":432,"width":10}, + {"count":377,"width":10,"lower_bound":170}, + {"count":349,"lower_bound":180,"width":10}, + {"lower_bound":190,"count":303,"width":10}, + {"lower_bound":200,"width":10,"count":297}, + {"count":248,"width":10,"lower_bound":210}, + {"width":10,"count":201,"lower_bound":220.00000000000003}, + {"width":10,"lower_bound":229.99999999999997,"count":189}, + {"count":174,"width":10,"lower_bound":240}, + {"lower_bound":250,"count":157,"width":10}, + {"width":10,"count":132,"lower_bound":260}, + {"width":10,"lower_bound":270,"count":112}, + {"lower_bound":280,"width":10,"count":97}, + {"lower_bound":290,"width":10,"count":80}, + {"width":10,"count":71,"lower_bound":300}, + {"lower_bound":310,"width":10,"count":56}, + {"lower_bound":320,"count":68,"width":10}, + {"width":10,"lower_bound":330,"count":55}, + {"lower_bound":340,"width":10,"count":51}, + {"width":10,"count":52,"lower_bound":350}, + {"width":10,"lower_bound":360,"count":47}, + {"width":10,"count":42,"lower_bound":370}, + {"lower_bound":380,"width":10,"count":33}, + {"width":10,"count":19,"lower_bound":390}, + {"lower_bound":400,"count":20,"width":10}, + {"width":10,"count":24,"lower_bound":409.99999999999994}, + {"lower_bound":420,"count":18,"width":10}, + {"width":10,"count":19,"lower_bound":430}, + {"lower_bound":440.00000000000006,"width":10,"count":12}, + {"width":10,"count":14,"lower_bound":450}, + {"lower_bound":459.99999999999994,"count":12,"width":10}, + {"count":12,"lower_bound":470,"width":10}, + {"lower_bound":480,"count":7,"width":10}, + {"lower_bound":490.00000000000006,"count":7,"width":10}, + {"width":10,"count":6,"lower_bound":500}, + {"count":4,"lower_bound":509.99999999999994,"width":10}, + {"width":10,"lower_bound":520,"count":2}, + {"width":10,"lower_bound":530,"count":4}, + {"lower_bound":540,"width":10,"count":4}, + {"width":10,"lower_bound":550,"count":3}, + {"lower_bound":560,"count":3,"width":10}, + {"lower_bound":570,"count":7,"width":10}, + {"lower_bound":580,"count":3,"width":10}, + {"count":1,"lower_bound":590,"width":10}, + {"width":10,"lower_bound":600,"count":1}, + {"width":10,"count":1,"lower_bound":610}, + {"count":2,"lower_bound":620,"width":10}, + {"width":10,"lower_bound":630,"count":4}, + {"width":10,"count":2,"lower_bound":640}, + {"lower_bound":650,"count":1,"width":10}, + {"width":10,"lower_bound":660,"count":1}, + {"width":10,"count":1,"lower_bound":670}, + {"width":10,"count":1,"lower_bound":680}, + {"width":10,"lower_bound":730,"count":2}, + {"width":10,"lower_bound":740,"count":1}, + {"lower_bound":750,"count":1,"width":10}, + {"count":1,"lower_bound":780,"width":10}, + {"count":1,"lower_bound":819.99999999999989,"width":10}, + {"width":10,"lower_bound":830.00000000000011,"count":2}, + {"lower_bound":840,"width":10,"count":1}, + {"lower_bound":880.00000000000011,"width":10,"count":2}, + {"count":1,"lower_bound":910,"width":10}, + {"count":1,"width":10,"lower_bound":930.00000000000011}, + {"width":10,"lower_bound":950,"count":1}, + {"width":10,"lower_bound":990,"count":1}, + {"lower_bound":1000,"count":1,"width":100}, + {"width":100,"count":1,"lower_bound":1100}], + "name":"listener_manager.worker_0.dispatcher.loop_duration_us"}], + "supported_percentiles":[0,25,50,75,90,95,99,99.5,99.9,100]}}]}; + + const idoc = iframe.contentWindow.document; + idoc.body.replaceChildren(); + + renderHistograms(idoc.body, stats); + await asyncTimeout(200); + const labels = idoc.getElementsByClassName('histogram-labels')[0]; + assertEq(18, labels.children.length); + const percentile_labels = idoc.getElementsByClassName('percentile-label'); + assertEq(34, percentile_labels.length); +} + addTest('?file=histograms_test.html', 'renderHistogram', testRenderHistogram); addTest('?file=histograms_test.html', 'manyBuckets', testManyBuckets); +addTest('?file=histograms_test.html', 'testDense', testDense); diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index f9d4e54f2466..438dbc8d4e92 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -57,6 +57,29 @@ TEST_P(AdsIntegrationTest, BasicClusterInitialWarming) { test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); } +// Basic CDS/EDS update that warms and makes active a single cluster. +TEST_P(AdsIntegrationTest, BasicClusterInitialWarmingWithResourceWrapper) { + initialize(); + const auto cds_type_url = Config::getTypeUrl(); + const auto eds_type_url = + Config::getTypeUrl(); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", + {{"test", ProtobufWkt::Any()}}); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); + sendDiscoveryResponse( + eds_type_url, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1", {{"test", ProtobufWkt::Any()}}); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); +} + // Tests that the Envoy xDS client can handle updates to a subset of the subscribed resources from // an xDS server without removing the resources not included in the DiscoveryResponse from the xDS // server. @@ -622,6 +645,78 @@ TEST_P(AdsIntegrationTest, CdsEdsReplacementWarming) { {"cluster_0"}, {}, {})); } +// Validate that an update to a Cluster that doesn't receive updated ClusterLoadAssignment +// uses the previous (cached) cluster load assignment. +TEST_P(AdsIntegrationTest, CdsKeepEdsAfterWarmingFailure) { + // TODO(adisuissa): this test should be kept after the runtime guard is deprecated + // (only the runtime guard should be removed). + config_helper_.addRuntimeOverride("envoy.restart_features.use_eds_cache_for_ads", "true"); + initialize(); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + envoy::config::cluster::v3::Cluster cluster = buildCluster("cluster_0"); + // Set a small EDS subscription expiration. + cluster.mutable_eds_cluster_config() + ->mutable_eds_config() + ->mutable_initial_fetch_timeout() + ->set_nanos(100 * 1000 * 1000); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {cluster}, {cluster}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "", + {"cluster_0"}, {"cluster_0"}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "", {}, {}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().Listener, {buildListener("listener_0", "route_config_0")}, + {buildListener("listener_0", "route_config_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"cluster_0"}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().RouteConfiguration, "", + {"route_config_0"}, {"route_config_0"}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, + {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "1", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().RouteConfiguration, "1", + {"route_config_0"}, {}, {})); + + test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + makeSingleRequest(); + + // Update a cluster's field (connect_timeout) so the cluster in Envoy will be explicitly updated. + cluster.mutable_connect_timeout()->set_seconds(7); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {cluster}, {cluster}, {}, "2"); + // Inconsistent SotW and delta behaviors for warming, see + // https://github.com/envoyproxy/envoy/issues/11477#issuecomment-657855029. + // TODO (dmitri-d) this should be remove when legacy mux implementations have been removed. + if (sotw_or_delta_ == Grpc::SotwOrDelta::Sotw) { + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"cluster_0"}, {}, {})); + } + + // Avoid sending an EDS update, and wait for EDS update timeout (that results in + // a cluster update without resources). + test_server_->waitForCounterGe("cluster.cluster_0.init_fetch_timeout", 1); + if (sotw_or_delta_ == Grpc::SotwOrDelta::Sotw) { + // Expect another EDS request after the previous one wasn't answered and timed out. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"cluster_0"}, {}, {})); + } + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "2", {}, {}, {})); + + // Envoy uses the cached resource. + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.assignment_use_cached")->value()); + // A single message should be successfully sent to the upstream. + makeSingleRequest(); +} + // Validate that the request with duplicate clusters in the initial request during server init is // rejected. TEST_P(AdsIntegrationTest, DuplicateInitialClusters) { diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index d0e6c6572c99..474789478570 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -186,15 +186,18 @@ class BaseIntegrationTest : protected Logger::Loggable { const std::vector& expected_resource_names_removed, bool expect_node = false, const Protobuf::int32 expected_error_code = Grpc::Status::WellKnownGrpcStatus::Ok, const std::string& expected_error_message = ""); + template - void sendDiscoveryResponse(const std::string& type_url, const std::vector& state_of_the_world, - const std::vector& added_or_updated, - const std::vector& removed, const std::string& version) { + void + sendDiscoveryResponse(const std::string& type_url, const std::vector& state_of_the_world, + const std::vector& added_or_updated, + const std::vector& removed, const std::string& version, + const absl::flat_hash_map& metadata = {}) { if (sotw_or_delta_ == Grpc::SotwOrDelta::Sotw || sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedSotw) { - sendSotwDiscoveryResponse(type_url, state_of_the_world, version); + sendSotwDiscoveryResponse(type_url, state_of_the_world, version, nullptr, metadata); } else { - sendDeltaDiscoveryResponse(type_url, added_or_updated, removed, version); + sendDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, metadata); } } @@ -225,15 +228,33 @@ class BaseIntegrationTest : protected Logger::Loggable { template void sendSotwDiscoveryResponse(const std::string& type_url, const std::vector& messages, const std::string& version, FakeStream* stream = nullptr) { + sendSotwDiscoveryResponse(type_url, messages, version, stream, {}); + } + template + void + sendSotwDiscoveryResponse(const std::string& type_url, const std::vector& messages, + const std::string& version, FakeStream* stream, + const absl::flat_hash_map& metadata) { if (stream == nullptr) { stream = xds_stream_.get(); } - envoy::service::discovery::v3::DiscoveryResponse discovery_response; discovery_response.set_version_info(version); discovery_response.set_type_url(type_url); for (const auto& message : messages) { - discovery_response.add_resources()->PackFrom(message); + if (metadata.size() != 0) { + envoy::service::discovery::v3::Resource resource; + resource.mutable_resource()->PackFrom(message); + resource.set_name(intResourceName(message)); + resource.set_version(version); + for (const auto& kvp : metadata) { + auto* map = resource.mutable_metadata()->mutable_typed_filter_metadata(); + (*map)[std::string(kvp.first)] = kvp.second; + } + discovery_response.add_resources()->PackFrom(resource); + } else { + discovery_response.add_resources()->PackFrom(message); + } } static int next_nonce_counter = 0; discovery_response.set_nonce(absl::StrCat("nonce", next_nonce_counter++)); @@ -244,15 +265,34 @@ class BaseIntegrationTest : protected Logger::Loggable { void sendDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, const std::string& version) { - sendDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, xds_stream_, {}); + sendDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, xds_stream_, {}, {}); } + template void sendDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, const std::string& version, FakeStreamPtr& stream, const std::vector& aliases = {}) { - auto response = - createDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, aliases); + sendDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, stream, aliases, {}); + } + + template + void + sendDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, + const std::vector& removed, const std::string& version, + const absl::flat_hash_map& metadata) { + sendDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, xds_stream_, {}, + metadata); + } + + template + void + sendDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, + const std::vector& removed, const std::string& version, + FakeStreamPtr& stream, const std::vector& aliases, + const absl::flat_hash_map& metadata) { + auto response = createDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, + aliases, metadata); stream->sendGrpcMessage(response); } @@ -276,18 +316,21 @@ class BaseIntegrationTest : protected Logger::Loggable { envoy::service::discovery::v3::DeltaDiscoveryResponse createDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, const std::string& version, - const std::vector& aliases) { + const std::vector& aliases, + const absl::flat_hash_map& metadata) { std::vector resources; for (const auto& message : added_or_updated) { envoy::service::discovery::v3::Resource resource; - ProtobufWkt::Any temp_any; - temp_any.PackFrom(message); resource.mutable_resource()->PackFrom(message); resource.set_name(intResourceName(message)); resource.set_version(version); for (const auto& alias : aliases) { resource.add_aliases(alias); } + for (const auto& kvp : metadata) { + auto* map = resource.mutable_metadata()->mutable_typed_filter_metadata(); + (*map)[std::string(kvp.first)] = kvp.second; + } resources.emplace_back(resource); } return createExplicitResourcesDeltaDiscoveryResponse(type_url, resources, removed); diff --git a/test/integration/cds_integration_test.cc b/test/integration/cds_integration_test.cc index 3da6ca2b3c08..1956b21e39c5 100644 --- a/test/integration/cds_integration_test.cc +++ b/test/integration/cds_integration_test.cc @@ -29,7 +29,8 @@ const char ClusterName2[] = "cluster_2"; const int UpstreamIndex1 = 1; const int UpstreamIndex2 = 2; -class CdsIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, public HttpIntegrationTest { +class CdsIntegrationTest : public Grpc::DeltaSotwDeferredClustersIntegrationParamTest, + public HttpIntegrationTest { public: CdsIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP2, ipVersion(), @@ -39,6 +40,11 @@ class CdsIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, public Ht ? "GRPC" : "DELTA_GRPC")), cluster_creator_(&ConfigHelper::buildStaticCluster) { + + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + bootstrap.mutable_cluster_manager()->set_enable_deferred_cluster_creation( + useDeferredCluster()); + }); config_helper_.addRuntimeOverride("envoy.reloadable_features.unified_mux", (sotwOrDelta() == Grpc::SotwOrDelta::UnifiedSotw || sotwOrDelta() == Grpc::SotwOrDelta::UnifiedDelta) @@ -151,8 +157,8 @@ class CdsIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, public Ht cluster_creator_; }; -INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, CdsIntegrationTest, - DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); +INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDeltaDeferredCluster, CdsIntegrationTest, + DELTA_SOTW_GRPC_CLIENT_DEFERRED_CLUSTERS_INTEGRATION_PARAMS); // 1) Envoy starts up with no static clusters (other than the CDS-over-gRPC server). // 2) Envoy is told of a cluster via CDS. @@ -164,9 +170,24 @@ INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, CdsIntegrationTest, TEST_P(CdsIntegrationTest, CdsClusterUpDownUp) { // Calls our initialize(), which includes establishing a listener, route, and cluster. config_helper_.addConfigModifier(configureProxyStatus()); + initialize(); + + if (useDeferredCluster()) { + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + } else { + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 2); + } + testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex1, "/cluster1"); test_server_->waitForCounterGe("cluster_manager.cluster_added", 1); + if (useDeferredCluster()) { + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + } else { + EXPECT_EQ( + test_server_->gauge("thread_local_cluster_manager.worker_0.clusters_inflated")->value(), 2); + } + // Tell Envoy that cluster_1 is gone. EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "55", {}, {}, {})); sendClusterDiscoveryResponse({}, {}, {ClusterName1}, "42"); @@ -174,6 +195,12 @@ TEST_P(CdsIntegrationTest, CdsClusterUpDownUp) { // the DiscoveryResponse that says cluster_1 is gone. test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + if (useDeferredCluster()) { + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + } else { + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + } + // Now that cluster_1 is gone, the listener (with its routing to cluster_1) should 503. BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( lookupPort("http"), "GET", "/cluster1", "", downstream_protocol_, version_, "foo.com"); @@ -192,9 +219,18 @@ TEST_P(CdsIntegrationTest, CdsClusterUpDownUp) { // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse describing cluster_1 that we sent. Again, 2 includes CDS server. test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + if (useDeferredCluster()) { + EXPECT_EQ( + test_server_->gauge("thread_local_cluster_manager.worker_0.clusters_inflated")->value(), 0); + } else { + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 2); + } // Does *not* call our initialize(). testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex1, "/cluster1"); + if (useDeferredCluster()) { + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + } cleanupUpstreamAndDownstream(); } @@ -268,7 +304,7 @@ class DeferredCreationClusterStatsTest : public CdsIntegrationTest { }; INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, DeferredCreationClusterStatsTest, - DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); + DELTA_SOTW_GRPC_CLIENT_DEFERRED_CLUSTERS_INTEGRATION_PARAMS); // Test that DeferredCreationTrafficStats gets created and updated correctly. TEST_P(DeferredCreationClusterStatsTest, diff --git a/test/integration/default_header_validator_integration_test.cc b/test/integration/default_header_validator_integration_test.cc index 87fc50cf764f..8a37c43b5115 100644 --- a/test/integration/default_header_validator_integration_test.cc +++ b/test/integration/default_header_validator_integration_test.cc @@ -1,21 +1,128 @@ +#include "source/common/http/character_set_validation.h" +#include "source/extensions/http/header_validators/envoy_default/character_tables.h" + #include "test/integration/http_protocol_integration.h" namespace Envoy { -using DownstreamUhvIntegrationTest = HttpProtocolIntegrationTest; +class DownstreamUhvIntegrationTest : public HttpProtocolIntegrationTest { +public: + using HttpProtocolIntegrationTest::HttpProtocolIntegrationTest; + + // This method sends requests with the :path header formatted using `path_format` where + // one character is replaced with a value from [0 to 0xFF]. + // If the replacement value is in either `uhv_allowed_characters` or + // `additionally_allowed_characters` sets then the request is expected to succeed and the :path + // sent by Envoy is checked against the value produced by the `expected_path_builder` functor. + // This allows validation of path normalization, fragment stripping, etc. If the replacement value + // is not in either set, then the request is expected to be rejected. + void + validateCharacterSetInUrl(absl::string_view path_format, + const std::array& uhv_allowed_characters, + absl::string_view additionally_allowed_characters, + const std::function& expected_path_builder) { + std::vector upstream_requests; + for (uint32_t ascii = 0x0; ascii <= 0xFF; ++ascii) { + // Skip cases where test client can not produce a request + if ((downstream_protocol_ == Http::CodecType::HTTP3 || + (downstream_protocol_ == Http::CodecType::HTTP2 && + GetParam().http2_implementation == Http2Impl::Oghttp2)) && + ascii == 0) { + // QUIC client does weird things when a header contains nul character + // oghttp2 replaces 0 with , in the URL path + continue; + } else if (downstream_protocol_ == Http::CodecType::HTTP1 && + (ascii == '\r' || ascii == '\n')) { + // \r and \n will produce invalid HTTP/1 request on the wire + continue; + } + auto client = makeHttpConnection(lookupPort("http")); + + std::string path = fmt::format(fmt::runtime(path_format), static_cast(ascii)); + Http::HeaderString invalid_value{}; + invalid_value.setCopyUnvalidatedForTestOnly(path); + Http::TestRequestHeaderMapImpl headers{ + {":scheme", "https"}, {":authority", "envoy.com"}, {":method", "GET"}}; + headers.addViaMove(Http::HeaderString(absl::string_view(":path")), std::move(invalid_value)); + auto response = client->makeHeaderOnlyRequest(headers); + + if (Http::testCharInTable(uhv_allowed_characters, static_cast(ascii)) || + absl::StrContains(additionally_allowed_characters, static_cast(ascii))) { + waitForNextUpstreamRequest(); + std::string expected_path = expected_path_builder(ascii); + EXPECT_EQ(upstream_request_->headers().getPathValue(), expected_path); + // Send a headers only response. + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); + upstream_requests.emplace_back(std::move(upstream_request_)); + } else { + ASSERT_TRUE(client->waitForDisconnect()); + if (downstream_protocol_ == Http::CodecType::HTTP1) { + EXPECT_EQ("400", response->headers().getStatusValue()); + } else { + EXPECT_TRUE(response->reset()); + } + } + client->close(); + } + } + + void enableOghttp2ForFakeUpstream() { + // Enable most permissive codec for fake upstreams, so it can accept unencoded TAB and space + // from the H/3 downstream + envoy::config::core::v3::Http2ProtocolOptions config; + config.mutable_use_oghttp2_codec()->set_value(true); + mergeOptions(config); + } + + std::string generateExtendedAsciiString() { + std::string extended_ascii_string; + for (uint32_t ascii = 0x80; ascii <= 0xff; ++ascii) { + extended_ascii_string.push_back(static_cast(ascii)); + } + return extended_ascii_string; + } + + std::string additionallyAllowedCharactersInUrlPath() { + // All codecs allow the following characters that are outside of RFC "<>[]^`{}\| + std::string additionally_allowed_characters(R"--("<>[]^`{}\|)--"); + if (downstream_protocol_ == Http::CodecType::HTTP3) { + // In addition H/3 allows TAB and SPACE in path + additionally_allowed_characters += +"\t "; + } else if (downstream_protocol_ == Http::CodecType::HTTP2) { + // Both nghttp2 and oghttp2 allow extended ASCII >= 0x80 in path + additionally_allowed_characters += generateExtendedAsciiString(); + if (GetParam().http2_implementation == Http2Impl::Oghttp2) { + // In addition H/2 oghttp2 allows TAB and SPACE in path + additionally_allowed_characters += +"\t "; + } + } + return additionally_allowed_characters; + } + + void setupCharacterValidationRuntimeValues() { + // This allows sending NUL, CR and LF in headers without triggering ASSERTs in Envoy + Http::HeaderStringValidator::disable_validation_for_tests_ = true; + disable_client_header_validation_ = true; + config_helper_.addRuntimeOverride("envoy.reloadable_features.validate_upstream_headers", + "false"); + config_helper_.addRuntimeOverride("envoy.reloadable_features.http_reject_path_with_fragment", + "false"); + } +}; + INSTANTIATE_TEST_SUITE_P(Protocols, DownstreamUhvIntegrationTest, testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( {Http::CodecType::HTTP1, Http::CodecType::HTTP2, Http::CodecType::HTTP3}, - {Http::CodecType::HTTP1})), + {Http::CodecType::HTTP2})), HttpProtocolIntegrationTest::protocolTestParamsToString); -// Without the `uhv_translate_backslash_to_slash` override UHV rejects requests with backslash in -// the path. +// Without the `allow_non_compliant_characters_in_path` override UHV rejects requests with backslash +// in the path. TEST_P(DownstreamUhvIntegrationTest, BackslashInUriPathConversionWithUhvOverride) { + config_helper_.addRuntimeOverride("envoy.uhv.allow_non_compliant_characters_in_path", "false"); disable_client_header_validation_ = true; - config_helper_.addRuntimeOverride("envoy.reloadable_features.uhv_translate_backslash_to_slash", - "false"); config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& hcm) -> void { hcm.mutable_normalize_path()->set_value(true); }); @@ -49,8 +156,8 @@ TEST_P(DownstreamUhvIntegrationTest, BackslashInUriPathConversionWithUhvOverride #endif } -// By default the `uhv_translate_backslash_to_slash` == true and UHV behaves just like legacy path -// normalization. +// By default the `allow_non_compliant_characters_in_path` == true and UHV behaves just like legacy +// path normalization. TEST_P(DownstreamUhvIntegrationTest, BackslashInUriPathConversion) { config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& @@ -125,6 +232,132 @@ TEST_P(DownstreamUhvIntegrationTest, UrlEncodedTripletsCasePreservedWithUhvOverr ASSERT_TRUE(response->waitForEndStream()); } +namespace { +std::map generateExtendedAsciiPercentEncoding() { + std::map encoding; + for (uint32_t ascii = 0x80; ascii <= 0xff; ++ascii) { + encoding.insert( + {static_cast(ascii), fmt::format("%{:02X}", static_cast(ascii))}); + } + return encoding; +} +} // namespace + +// This test shows validation of character sets in URL path for all codecs. +// It also shows that UHV in compatibility mode has the same validation. +TEST_P(DownstreamUhvIntegrationTest, CharacterValidationInPathWithoutPathNormalization) { +#ifdef WIN32 + // H/3 test on Windows is flaky + if (downstream_protocol_ == Http::CodecType::HTTP3) { + return; + } +#endif + setupCharacterValidationRuntimeValues(); + enableOghttp2ForFakeUpstream(); + initialize(); + std::string additionally_allowed_characters = additionallyAllowedCharactersInUrlPath(); + // # and ? will just cause path to be interpreted as having a query or a fragment + // Note that the fragment will be stripped from the URL path + additionally_allowed_characters += "?#"; + + // Fragment will be stripped from path in this test + validateCharacterSetInUrl("/path/with/ad{:c}itional/characters", + Extensions::Http::HeaderValidators::EnvoyDefault::kPathHeaderCharTable, + additionally_allowed_characters, [](uint32_t ascii) -> std::string { + return ascii == '#' + ? "/path/with/ad" + : fmt::format("/path/with/ad{:c}itional/characters", + static_cast(ascii)); + }); +} + +TEST_P(DownstreamUhvIntegrationTest, CharacterValidationInPathWithPathNormalization) { +#ifdef WIN32 + // H/3 test on Windows is flaky + if (downstream_protocol_ == Http::CodecType::HTTP3) { + return; + } +#endif + setupCharacterValidationRuntimeValues(); + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.mutable_normalize_path()->set_value(true); }); + initialize(); + std::string additionally_allowed_characters = additionallyAllowedCharactersInUrlPath(); + // # and ? will just cause path to be interpreted as having a query or a fragment + // Note that the fragment will be stripped from the URL path + additionally_allowed_characters += "?#"; + + std::map encoded_characters{ + {'\t', "%09"}, {' ', "%20"}, {'"', "%22"}, {'<', "%3C"}, {'>', "%3E"}, {'\\', "/"}, + {'^', "%5E"}, {'`', "%60"}, {'{', "%7B"}, {'|', "%7C"}, {'}', "%7D"}}; + std::map percent_encoded_extended_ascii = + generateExtendedAsciiPercentEncoding(); + encoded_characters.merge(percent_encoded_extended_ascii); + + validateCharacterSetInUrl( + "/path/with/ad{:c}itional/characters", Http::kUriQueryAndFragmentCharTable, + additionally_allowed_characters, [&encoded_characters](uint32_t ascii) -> std::string { + if (ascii == '#') { + return "/path/with/ad"; + } + + auto encoding = encoded_characters.find(static_cast(ascii)); + if (encoding != encoded_characters.end()) { + return absl::StrCat("/path/with/ad", encoding->second, "itional/characters"); + } + + return fmt::format("/path/with/ad{:c}itional/characters", static_cast(ascii)); + }); +} + +TEST_P(DownstreamUhvIntegrationTest, CharacterValidationInQuery) { +#ifdef WIN32 + // H/3 test on Windows is flaky + if (downstream_protocol_ == Http::CodecType::HTTP3) { + return; + } +#endif + setupCharacterValidationRuntimeValues(); + // Path normalization should not affect query, however enable it to make sure it is so. + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.mutable_normalize_path()->set_value(true); }); + enableOghttp2ForFakeUpstream(); + initialize(); + std::string additionally_allowed_characters = additionallyAllowedCharactersInUrlPath(); + // Adding fragment separator, since it will just cause the URL to be interpreted as having a + // fragment Note that the fragment will be stripped from the URL path + additionally_allowed_characters += '#'; + + validateCharacterSetInUrl( + "/query?with=a{:c}ditional&characters", Http::kUriQueryAndFragmentCharTable, + additionally_allowed_characters, [](uint32_t ascii) -> std::string { + return ascii == '#' + ? "/query?with=a" + : fmt::format("/query?with=a{:c}ditional&characters", static_cast(ascii)); + }); +} + +TEST_P(DownstreamUhvIntegrationTest, CharacterValidationInFragment) { +#ifdef WIN32 + // H/3 test on Windows is flaky + if (downstream_protocol_ == Http::CodecType::HTTP3) { + return; + } +#endif + setupCharacterValidationRuntimeValues(); + initialize(); + std::string additionally_allowed_characters = additionallyAllowedCharactersInUrlPath(); + // In addition all codecs allow # in fragment + additionally_allowed_characters += '#'; + + // Note that fragment is stripped from the URL path in this test + validateCharacterSetInUrl("/query?with=a#frag{:c}ment", Http::kUriQueryAndFragmentCharTable, + additionally_allowed_characters, + [](uint32_t) -> std::string { return "/query?with=a"; }); +} + // Without the `uhv_allow_malformed_url_encoding` override UHV rejects requests with malformed // percent encoding. TEST_P(DownstreamUhvIntegrationTest, MalformedUrlEncodedTripletsRejectedWithUhvOverride) { @@ -187,4 +420,55 @@ TEST_P(DownstreamUhvIntegrationTest, MalformedUrlEncodedTripletsAllowed) { ASSERT_TRUE(response->waitForEndStream()); } +// Without the `envoy.uhv.reject_percent_00` override UHV rejects requests with the %00 +// sequence. +TEST_P(DownstreamUhvIntegrationTest, RejectPercent00) { + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.mutable_normalize_path()->set_value(true); }); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Start the request. + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/path%00/to/something"}, + {":scheme", "http"}, + {":authority", "host"}}); + + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("400", response->headers().getStatusValue()); +} + +TEST_P(DownstreamUhvIntegrationTest, UhvAllowsPercent00WithOverride) { + config_helper_.addRuntimeOverride("envoy.uhv.reject_percent_00", "false"); + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.mutable_normalize_path()->set_value(true); }); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Start the request. + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/path%00/to/something"}, + {":scheme", "http"}, + {":authority", "host"}}); +#ifdef ENVOY_ENABLE_UHV + waitForNextUpstreamRequest(); + + EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path%00/to/something"); + + // Send a headers only response. + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); +#else + // In legacy mode %00 in URL path always causes request to be rejected + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("400", response->headers().getStatusValue()); +#endif +} + } // namespace Envoy diff --git a/test/integration/eds_integration_test.cc b/test/integration/eds_integration_test.cc index 9b6e142a67f4..b7031214a1d0 100644 --- a/test/integration/eds_integration_test.cc +++ b/test/integration/eds_integration_test.cc @@ -17,14 +17,30 @@ namespace Envoy { namespace { +void validateClusters(const Upstream::ClusterManager::ClusterInfoMap& active_cluster_map, + const std::string& cluster, size_t expected_active_clusters, + size_t hosts_expected, size_t healthy_hosts, size_t degraded_hosts) { + EXPECT_EQ(expected_active_clusters, active_cluster_map.size()); + ASSERT_EQ(1, active_cluster_map.count(cluster)); + const auto& cluster_ref = active_cluster_map.find(cluster)->second; + const auto& hostset_per_priority = cluster_ref.get().prioritySet().hostSetsPerPriority(); + EXPECT_EQ(1, hostset_per_priority.size()); + const Envoy::Upstream::HostSetPtr& host_set = hostset_per_priority[0]; + EXPECT_EQ(hosts_expected, host_set->hosts().size()); + EXPECT_EQ(healthy_hosts, host_set->healthyHosts().size()); + EXPECT_EQ(degraded_hosts, host_set->degradedHosts().size()); +}; + // Integration test for EDS features. EDS is consumed via filesystem // subscription. -class EdsIntegrationTest : public testing::TestWithParam, - public HttpIntegrationTest { +class EdsIntegrationTest + : public testing::TestWithParam>, + public HttpIntegrationTest { public: EdsIntegrationTest() - : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()), - codec_client_type_(envoy::type::v3::HTTP1) {} + : HttpIntegrationTest(Http::CodecType::HTTP1, std::get<0>(GetParam())), + codec_client_type_(envoy::type::v3::HTTP1), + deferred_cluster_creation_(std::get<1>(GetParam())) {} // We need to supply the endpoints via EDS to provide health status. Use a // filesystem delivery to simplify test mechanics. @@ -136,6 +152,8 @@ class EdsIntegrationTest : public testing::TestWithParammutable_path_config_source() ->set_path(cds_helper_.cdsPath()); bootstrap.mutable_static_resources()->clear_clusters(); + bootstrap.mutable_cluster_manager()->set_enable_deferred_cluster_creation( + deferred_cluster_creation_); }); // Set validate_clusters to false to allow us to reference a CDS cluster. @@ -178,13 +196,15 @@ class EdsIntegrationTest : public testing::TestWithParamwaitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/cluster_0", "", downstream_protocol_, version_, "foo.com"); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + cleanupUpstreamAndDownstream(); + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + + const auto& active_cluster_map = + test_server_->server().clusterManager().clusters().active_clusters_; + { + const size_t expected_active_cluster = 1; + const size_t expected_hosts = 2, healthy_hosts = 2, degraded_hosts = 0; + validateClusters(active_cluster_map, "cluster_0", expected_active_cluster, expected_hosts, + healthy_hosts, degraded_hosts); + } +} + +// Test that a deferred EDS cluster that was created inline can get EDS updates +// and receive traffic after the update. +TEST_P(EdsIntegrationTest, DataplaneTrafficAfterEdsUpdateOfInitializedCluster) { + if (!deferred_cluster_creation_) { + GTEST_SKIP() << "Test depends on deferred cluster creation. Skipping."; + } + autonomous_upstream_ = true; + + initializeTest(false); + EndpointSettingOptions options; + options.total_endpoints = 1; + options.healthy_endpoints = 1; + setEndpoints(options); + + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/cluster_0", "", downstream_protocol_, version_, "foo.com"); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + cleanupUpstreamAndDownstream(); + test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + const auto& active_cluster_map = + test_server_->server().clusterManager().clusters().active_clusters_; + { + const size_t expected_active_cluster = 1; + const size_t expected_hosts = 1, healthy_hosts = 1, degraded_hosts = 0; + validateClusters(active_cluster_map, "cluster_0", expected_active_cluster, expected_hosts, + healthy_hosts, degraded_hosts); + } + + options.total_endpoints = 2; + options.healthy_endpoints = 1; + options.degraded_endpoints = 1; + setEndpoints(options); + + response = IntegrationUtil::makeSingleRequest(lookupPort("http"), "GET", "/cluster_0", "", + downstream_protocol_, version_, "foo.com"); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + cleanupUpstreamAndDownstream(); + { + const size_t expected_active_cluster = 1; + const size_t expected_hosts = 2, healthy_hosts = 1, degraded_hosts = 1; + validateClusters(active_cluster_map, "cluster_0", expected_active_cluster, expected_hosts, + healthy_hosts, degraded_hosts); + } +} + } // namespace } // namespace Envoy diff --git a/test/integration/filter_integration_test.cc b/test/integration/filter_integration_test.cc index 3aff48befacf..a700f9e44eb0 100644 --- a/test/integration/filter_integration_test.cc +++ b/test/integration/filter_integration_test.cc @@ -691,7 +691,7 @@ name: passthrough-filter verifyUpStreamRequestAfterStopAllFilter(); } -// Tests StopAllIterationAndWatermark. decode-headers-return-stop-all-watermark-filter sets buffer +// Tests StopAllIterationAndWatermark. decode-headers-return-stop-all-filter sets buffer // limit to 100. Verifies data pause when limit is reached, and resume after iteration continues. TEST_P(FilterIntegrationTest, TestDecodeHeadersReturnsStopAllWatermark) { prependFilter(R"EOF( diff --git a/test/integration/http_subset_lb_integration_test.cc b/test/integration/http_subset_lb_integration_test.cc index 2158305e8b6f..11707c624851 100644 --- a/test/integration/http_subset_lb_integration_test.cc +++ b/test/integration/http_subset_lb_integration_test.cc @@ -185,6 +185,87 @@ class HttpSubsetLbIntegrationTest } } + struct EndpointConfig { + envoy::config::core::v3::HealthStatus health_status; + uint32_t weight; + }; + + // Runs a subset lb test to verify traffic correctly spills over across priorities. Two + // priorities are created: the first three hosts are added to p0, and the fourth host is added to + // p1. The provided endpoint config determines the health and weight of each endpoint. This + // config, along with the provided overprovisioning factor and weighted priority health, can be + // used to test various spillover behavior. + void runSpilloverTest(std::vector endpoint_config, + uint32_t overprovisioning_factor, bool weighted_priority_health, + uint32_t expected_host_count) { + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + ASSERT(endpoint_config.size() == 4); + + auto* static_resources = bootstrap.mutable_static_resources(); + auto* cluster = static_resources->mutable_clusters(0); + + // We specifically don't want to use a load balancing policy that does consistent + // hashing because we want our requests to be load balanced across all hosts. + cluster->set_lb_policy(envoy::config::cluster::v3::Cluster::ROUND_ROBIN); + + cluster->mutable_lb_subset_config()->set_fallback_policy( + envoy::config::cluster::v3::Cluster::LbSubsetConfig::ANY_ENDPOINT); + + cluster->clear_load_assignment(); + + auto* load_assignment = cluster->mutable_load_assignment(); + load_assignment->set_cluster_name(cluster->name()); + auto* policy = load_assignment->mutable_policy(); + policy->mutable_overprovisioning_factor()->set_value(overprovisioning_factor); + policy->set_weighted_priority_health(weighted_priority_health); + for (uint32_t i = 0; i < 2; i++) { + auto* endpoints = load_assignment->add_endpoints(); + endpoints->set_priority(i); + } + + for (uint32_t i = 0; i < num_hosts_; i++) { + uint32_t priority; + if (i < num_hosts_ - 1) { + priority = 0; + } else { + priority = 1; + } + + auto* endpoints = load_assignment->mutable_endpoints(priority); + auto* lb_endpoint = endpoints->add_lb_endpoints(); + lb_endpoint->set_health_status(endpoint_config[i].health_status); + lb_endpoint->mutable_load_balancing_weight()->set_value(endpoint_config[i].weight); + + // ConfigHelper will fill in ports later. + auto* endpoint = lb_endpoint->mutable_endpoint(); + auto* addr = endpoint->mutable_address()->mutable_socket_address(); + addr->set_address(Network::Test::getLoopbackAddressString( + TestEnvironment::getIpVersionsForTest().front())); + addr->set_port_value(0); + } + }); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + std::set hosts; + for (int i = 0; i < 100; i++) { + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + // Send header only request. + auto response = codec_client_->makeHeaderOnlyRequest(type_a_request_headers_); + ASSERT_TRUE(response->waitForEndStream()); + + // Record the upstream address. + hosts.emplace(response->headers() + .get(Envoy::Http::LowerCaseString{host_header_})[0] + ->value() + .getStringView()); + } + + EXPECT_EQ(hosts.size(), expected_host_count); + } + const uint32_t num_hosts_{4}; const bool is_hash_lb_; @@ -250,4 +331,42 @@ TEST_P(HttpSubsetLbIntegrationTest, SubsetLoadBalancerSingleHostPerSubsetNoMetad EXPECT_THAT(response->headers(), Http::HttpStatusIs("503")); } +// Tests that overriding the overprovisioning factor works as expected. +TEST_P(HttpSubsetLbIntegrationTest, SubsetLoadBalancerOverrideOverprovisioningFactor) { + std::vector endpoint_config; + endpoint_config.push_back({envoy::config::core::v3::HEALTHY, 1}); + endpoint_config.push_back({envoy::config::core::v3::UNHEALTHY, 1}); + endpoint_config.push_back({envoy::config::core::v3::UNHEALTHY, 1}); + endpoint_config.push_back({envoy::config::core::v3::HEALTHY, 1}); + + // Even though 2/3 hosts in p0 are unhealthy, the overprovisioning factor value is so + // high that traffic won't spill over into p1. + runSpilloverTest(endpoint_config, 9999, false, 1); +} + +// Tests behavior when weighted priority health is disabled. +TEST_P(HttpSubsetLbIntegrationTest, SubsetLoadBalancerWeightedPriorityHealthDisabled) { + std::vector endpoint_config; + // Since 2/3 hosts in p0 are unhealthy, traffic will spill over into p1. + endpoint_config.push_back({envoy::config::core::v3::HEALTHY, 6}); + endpoint_config.push_back({envoy::config::core::v3::UNHEALTHY, 1}); + endpoint_config.push_back({envoy::config::core::v3::UNHEALTHY, 1}); + endpoint_config.push_back({envoy::config::core::v3::HEALTHY, 1}); + + runSpilloverTest(endpoint_config, 140, false, 2); +} + +// Tests behavior when weighted priority health is enabled. +TEST_P(HttpSubsetLbIntegrationTest, SubsetLoadBalancerWeightedPriorityHealthEnabled) { + std::vector endpoint_config; + // Even though 2/3 hosts in p0 are unhealthy, traffic will not spill over into p1 due + // to the weight of the healthy host in p0. + endpoint_config.push_back({envoy::config::core::v3::HEALTHY, 6}); + endpoint_config.push_back({envoy::config::core::v3::UNHEALTHY, 1}); + endpoint_config.push_back({envoy::config::core::v3::UNHEALTHY, 1}); + endpoint_config.push_back({envoy::config::core::v3::HEALTHY, 1}); + + runSpilloverTest(endpoint_config, 140, true, 1); +} + } // namespace Envoy diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index d6007d0f418a..0d12a2c92d8a 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -2714,4 +2714,30 @@ TEST_P(IntegrationTest, DoNotOverwriteXForwardedPortFromUntrustedHop) { EXPECT_THAT(response->headers(), HttpStatusIs("200")); } +TEST_P(IntegrationTest, TestDuplicatedContentLengthSameValue) { + config_helper_.disableDelayClose(); + initialize(); + + std::string response; + const std::string full_request = "POST / HTTP/1.1\r\n" + "Host: host\r\n" + "content-length: 20\r\n" + "content-length: 20\r\n\r\n"; + sendRawHttpAndWaitForResponse(lookupPort("http"), full_request.c_str(), &response, false); + EXPECT_THAT(response, StartsWith("HTTP/1.1 400 Bad Request\r\n")); +} + +TEST_P(IntegrationTest, TestDuplicatedContentLengthDifferentValue) { + config_helper_.disableDelayClose(); + initialize(); + + std::string response; + const std::string full_request = "POST / HTTP/1.1\r\n" + "Host: host\r\n" + "content-length: 20\r\n" + "content-length: 53\r\n\r\n"; + sendRawHttpAndWaitForResponse(lookupPort("http"), full_request.c_str(), &response, false); + EXPECT_THAT(response, StartsWith("HTTP/1.1 400 Bad Request\r\n")); +} + } // namespace Envoy diff --git a/test/integration/network_extension_discovery_integration_test.cc b/test/integration/network_extension_discovery_integration_test.cc index d569fe5dc463..c348f55d3efa 100644 --- a/test/integration/network_extension_discovery_integration_test.cc +++ b/test/integration/network_extension_discovery_integration_test.cc @@ -366,6 +366,9 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { tcp_client->waitForDisconnect(); } + // The network_extension_config_missing stats counter increases by 1. + test_server_->waitForCounterGe("listener.listener_stat.network_extension_config_missing", 1); + // Reinstate the configuration. sendXdsResponse(filter_name_, "1", 3); test_server_->waitForCounterGe( @@ -432,6 +435,9 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailWithoutDefault) { if (result) { tcp_client->waitForDisconnect(); } + + // The network_extension_config_missing stats counter increases by 1. + test_server_->waitForCounterGe("listener.listener_stat.network_extension_config_missing", 1); } TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { @@ -600,6 +606,9 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailTerminalFilterNotAtEnd if (result) { tcp_client->waitForDisconnect(); } + + // The network_extension_config_missing stats counter increases by 1. + test_server_->waitForCounterGe("listener.listener_stat.network_extension_config_missing", 1); } // Basic ECDS config dump test with one filter. @@ -763,7 +772,7 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsConfigDumpWithR EXPECT_EQ(4, network_filter_config.bytes_to_drain()); } -TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyExistingConnection) { +TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyToExistingConnection) { on_server_init_function_ = [&]() { waitXdsStream(); }; addFilterChain(); addDynamicFilter(filter_name_, false); @@ -779,6 +788,8 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyExistin "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); // Send 2nd config update to have filter drain 3 bytes of data. sendXdsResponse(filter_name_, "2", 3); @@ -786,8 +797,6 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyExistin "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); ASSERT_TRUE(tcp_client->write(data_)); - FakeRawConnectionPtr fake_upstream_connection; - ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); std::string received_data; // Expect drained bytes to be 5 as the 2nd config update was performed after new connection // establishment. diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index b4dbf27d6961..fa2683d4094a 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -2101,6 +2101,7 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidContentLength) { codec_client_->startRequest(Http::TestRequestHeaderMapImpl{{":method", "POST"}, {":path", "/test/long/url"}, {":authority", "sni.lyft.com"}, + {":scheme", "http"}, {"content-length", "-1"}}); auto response = std::move(encoder_decoder.second); @@ -2154,6 +2155,7 @@ TEST_P(DownstreamProtocolIntegrationTest, MultipleContentLengths) { codec_client_->startRequest(Http::TestRequestHeaderMapImpl{{":method", "POST"}, {":path", "/test/long/url"}, {":authority", "sni.lyft.com"}, + {":scheme", "http"}, {"content-length", "3,2"}}); auto response = std::move(encoder_decoder.second); @@ -2178,6 +2180,7 @@ TEST_P(DownstreamProtocolIntegrationTest, MultipleContentLengthsAllowed) { codec_client_->startRequest(Http::TestRequestHeaderMapImpl{{":method", "POST"}, {":path", "/test/long/url"}, {":authority", "sni.lyft.com"}, + {":scheme", "http"}, {"content-length", "3,2"}}); auto response = std::move(encoder_decoder.second); @@ -2505,7 +2508,7 @@ name: passthrough-filter verifyUpStreamRequestAfterStopAllFilter(); } -// Tests StopAllIterationAndWatermark. decode-headers-return-stop-all-watermark-filter sets buffer +// Tests StopAllIterationAndWatermark. decode-headers-return-stop-all-filter sets buffer // limit to 100. Verifies data pause when limit is reached, and resume after iteration continues. TEST_P(DownstreamProtocolIntegrationTest, TestDecodeHeadersReturnsStopAllWatermark) { config_helper_.prependFilter(R"EOF( diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 5597b44ef10f..11fb588cde26 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -914,10 +914,9 @@ TEST_P(QuicHttpIntegrationTest, ResetRequestWithoutAuthorityHeader) { request_encoder_ = &encoder_decoder.first; auto response = std::move(encoder_decoder.second); - ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->waitForReset()); + ASSERT_FALSE(response->complete()); codec_client_->close(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ("400", response->headers().getStatusValue()); } TEST_P(QuicHttpIntegrationTest, ResetRequestWithInvalidCharacter) { @@ -1280,6 +1279,42 @@ TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithQuicReset) { initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); + // omit required authority header to invoke EnvoyQuicServerStream::onStreamError + auto encoder_decoder = codec_client_->startRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, {":path", "/dynamo/url"}, {":scheme", "http"}}); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + + ASSERT_TRUE(response->waitForReset()); + EXPECT_FALSE(response->complete()); + + std::string log = waitForAccessLog(access_log_name_); + std::vector metrics = absl::StrSplit(log, ','); + ASSERT_EQ(metrics.size(), 21); + EXPECT_EQ(/* PROTOCOL */ metrics.at(0), "HTTP/3"); + EXPECT_EQ(/* ROUNDTRIP_DURATION */ metrics.at(1), "-"); + EXPECT_EQ(/* REQUEST_DURATION */ metrics.at(2), "-"); + EXPECT_EQ(/* RESPONSE_DURATION */ metrics.at(3), "-"); + EXPECT_EQ(/* RESPONSE_CODE */ metrics.at(4), "0"); + EXPECT_EQ(/* BYTES_RECEIVED */ metrics.at(5), "0"); + EXPECT_EQ(/* request headers */ metrics.at(19), metrics.at(20)); +} + +TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithEnvoyReset) { + config_helper_.addRuntimeOverride( + "envoy.reloadable_features.FLAGS_envoy_quic_reloadable_flag_quic_act_upon_invalid_header", + "false"); + + useAccessLog( + "%PROTOCOL%,%ROUNDTRIP_DURATION%,%REQUEST_DURATION%,%RESPONSE_DURATION%,%RESPONSE_" + "CODE%,%BYTES_RECEIVED%,%ROUTE_NAME%,%VIRTUAL_CLUSTER_NAME%,%RESPONSE_CODE_DETAILS%,%" + "CONNECTION_TERMINATION_DETAILS%,%START_TIME%,%UPSTREAM_HOST%,%DURATION%,%BYTES_SENT%,%" + "RESPONSE_FLAGS%,%DOWNSTREAM_LOCAL_ADDRESS%,%UPSTREAM_CLUSTER%,%STREAM_ID%,%DYNAMIC_" + "METADATA(" + "udp.proxy.session:bytes_sent)%,%REQ(:path)%,%STREAM_INFO_REQ(:path)%"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + // omit required authority header to invoke EnvoyQuicServerStream::resetStream auto encoder_decoder = codec_client_->startRequest(Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/dynamo/url"}, {":scheme", "http"}}); diff --git a/test/integration/rtds_integration_test.cc b/test/integration/rtds_integration_test.cc index 1b3c55c7b71d..12fddc232c96 100644 --- a/test/integration/rtds_integration_test.cc +++ b/test/integration/rtds_integration_test.cc @@ -269,6 +269,40 @@ TEST_P(RtdsIntegrationTest, RtdsReload) { EXPECT_EQ(3, test_server_->gauge("runtime.num_layers")->value()); } +// Test Rtds update with Resource wrapper. +TEST_P(RtdsIntegrationTest, RtdsUpdate) { + initialize(); + acceptXdsConnection(); + + EXPECT_EQ("whatevs", getRuntimeKey("foo")); + EXPECT_EQ("yar", getRuntimeKey("bar")); + EXPECT_EQ("", getRuntimeKey("baz")); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Runtime, "", {"some_rtds_layer"}, + {"some_rtds_layer"}, {}, true)); + auto some_rtds_layer = TestUtility::parseYaml(R"EOF( + name: some_rtds_layer + layer: + foo: bar + baz: meh + )EOF"); + + // Use the Resource wrapper no matter if it is Sotw or Delta. + sendDiscoveryResponse( + Config::TypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "1", + {{"test", ProtobufWkt::Any()}}); + test_server_->waitForCounterGe("runtime.load_success", initial_load_success_ + 1); + + EXPECT_EQ("bar", getRuntimeKey("foo")); + EXPECT_EQ("yar", getRuntimeKey("bar")); + EXPECT_EQ("meh", getRuntimeKey("baz")); + + EXPECT_EQ(0, test_server_->counter("runtime.load_error")->value()); + EXPECT_EQ(initial_load_success_ + 1, test_server_->counter("runtime.load_success")->value()); + EXPECT_EQ(initial_keys_ + 1, test_server_->gauge("runtime.num_keys")->value()); + EXPECT_EQ(3, test_server_->gauge("runtime.num_layers")->value()); +} + // Verify that RTDS initialization starts only after initialization of all primary clusters has // completed. Primary cluster initialization completes asynchronously when some of the clusters use // DNS for endpoint discovery or when health check is configured. diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index e70e7796ac73..f25fb856e482 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -432,6 +432,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeHostSizeWithStats) { // 2020/09/10 2000 Reduce flakes // 2020/03/23 3000 Reduce flakes // 2023/07/21 3005 Additional 4 bytes for new health status + // 2023/07/21 3500 Workaround CI flake pending resolution of #28793 // Note: when adjusting this value: EXPECT_MEMORY_EQ is active only in CI // 'release' builds, where we control the platform and tool-chain. So you @@ -448,7 +449,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeHostSizeWithStats) { // https://github.com/envoyproxy/envoy/issues/12209 // EXPECT_MEMORY_EQ(m_per_host, 1380); } - EXPECT_MEMORY_LE(m_per_host, 3005); // Round up to allow platform variations. + EXPECT_MEMORY_LE(m_per_host, 3500); // Round up to allow platform variations. } } // namespace diff --git a/test/integration/vhds.h b/test/integration/vhds.h index 4a97aaed9649..9c8e3652b7e3 100644 --- a/test/integration/vhds.h +++ b/test/integration/vhds.h @@ -253,7 +253,7 @@ class VhdsIntegrationTest : public HttpIntegrationTest, const std::vector& removed, const std::string& version, FakeStreamPtr& stream, const std::vector& aliases, const std::vector& unresolved_aliases) { auto response = createDeltaDiscoveryResponse( - Config::TypeUrl::get().VirtualHost, added_or_updated, removed, version, aliases); + Config::TypeUrl::get().VirtualHost, added_or_updated, removed, version, aliases, {}); for (const auto& unresolved_alias : unresolved_aliases) { auto* resource = response.add_resources(); resource->set_name(unresolved_alias); diff --git a/test/integration/xds_config_tracker_integration_test.cc b/test/integration/xds_config_tracker_integration_test.cc index bbc6fc04920e..1bbd4c9a761b 100644 --- a/test/integration/xds_config_tracker_integration_test.cc +++ b/test/integration/xds_config_tracker_integration_test.cc @@ -20,6 +20,7 @@ namespace Envoy { namespace { +const char kTestKey[] = "test_key"; const char ClusterName1[] = "cluster_1"; const char ClusterName2[] = "cluster_2"; const int UpstreamIndex1 = 1; @@ -30,7 +31,8 @@ const int UpstreamIndex2 = 2; */ #define ALL_TEST_XDS_TRACKER_STATS(COUNTER) \ COUNTER(on_config_accepted) \ - COUNTER(on_config_rejected) + COUNTER(on_config_rejected) \ + COUNTER(on_config_metadata_read) /** * Struct definition for stats. @see stats_macros.h @@ -48,14 +50,45 @@ class TestXdsConfigTracker : public Config::XdsConfigTracker { TestXdsConfigTracker(Stats::Scope& scope) : stats_(generateStats("test_xds_tracker", scope)) {} void onConfigAccepted(const absl::string_view, - const std::vector&) override { + const std::vector& decoded_resources) override { stats_.on_config_accepted_.inc(); + test::envoy::config::xds::TestTrackerMetadata test_metadata; + for (const auto& resource : decoded_resources) { + if (resource->metadata().has_value()) { + const auto& config_typed_metadata = resource->metadata()->typed_filter_metadata(); + if (const auto& metadata_it = config_typed_metadata.find(kTestKey); + metadata_it != config_typed_metadata.end()) { + const auto status = + Envoy::MessageUtil::unpackToNoThrow(metadata_it->second, test_metadata); + if (!status.ok()) { + continue; + } + stats_.on_config_metadata_read_.inc(); + } + } + } } - void onConfigAccepted(const absl::string_view, - const Protobuf::RepeatedPtrField&, - const Protobuf::RepeatedPtrField&) override { + void onConfigAccepted( + const absl::string_view, + const Protobuf::RepeatedPtrField& resources, + const Protobuf::RepeatedPtrField&) override { stats_.on_config_accepted_.inc(); + test::envoy::config::xds::TestTrackerMetadata test_metadata; + for (const auto& resource : resources) { + if (resource.has_metadata()) { + const auto& config_typed_metadata = resource.metadata().typed_filter_metadata(); + if (const auto& metadata_it = config_typed_metadata.find(kTestKey); + metadata_it != config_typed_metadata.end()) { + const auto status = + Envoy::MessageUtil::unpackToNoThrow(metadata_it->second, test_metadata); + if (!status.ok()) { + continue; + } + stats_.on_config_metadata_read_.inc(); + } + } + } } void onConfigRejected(const envoy::service::discovery::v3::DiscoveryResponse&, @@ -178,6 +211,32 @@ TEST_P(XdsConfigTrackerIntegrationTest, XdsConfigTrackerSuccessCount) { // onConfigAccepted is called when all the resources are accepted. test_server_->waitForCounterEq("test_xds_tracker.on_config_accepted", 1); + test_server_->waitForCounterEq("test_xds_tracker.on_config_metadata_read", 0); + EXPECT_EQ(1, test_server_->counter("test_xds_tracker.on_config_accepted")->value()); +} + +// This is to test the Resource wrapper usage with metadata field. +TEST_P(XdsConfigTrackerIntegrationTest, XdsConfigTrackerSuccessCountWithWrapper) { + TestXdsConfigTrackerFactory factory; + Registry::InjectFactory registered(factory); + + initialize(); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + + // Add a typed metadata to the Resource wrapper. + test::envoy::config::xds::TestTrackerMetadata test_metadata; + ProtobufWkt::Any packed_value; + packed_value.PackFrom(test_metadata); + sendDiscoveryResponse( + Config::TypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster1_, cluster2_}, {}, "1", + {{kTestKey, packed_value}}); + + // 3 because the statically specified CDS server itself counts as a cluster. + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + + // onConfigAccepted is called when all the resources are accepted. + test_server_->waitForCounterEq("test_xds_tracker.on_config_accepted", 1); + test_server_->waitForCounterEq("test_xds_tracker.on_config_metadata_read", 2); EXPECT_EQ(1, test_server_->counter("test_xds_tracker.on_config_accepted")->value()); } diff --git a/test/integration/xds_config_tracker_test.proto b/test/integration/xds_config_tracker_test.proto index 7a74cc0fed59..2a03f023505e 100644 --- a/test/integration/xds_config_tracker_test.proto +++ b/test/integration/xds_config_tracker_test.proto @@ -5,3 +5,7 @@ package test.envoy.config.xds; // Configuration for TestXdsConfigTracker. message TestXdsConfigTracker { } + +// Typed metadata for XDS tracker. +message TestTrackerMetadata { +} diff --git a/test/mocks/config/BUILD b/test/mocks/config/BUILD index ea9057e1aed7..82c7ba41ecf0 100644 --- a/test/mocks/config/BUILD +++ b/test/mocks/config/BUILD @@ -35,3 +35,13 @@ envoy_cc_mock( "//envoy/config:config_validator_interface", ], ) + +envoy_cc_mock( + name = "eds_resources_cache_mocks", + srcs = ["eds_resources_cache.cc"], + hdrs = ["eds_resources_cache.h"], + deps = [ + "//envoy/config:eds_resources_cache_interface", + "//source/common/common:logger_lib", + ], +) diff --git a/test/mocks/config/eds_resources_cache.cc b/test/mocks/config/eds_resources_cache.cc new file mode 100644 index 000000000000..1f11831228d5 --- /dev/null +++ b/test/mocks/config/eds_resources_cache.cc @@ -0,0 +1,16 @@ +#include "test/mocks/config/eds_resources_cache.h" + +#include "source/common/common/logger.h" + +namespace Envoy { +namespace Config { +using testing::_; +using testing::Return; + +using testing::Invoke; +MockEdsResourcesCache::MockEdsResourcesCache() { + ON_CALL(*this, getResource(_, _)).WillByDefault(Return(absl::nullopt)); +} + +} // namespace Config +} // namespace Envoy diff --git a/test/mocks/config/eds_resources_cache.h b/test/mocks/config/eds_resources_cache.h new file mode 100644 index 000000000000..72661c0312bd --- /dev/null +++ b/test/mocks/config/eds_resources_cache.h @@ -0,0 +1,31 @@ +#pragma once + +#include "envoy/config/eds_resources_cache.h" + +#include "gmock/gmock.h" + +namespace Envoy { +namespace Config { + +class MockEdsResourcesCache : public EdsResourcesCache { +public: + MockEdsResourcesCache(); + ~MockEdsResourcesCache() override = default; + + MOCK_METHOD(void, setResource, + (absl::string_view resource_name, + const envoy::config::endpoint::v3::ClusterLoadAssignment& resource)); + MOCK_METHOD(void, removeResource, (absl::string_view resource_name)); + MOCK_METHOD(OptRef, getResource, + (absl::string_view resource_name, EdsResourceRemovalCallback* removal_cb)); + MOCK_METHOD(void, removeCallback, + (absl::string_view resource_name, EdsResourceRemovalCallback* removal_cb)); + MOCK_METHOD(uint32_t, cacheSizeForTest, (), (const)); + + MOCK_METHOD(void, setExpiryTimer, + (absl::string_view resource_name, std::chrono::milliseconds ms)); + MOCK_METHOD(void, disableExpiryTimer, (absl::string_view resource_name)); +}; + +} // namespace Config +} // namespace Envoy diff --git a/test/mocks/config/mocks.h b/test/mocks/config/mocks.h index 4147b4046f1d..d6fe23e3c3f9 100644 --- a/test/mocks/config/mocks.h +++ b/test/mocks/config/mocks.h @@ -129,6 +129,8 @@ class MockGrpcMux : public GrpcMux { const absl::flat_hash_set& add_these_names)); MOCK_METHOD(bool, paused, (const std::string& type_url), (const)); + + MOCK_METHOD(EdsResourcesCacheOptRef, edsResourcesCache, ()); }; class MockGrpcStreamCallbacks diff --git a/test/mocks/grpc/mocks.h b/test/mocks/grpc/mocks.h index 3cff1d2d75aa..12bfa15dfa1d 100644 --- a/test/mocks/grpc/mocks.h +++ b/test/mocks/grpc/mocks.h @@ -118,6 +118,7 @@ class MockAsyncClientManager : public AsyncClientManager { bool skip_cluster_check)); }; +#ifdef ENVOY_ENABLE_FULL_PROTOS MATCHER_P(ProtoBufferEq, expected, "") { typename std::remove_const::type proto; if (!proto.ParseFromString(arg->toString())) { @@ -177,6 +178,7 @@ MATCHER_P(ProtoBufferEqIgnoreRepeatedFieldOrdering, expected, "") { } return equal; } +#endif } // namespace Grpc } // namespace Envoy diff --git a/test/mocks/upstream/cluster_manager.h b/test/mocks/upstream/cluster_manager.h index 977e11a45ff4..e2500ee1dda6 100644 --- a/test/mocks/upstream/cluster_manager.h +++ b/test/mocks/upstream/cluster_manager.h @@ -91,6 +91,8 @@ class MockClusterManager : public ClusterManager { common_lb_config); } + MOCK_METHOD(Config::EdsResourcesCacheOptRef, edsResourcesCache, ()); + envoy::config::core::v3::BindConfig& mutableBindConfig(); NiceMock thread_local_cluster_; diff --git a/test/mocks/upstream/cluster_update_callbacks.h b/test/mocks/upstream/cluster_update_callbacks.h index 782c319004c1..2de2fffefb40 100644 --- a/test/mocks/upstream/cluster_update_callbacks.h +++ b/test/mocks/upstream/cluster_update_callbacks.h @@ -14,7 +14,8 @@ class MockClusterUpdateCallbacks : public ClusterUpdateCallbacks { MockClusterUpdateCallbacks(); ~MockClusterUpdateCallbacks() override; - MOCK_METHOD(void, onClusterAddOrUpdate, (ThreadLocalCluster & cluster)); + MOCK_METHOD(void, onClusterAddOrUpdate, + (absl::string_view cluster_name, ThreadLocalClusterCommand& command)); MOCK_METHOD(void, onClusterRemoval, (const std::string& cluster_name)); }; } // namespace Upstream diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index 9a5a751fb527..da2c8b76313c 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -59,12 +59,10 @@ if [[ "${FUZZ_COVERAGE}" == "true" ]]; then while read -r line; do COVERAGE_TARGETS+=("$line"); done \ <<< "$_targets" BAZEL_COVERAGE_OPTIONS+=( - "--config=fuzz-coverage" - "--test_tag_filters=-nocoverage") + "--config=fuzz-coverage") else BAZEL_COVERAGE_OPTIONS+=( - "--config=test-coverage" - "--test_tag_filters=-nocoverage,-fuzz_target") + "--config=test-coverage") fi # Output unusually long logs due to trace logging. @@ -74,6 +72,7 @@ BAZEL_OUTPUT_BASE="$(bazel "${BAZEL_STARTUP_OPTIONS[@]}" info "${BAZEL_BUILD_OPT echo "Running bazel coverage with:" echo " Options: ${BAZEL_BUILD_OPTIONS[*]} ${BAZEL_COVERAGE_OPTIONS[*]}" echo " Targets: ${COVERAGE_TARGETS[*]}" + bazel "${BAZEL_STARTUP_OPTIONS[@]}" coverage "${BAZEL_BUILD_OPTIONS[@]}" "${BAZEL_COVERAGE_OPTIONS[@]}" "${COVERAGE_TARGETS[@]}" echo "Collecting profile and testlogs" @@ -82,6 +81,9 @@ if [[ -n "${ENVOY_BUILD_PROFILE}" ]]; then fi if [[ -n "${ENVOY_BUILD_DIR}" ]]; then + if [[ -e "${ENVOY_BUILD_DIR}/testlogs.tar.zst" ]]; then + rm -f "${ENVOY_BUILD_DIR}/testlogs.tar.zst" + fi find bazel-testlogs/ -name test.log \ | tar cf - -T - \ | bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ @@ -119,6 +121,10 @@ if [[ "${FUZZ_COVERAGE}" == "true" ]]; then - -T0 -o "${ENVOY_FUZZ_COVERAGE_ARTIFACT}" fi elif [[ -n "${ENVOY_COVERAGE_ARTIFACT}" ]]; then + if [[ -e "${ENVOY_COVERAGE_ARTIFACT}" ]]; then + rm "${ENVOY_COVERAGE_ARTIFACT}" + fi + tar cf - -C "${COVERAGE_DIR}" --transform 's/^\./coverage/' . \ | bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/zstd -- \ - -T0 -o "${ENVOY_COVERAGE_ARTIFACT}" diff --git a/test/server/admin/config_dump_handler_test.cc b/test/server/admin/config_dump_handler_test.cc index 962039e3c8e9..8d2dc83b6330 100644 --- a/test/server/admin/config_dump_handler_test.cc +++ b/test/server/admin/config_dump_handler_test.cc @@ -200,44 +200,44 @@ TEST_P(AdminInstanceTest, ConfigDumpWithLocalityEndpoint) { ON_CALL(*cluster.info_, addedViaApi()).WillByDefault(Return(false)); - Upstream::MockHostSet* host_set_1 = cluster.priority_set_.getMockHostSet(0); - auto host_1 = std::make_shared>(); - host_set_1->hosts_.emplace_back(host_1); + const std::string hostname_for_healthcheck = "test_hostname_healthcheck"; + const std::string empty_hostname_for_healthcheck = ""; envoy::config::core::v3::Locality locality_1; - locality_1.set_region("oceania"); - locality_1.set_zone("hello"); - locality_1.set_sub_zone("world"); - const std::string hostname_for_healthcheck = "test_hostname_healthcheck"; - const std::string hostname_1 = "foo.com"; + Upstream::MockHostSet* host_set_1 = cluster.priority_set_.getMockHostSet(0); + auto host_1 = std::make_shared>(); + host_set_1->hosts_.emplace_back(host_1); + const std::string hostname_1 = "coo.com"; - addHostInfo(*host_1, hostname_1, "tcp://1.2.3.4:80", locality_1, hostname_for_healthcheck, - "tcp://1.2.3.5:90", 5, 6); + addHostInfo(*host_1, hostname_1, "tcp://1.2.3.8:8", locality_1, empty_hostname_for_healthcheck, + "tcp://1.2.3.8:8", 3, 4); + const std::string hostname_2 = "foo.com"; auto host_2 = std::make_shared>(); host_set_1->hosts_.emplace_back(host_2); - const std::string empty_hostname_for_healthcheck = ""; - const std::string hostname_2 = "boo.com"; - - addHostInfo(*host_2, hostname_2, "tcp://1.2.3.7:8", locality_1, empty_hostname_for_healthcheck, - "tcp://1.2.3.7:8", 3, 6); envoy::config::core::v3::Locality locality_2; + locality_2.set_region("oceania"); + locality_2.set_zone("hello"); + locality_2.set_sub_zone("world"); + + addHostInfo(*host_2, hostname_2, "tcp://1.2.3.4:80", locality_2, hostname_for_healthcheck, + "tcp://1.2.3.5:90", 5, 6); auto host_3 = std::make_shared>(); host_set_1->hosts_.emplace_back(host_3); - const std::string hostname_3 = "coo.com"; + const std::string hostname_3 = "boo.com"; - addHostInfo(*host_3, hostname_3, "tcp://1.2.3.8:8", locality_2, empty_hostname_for_healthcheck, - "tcp://1.2.3.8:8", 3, 4); + addHostInfo(*host_3, hostname_3, "tcp://1.2.3.7:8", locality_2, empty_hostname_for_healthcheck, + "tcp://1.2.3.7:8", 3, 6); std::vector locality_hosts = { - {Upstream::HostSharedPtr(host_1), Upstream::HostSharedPtr(host_2)}, - {Upstream::HostSharedPtr(host_3)}}; + {Upstream::HostSharedPtr(host_1)}, + {Upstream::HostSharedPtr(host_2), Upstream::HostSharedPtr(host_3)}}; auto hosts_per_locality = new Upstream::HostsPerLocalityImpl(std::move(locality_hosts), false); - Upstream::LocalityWeightsConstSharedPtr locality_weights{new Upstream::LocalityWeights{1, 3}}; + Upstream::LocalityWeightsConstSharedPtr locality_weights{new Upstream::LocalityWeights{3, 1}}; ON_CALL(*host_set_1, hostsPerLocality()).WillByDefault(ReturnRef(*hosts_per_locality)); ON_CALL(*host_set_1, localityWeights()).WillByDefault(Return(locality_weights)); @@ -246,7 +246,7 @@ TEST_P(AdminInstanceTest, ConfigDumpWithLocalityEndpoint) { host_set_2->hosts_.emplace_back(host_4); const std::string hostname_4 = "doo.com"; - addHostInfo(*host_4, hostname_4, "tcp://1.2.3.9:8", locality_1, empty_hostname_for_healthcheck, + addHostInfo(*host_4, hostname_4, "tcp://1.2.3.9:8", locality_2, empty_hostname_for_healthcheck, "tcp://1.2.3.9:8", 3, 2); Buffer::OwnedImpl response; @@ -263,6 +263,28 @@ TEST_P(AdminInstanceTest, ConfigDumpWithLocalityEndpoint) { "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", "cluster_name": "fake_cluster", "endpoints": [ + { + "locality": {}, + "lb_endpoints": [ + { + "endpoint": { + "address": { + "socket_address": { + "address": "1.2.3.8", + "port_value": 8 + } + }, + "health_check_config": {}, + "hostname": "coo.com" + }, + "health_status": "HEALTHY", + "metadata": {}, + "load_balancing_weight": 3 + } + ], + "load_balancing_weight": 3, + "priority": 4 + }, { "locality": { "region": "oceania", @@ -307,28 +329,6 @@ TEST_P(AdminInstanceTest, ConfigDumpWithLocalityEndpoint) { "load_balancing_weight": 1, "priority": 6 }, - { - "locality": {}, - "lb_endpoints": [ - { - "endpoint": { - "address": { - "socket_address": { - "address": "1.2.3.8", - "port_value": 8 - } - }, - "health_check_config": {}, - "hostname": "coo.com" - }, - "health_status": "HEALTHY", - "metadata": {}, - "load_balancing_weight": 3 - } - ], - "load_balancing_weight": 3, - "priority": 4 - }, { "locality": { "region": "oceania", diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index d10324140f32..816f1f97a324 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -157,6 +157,7 @@ envoy_cc_test_library( envoy_cc_fuzz_test( name = "xds_fuzz_test", + size = "large", srcs = ["xds_fuzz_test.cc"], corpus = "xds_corpus", deps = [ diff --git a/test/server/overload_manager_impl_test.cc b/test/server/overload_manager_impl_test.cc index 081f66f6dc44..738a94b26328 100644 --- a/test/server/overload_manager_impl_test.cc +++ b/test/server/overload_manager_impl_test.cc @@ -955,7 +955,7 @@ TEST_F(OverloadManagerLoadShedPointImplTest, ThrowsIfDuplicateTrigger) { "Duplicate trigger resource for LoadShedPoint .*"); } -TEST_F(OverloadManagerLoadShedPointImplTest, ShouldLogIfNonExistentLoadShedPointRequested) { +TEST_F(OverloadManagerLoadShedPointImplTest, ReturnsNullIfNonExistentLoadShedPointRequested) { setDispatcherExpectation(); const std::string config = R"EOF( resource_monitors: @@ -970,10 +970,8 @@ TEST_F(OverloadManagerLoadShedPointImplTest, ShouldLogIfNonExistentLoadShedPoint auto manager{createOverloadManager(config)}; manager->start(); - EXPECT_LOG_CONTAINS("trace", "LoadShedPoint non_existent_point is not found", { - LoadShedPoint* point = manager->getLoadShedPoint("non_existent_point"); - EXPECT_EQ(point, nullptr); - }); + LoadShedPoint* point = manager->getLoadShedPoint("non_existent_point"); + EXPECT_EQ(point, nullptr); } TEST_F(OverloadManagerLoadShedPointImplTest, PointUsesTriggerToDetermineWhetherToLoadShed) { diff --git a/test/server/server_test.cc b/test/server/server_test.cc index c732c88209ab..8ade9b1d649c 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -1541,7 +1541,7 @@ class CallbacksStatsSink : public Stats::Sink, public Upstream::ClusterUpdateCal void onHistogramComplete(const Stats::Histogram&, uint64_t) override {} // Upstream::ClusterUpdateCallbacks - void onClusterAddOrUpdate(Upstream::ThreadLocalCluster&) override {} + void onClusterAddOrUpdate(absl::string_view, Upstream::ThreadLocalClusterCommand&) override {} void onClusterRemoval(const std::string&) override {} private: diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 9bcbdf2dd7f4..304a7ab52f5c 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -369,6 +369,7 @@ class TestUtility { */ static std::string uniqueFilename(absl::string_view prefix = ""); +#ifdef ENVOY_ENABLE_FULL_PROTOS /** * Compare two protos of the same type for equality. * @@ -410,6 +411,7 @@ class TestUtility { lhs.version() == rhs.version() && lhs.hasResource() == rhs.hasResource() && (!lhs.hasResource() || protoEqual(lhs.resource(), rhs.resource())); } +#endif /** * Symmetrically pad a string with '=' out to a desired length. @@ -438,6 +440,7 @@ class TestUtility { static std::vector split(const std::string& source, const std::string& split, bool keep_empty_string = false); +#ifdef ENVOY_ENABLE_FULL_PROTOS /** * Compare two RepeatedPtrFields of the same type for equality. * @@ -502,6 +505,7 @@ class TestUtility { return AssertionSuccess(); } +#endif /** * Returns a "novel" IPv4 loopback address, if available. @@ -1227,6 +1231,7 @@ MATCHER_P(HeaderMapEqualIgnoreOrder, expected, "") { return equal; } +#ifdef ENVOY_ENABLE_FULL_PROTOS MATCHER_P(ProtoEq, expected, "") { const bool equal = TestUtility::protoEqual(arg, expected, /*ignore_repeated_field_ordering=*/false); @@ -1323,6 +1328,8 @@ MATCHER_P(Percent, rhs, "") { return TestUtility::protoEqual(expected, arg, /*ignore_repeated_field_ordering=*/false); } +#endif + #ifdef ENVOY_ENABLE_YAML MATCHER_P(JsonStringEq, expected, "") { const bool equal = TestUtility::jsonStringEqual(arg, expected); diff --git a/tools/base/envoy_python.bzl b/tools/base/envoy_python.bzl index f88150fffc14..33473b375258 100644 --- a/tools/base/envoy_python.bzl +++ b/tools/base/envoy_python.bzl @@ -183,7 +183,7 @@ def envoy_jinja_env( > $@ """ % (template_arg, load_args), outs = [name_env_py], - exec_tools = [name_templates], + tools = [name_templates], ) py_library( diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index c2947d701ad7..5ba64fd2c417 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -383,30 +383,30 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==41.0.2 \ - --hash=sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711 \ - --hash=sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7 \ - --hash=sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd \ - --hash=sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e \ - --hash=sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58 \ - --hash=sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0 \ - --hash=sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d \ - --hash=sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83 \ - --hash=sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831 \ - --hash=sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766 \ - --hash=sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b \ - --hash=sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c \ - --hash=sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182 \ - --hash=sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f \ - --hash=sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa \ - --hash=sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4 \ - --hash=sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a \ - --hash=sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2 \ - --hash=sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76 \ - --hash=sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5 \ - --hash=sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee \ - --hash=sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f \ - --hash=sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14 +cryptography==41.0.3 \ + --hash=sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306 \ + --hash=sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84 \ + --hash=sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47 \ + --hash=sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d \ + --hash=sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116 \ + --hash=sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207 \ + --hash=sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81 \ + --hash=sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087 \ + --hash=sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd \ + --hash=sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507 \ + --hash=sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858 \ + --hash=sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae \ + --hash=sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34 \ + --hash=sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906 \ + --hash=sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd \ + --hash=sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922 \ + --hash=sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7 \ + --hash=sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4 \ + --hash=sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574 \ + --hash=sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1 \ + --hash=sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c \ + --hash=sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e \ + --hash=sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de # via # -r requirements.in # pyjwt @@ -922,53 +922,63 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.2 \ - --hash=sha256:00c983896c2e01c94c0ef72fd7373b2aa06d0c0eed0342c4884559f812a6835b \ - --hash=sha256:02ef014f9a605e84b675060785e37ec9c0d2347a04f1307a9d6840ab8ecd6f55 \ - --hash=sha256:0325fe2d69512187761f7368c8cda1959bcb75fc56b8e7a884e9569112320e57 \ - --hash=sha256:03fb36f187a0c19ff38f6289418863df8b9b7880cdbe279e920bef3a09d8dab1 \ - --hash=sha256:0b9a26f1d1427a9101a1e8910f2e2df1f44d3d18ad5480ba031b15d5c1cb282e \ - --hash=sha256:1272688ea1865f711b01ba479dea2d53e037ea00892fd04196b5875f7021d9d3 \ - --hash=sha256:16fdf5a82df80c544c3c91516ab3882cd1ac4f1f84eefeafa642e05cef5f6699 \ - --hash=sha256:1882a70bb69595b9ec5aac0040a819e94d2833fe54901e2b32f5e734bc259a8b \ - --hash=sha256:1a6cdfcf9c7dd4026b2b01fdff56986251dc0cc1e980c690c79eec3ae07b36e7 \ - --hash=sha256:1aaa46d7d4ae55335f635eadc9be0bd9bcf742e6757209fc6dc697e390010adc \ - --hash=sha256:205925b179550a4ee39b8418dd4c94ad6b777d165d7d22614771c771d44f57bd \ - --hash=sha256:20925d07a97c49c6305bff1635318d9fc1804aa4ccacb5fb0deb8a910e57d97a \ - --hash=sha256:24257c8f641979bf25ecd3e27251b5cc194cdd3a6e96004aac8446f5e63d9664 \ - --hash=sha256:275b5a18fd9ed60b2720543d3ddac170051c43d680e47d04ff5203d2c6d8ebf1 \ - --hash=sha256:2e52c67ed6bb368083aa2078ea3ccbd9721920b93d4b06c43eb4e20c4c860046 \ - --hash=sha256:2ee743e8890b16c87a2f89733f983370672272b61ee77429c0a5899b2c98c1a7 \ - --hash=sha256:3164fc20a585ec30a9aff33ad5de3b20ce85702b2b2a456852c413e3f0d7ab09 \ - --hash=sha256:3245d230370f571c945f69aab823c279a868dc877352817e22e551de155cb06c \ - --hash=sha256:368e9cc91ecb7ac21f2aa475e1901204110cf3e714e98649c2502227d248f947 \ - --hash=sha256:4a39c2529d75373b7167bf84c814ef9b8f3737a339c225ed6c0df40736df8748 \ - --hash=sha256:58e9e70f0dcd6a802c35887f306b555ff7a214840aad7de24901fc8bd9cf5dde \ - --hash=sha256:5a60a1cfcfe310547a1946506dd4f1ed0a7d5bd5b02c8697d9d5dcd8d2e9245e \ - --hash=sha256:6320b28e7bdb58c3a3a5efffe04b9edad3318d82409e84670a9b24e8035a249d \ - --hash=sha256:6a5ca55b0d8f25f18b471e34abaee4b175924b6cd62f59992945b25963443141 \ - --hash=sha256:7323e4ca8322b1ecb87562f1ec2491831c086d9faa9a6c6503f489dadbed37d7 \ - --hash=sha256:7a6ccadf788531595ed4728aa746bc271955448d2460ff0ef8e21eb3f2a281ba \ - --hash=sha256:7d74ae0e101d17c22ef67b741ba356ab896fc0fa64b301c2bf2bb0a4d874b190 \ - --hash=sha256:806704cd58708acc66a064a9a58e3be25cf1c3f9f159e8757bd3f515bfabdfa1 \ - --hash=sha256:8170157288714678ffd64f5de33039e1164a73fd8b6be40a8a273f80093f5c4f \ - --hash=sha256:84ebd6fdf138eb0eb4280045442331ee71c0aab5e16397ba6645f32f911bfb37 \ - --hash=sha256:869b961df5fcedf6c79f4096119b35679b63272362e9b745e668f0391a892d39 \ - --hash=sha256:877872db2c0f41fbe21f852ff642ca842a43bc34895b70f71c9d575df31fffb4 \ - --hash=sha256:8cd4385c59bbc1433cad4a80aca65d2d9039646a9c57f8084897549b55913b17 \ - --hash=sha256:93864dec3e3dd058a2dbe488d11ac0345214a6a12697f53a63e34de7d28d4257 \ - --hash=sha256:992af54265ada1c1579500d6594ed73fe333e726de70d64919cf37f93defdd06 \ - --hash=sha256:a40958f7af7c6d992ee67b2da4098dca8b770fc3b4b3834d540477788bfa76d3 \ - --hash=sha256:a74036aab1a80c361039290cdbc51aa7adc7ea13f56e5ef94e9be536abd227bd \ - --hash=sha256:b7b065942d362aad4818ff599d2f104c35a565c2cbcbab8c09ec49edba91da75 \ - --hash=sha256:b9aea6dcb99fcbc9f6d1dd84fca92322fda261da7fb014514bb4689c7c2097a8 \ - --hash=sha256:c290c4f81e8fd0c1683638802c11610b2f722b540f8e5e858b6914b495cf90c8 \ - --hash=sha256:d7de3dbbe74109ae598692113cec327fd30c5a30ebca819b21dfa4052f7b08ef \ - --hash=sha256:e3e2f087161947dafe8319ea2cfcb9cea4bb9d2172ecc60ac3c9738f72ef2909 \ - --hash=sha256:e46e9c5b404bb9e41d5555762fd410d5466b7eb1ec170ad1b1609cbebe71df21 \ - --hash=sha256:eebfed53bec5674e981ebe8ed2cf00b3f7bcda62d634733ff779c264307ea505 \ - --hash=sha256:f8bc2c40d9bb26efefb10949d261a47ca196772c308babc538dd9f4b73e8d386 \ - --hash=sha256:fc05e060d452145ab3c0b5420769e7356050ea311fc03cb9d79c481982917cca +orjson==3.9.4 \ + --hash=sha256:004f0d307473af210717260dab2ddceab26750ef5d2c6b1f7454c33f7bb69f0c \ + --hash=sha256:04cd7f4a4f4cd2fe43d104eb70e7435c6fcbdde7aa0cde4230e444fbc66924d3 \ + --hash=sha256:0725260a12d7102b6e66f9925a027f55567255d8455f8288b02d5eedc8925c3e \ + --hash=sha256:0a31c2cab0ba86998205c2eba550c178a8b4ee7905cadeb402eed45392edb178 \ + --hash=sha256:0b400cf89c15958cd829c8a4ade8f5dd73588e63d2fb71a00483e7a74e9f92da \ + --hash=sha256:0e7c3b7e29572ef2d845a59853475f40fdabec53b8b7d6effda4bb26119c07f5 \ + --hash=sha256:144a3b8c7cbdd301e1b8cd7dd33e3cbfe7b011df2bebd45b84bacc8cb490302d \ + --hash=sha256:149d1b7630771222f73ecb024ab5dd8e7f41502402b02015494d429bacc4d5c1 \ + --hash=sha256:161cc72dd3ff569fd67da4af3a23c0c837029085300f0cebc287586ae3b559e0 \ + --hash=sha256:1e4b20164809b21966b63e063f894927bc85391e60d0a96fa0bb552090f1319c \ + --hash=sha256:1fb36efdf2a35286fb87cfaa195fc34621389da1c7b28a8eb51a4d212d60e56d \ + --hash=sha256:220ca4125416636a3d6b53a77d50434987a83da243f9080ee4cce7ac6a34bb4a \ + --hash=sha256:224ad19dcdc21bb220d893807f2563e219319a8891ead3c54243b51a4882d767 \ + --hash=sha256:23d3b6f2706cb324661899901e6b1fcaee4f5aac7d7588306df3f43e68173840 \ + --hash=sha256:264637cad35a1755ab90a8ea290076d444deda20753e55a0eb75496a4645f7bc \ + --hash=sha256:2e83ec1ee66d83b558a6d273d8a01b86563daa60bea9bc040e2c1cb8008de61f \ + --hash=sha256:2f57ccb50e9e123709e9f2d7b1a9e09e694e49d1fa5c5585e34b8e3f01929dc3 \ + --hash=sha256:32a9e0f140c7d0d52f79553cabd1a471f6a4f187c59742239939f1139258a053 \ + --hash=sha256:336ec8471102851f0699198031924617b7a77baadea889df3ffda6000bd59f4c \ + --hash=sha256:3932b06abf49135c93816c74139c7937fa54079fce3f44db2d598859894c344a \ + --hash=sha256:3b9f8bf43a5367d5522f80e7d533c98d880868cd0b640b9088c9237306eca6e8 \ + --hash=sha256:3d947366127abef192419257eb7db7fcee0841ced2b49ccceba43b65e9ce5e3f \ + --hash=sha256:4974cc2ebb53196081fef96743c02c8b073242b20a40b65d2aa2365ba8c949df \ + --hash=sha256:4fdb59cfa00e10c82e09d1c32a9ce08a38bd29496ba20a73cd7f498e3a0a5024 \ + --hash=sha256:53b417cc9465dbb42ec9cd7be744a921a0ce583556315d172a246d6e71aa043b \ + --hash=sha256:562cf24f9f11df8099e0e78859ba6729e7caa25c2f3947cb228d9152946c854b \ + --hash=sha256:5e876ef36801b3d4d3a4b0613b6144b0b47f13f3043fd1fcdfafd783c174b538 \ + --hash=sha256:644728d803200d7774164d252a247e2fcb0d19e4ef7a4a19a1a139ae472c551b \ + --hash=sha256:6bae10f4e7a9145b120e37b6456f1d3853a953e5131fe4740a764e46420289f5 \ + --hash=sha256:73d9507a547202f0dd0672e529ce3ca45582d152369c684a9ce75677ce5ae089 \ + --hash=sha256:915da36bc93ef0c659fa50fe7939d4f208804ad252fc4fc8d55adbbb82293c48 \ + --hash=sha256:94d15ee45c2aaed334688e511aa73b4681f7c08a0810884c6b3ae5824dea1222 \ + --hash=sha256:9ab3720fba68cc1c0bad00803d2c5e2c70177da5af12c45e18cc4d14426d56d8 \ + --hash=sha256:9c32dea3b27a97ac88783c1eb61ccb531865bf478a37df3707cbc96ca8f34a04 \ + --hash=sha256:a4c9254d21fc44526a3850355b89afd0d00ed73bdf902a5ab416df14a61eac6b \ + --hash=sha256:a4f12e9ec62679c3f2717d9ec41b497a2c2af0b1361229db0dc86ef078a4c034 \ + --hash=sha256:a533e664a0e3904307d662c5d45775544dc2b38df6e39e213ff6a86ceaa3d53c \ + --hash=sha256:a7d029fc34a516f7eae29b778b30371fcb621134b2acfe4c51c785102aefc6cf \ + --hash=sha256:b39747f8e57728b9d8c26bd1d28e9a31c028717617a5938a179244b9436c0b31 \ + --hash=sha256:b5b5038187b74e2d33e5caee8a7e83ddeb6a21da86837fa2aac95c69aeb366e6 \ + --hash=sha256:b749d06a3d84ac27311cb85fb5e8f965efd1c5f27556ad8fcfd1853c323b4d54 \ + --hash=sha256:b75f0fc7a64a95027c6f0c70f17969299bdf2b6a85e342b29fc23be2788bad6f \ + --hash=sha256:ba21fe581a83555024f3cfc9182a2390a61bc50430364855022c518b8ba285a4 \ + --hash=sha256:bcda6179eb863c295eb5ea832676d33ef12c04d227b4c98267876c8322e5a96e \ + --hash=sha256:c2fb7963c17ab347428412a0689f5c89ea480f5d5f7ba3e46c6c2f14f3159ee4 \ + --hash=sha256:c416c50f63bfcf453b6e28d1df956938486191fd1a15aeb95107e810e6e219c8 \ + --hash=sha256:c4fcd1ac0b7850f85398fd9fdbc7150ac4e82d2ae6754cc6acaf49ca7c30d79a \ + --hash=sha256:c65df12f92e771361dca45765fcac3d97491799ee8ab3c6c5ecf0155a397a313 \ + --hash=sha256:d73c0fd54a52a1a1abfad69d4f1dfb7048cd0b3ef1828ddb4920ef2d3739d8fb \ + --hash=sha256:daeed2502ddf1f2b29ec8da2fe2ea82807a5c4acf869608ce6c476db8171d070 \ + --hash=sha256:e12492ce65cb10f385e70a88badc6046bc720fa7d468db27b7429d85d41beaeb \ + --hash=sha256:edcbccfe852d1d3d56cc8bfc5fa3688c866619328a73cb2394e79b29b4ab24d2 \ + --hash=sha256:ef7119ebc9b76d5e37c330596616c697d1957779c916aec30cefd28df808f796 \ + --hash=sha256:f009c1a02773bdecdd1157036918fef1da47f7193d4ad599c9edb1e1960a0491 \ + --hash=sha256:f0a4cf31bfa94cd235aa50030bef3df529e4eb2893ea6a7771c0fb087e4e53b2 \ + --hash=sha256:fb429c56ea645e084e34976c2ea0efca7661ee961f61e51405f28bc5a9d1fb24 # via # -r requirements.in # envoy-base-utils @@ -1047,9 +1057,9 @@ pyflakes==3.1.0 \ --hash=sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774 \ --hash=sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc # via flake8 -pygithub==1.59.0 \ - --hash=sha256:126bdbae72087d8d038b113aab6b059b4553cb59348e3024bb1a1cae406ace9e \ - --hash=sha256:6e05ff49bac3caa7d1d6177a10c6e55a3e20c85b92424cc198571fd0cf786690 +pygithub==1.59.1 \ + --hash=sha256:3d87a822e6c868142f0c2c4bf16cce4696b5a7a4d142a7bd160e1bdf75bc54a9 \ + --hash=sha256:c44e3a121c15bf9d3a5cc98d94c9a047a5132a9b01d22264627f58ade9ddc217 # via -r requirements.in pygments==2.15.1 \ --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ @@ -1192,9 +1202,9 @@ snowballstemmer==2.2.0 \ --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a # via sphinx -sphinx==7.1.1 \ - --hash=sha256:4e6c5ea477afa0fb90815210fd1312012e1d7542589ab251ac9b53b7c0751bce \ - --hash=sha256:59b8e391f0768a96cd233e8300fe7f0a8dc2f64f83dc2a54336a9a84f428ff4e +sphinx==7.1.2 \ + --hash=sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f \ + --hash=sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe # via # -r requirements.in # envoy-docs-sphinx-runner diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 19b91f94fa89..4c30f15bada9 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -116,6 +116,8 @@ paths: - source/common/network/lc_trie.h - source/common/network/socket_impl.cc - source/common/ssl/tls_certificate_config_impl.cc + - source/common/formatter/http_specific_formatter.cc + - source/common/formatter/stream_info_formatter.cc - source/common/formatter/substitution_formatter.cc - source/common/formatter/substitution_format_string.cc - source/common/stats/tag_extractor_impl.cc @@ -220,6 +222,7 @@ paths: json_string_to_message: include: - source/common/protobuf/utility.cc + - source/common/protobuf/protobuf.h - source/common/protobuf/yaml_utility.cc - test/extensions/bootstrap/wasm/test_data/speed_cpp.cc @@ -241,6 +244,7 @@ paths: - bazel/cc_proto_descriptor_library - ci/prebuilt - source/common/protobuf + - source/extensions/filters/http/grpc_field_extraction - test/extensions/bootstrap/wasm/test_data # Files that are allowed to use try without main thread assertion. @@ -290,6 +294,7 @@ paths: include: - bazel/cc_proto_descriptor_library/file_descriptor_generator.cc - contrib/config/source/kv_store_xds_delegate.cc + - source/common/protobuf/utility.h - source/common/protobuf/utility.cc - source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc - source/extensions/http/cache/file_system_http_cache/cache_file_header_proto_util.cc @@ -310,6 +315,8 @@ paths: - source/common/common/regex.h - source/common/common/regex.cc - source/common/common/utility.cc + - source/common/formatter/http_specific_formatter.cc + - source/common/formatter/stream_info_formatter.cc - source/common/formatter/substitution_formatter.cc - source/common/stats/tag_extractor_impl.h - source/common/stats/tag_extractor_impl.cc diff --git a/tools/dependency/BUILD b/tools/dependency/BUILD index 5d0e7c4da622..2456d70be13f 100644 --- a/tools/dependency/BUILD +++ b/tools/dependency/BUILD @@ -67,7 +67,7 @@ genrule( --download_cves $@ \ --repository_locations=$(location //bazel:all_repository_locations) """, - exec_tools = [ + tools = [ ":cve_download", "//bazel:all_repository_locations", ], diff --git a/tools/proto_format/BUILD b/tools/proto_format/BUILD index 3c9e8025bb5e..5186e9a0dab6 100644 --- a/tools/proto_format/BUILD +++ b/tools/proto_format/BUILD @@ -107,11 +107,11 @@ genrule( --build_file=$(location //tools/type_whisperer:api_build_file) \ --protoprinted=$(location //tools/protoprint:protoprinted) \ """, - exec_tools = [ + tools = [ ":format_api", ":xformed", - "//tools/type_whisperer:api_build_file", "//tools/protoprint:protoprinted", + "//tools/type_whisperer:api_build_file", ], ) diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index dc733676f3d3..d56e1bc7ff77 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -30,6 +30,7 @@ BSON BPF Bdecoded Bencoded +CIO DFP DOM GiB @@ -248,6 +249,7 @@ hls iframe ingressed integrations +iouring jkl lang libsxg @@ -1397,6 +1399,7 @@ versa versioned vhost viewable +viewport vip virtualhost virtualize