diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 6cb7ac6fff03..99f458d07abd 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -45,7 +45,7 @@ variables: ## Variable settings # Caches (tip: append a version suffix while testing caches) - name: cacheKeyVersion - value: v2 + value: v3 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index c0d6f3091f41..f429feb4ff44 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -12,8 +12,7 @@ jobs: displayName: Debs (x64) 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: - vmImage: $(agentUbuntu) + pool: envoy-x64-small steps: - task: DownloadBuildArtifacts@0 inputs: @@ -27,6 +26,7 @@ jobs: ciTarget: verify_distro cacheName: verify_distro publishTestResults: false + tmpfsDockerDisabled: true env: ENVOY_DOCKER_IN_DOCKER: 1 diff --git a/.github/actions/pr_notifier/requirements.txt b/.github/actions/pr_notifier/requirements.txt index cc6a4466bd8b..12832516484f 100644 --- a/.github/actions/pr_notifier/requirements.txt +++ b/.github/actions/pr_notifier/requirements.txt @@ -210,7 +210,9 @@ pynacl==1.4.0 \ python-dateutil==2.8.2 \ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 - # via pygithub + # via + # icalendar + # pygithub pytz==2023.3.post1 \ --hash=sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b \ --hash=sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7 @@ -227,17 +229,17 @@ six==1.16.0 \ # via # pynacl # python-dateutil -slack-sdk==3.22.0 \ - --hash=sha256:6eacce0fa4f8cfb4d84eac0d7d7e1b1926040a2df654ae86b94179bdf2bc4d8c \ - --hash=sha256:f102a4902115dff3b97c3e8883ad4e22d54732221886fc5ef29bfc290f063b4a +slack-sdk==3.23.0 \ + --hash=sha256:2a8513505cced20ceee22b5b49c11d9545caa6234b56bf0ad47133ea5b357d10 \ + --hash=sha256:9d6ebc4ff74e7983e1b27dbdb0f2bb6fc3c2a2451694686eaa2be23bbb085a73 # via -r requirements.in typing-extensions==4.8.0 \ --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef # via pygithub -urllib3==1.26.6 \ - --hash=sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4 \ - --hash=sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f +urllib3==1.26.17 \ + --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ + --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b # via # pygithub # requests diff --git a/.github/workflows/_ci.yml b/.github/workflows/_ci.yml index 057d4d462012..b400d76a6e6e 100644 --- a/.github/workflows/_ci.yml +++ b/.github/workflows/_ci.yml @@ -96,21 +96,26 @@ jobs: with: image_tag: ${{ inputs.cache_build_image }} - # If the run is "trusted" (ie has access to secrets) then it should - # **not** set the ref and should use the code of the calling context. - - if: ${{ inputs.repo_ref && inputs.trusted }} - run: | - echo '`repo_ref` should not be set for trusted CI runs' - exit 1 - - uses: actions/checkout@v4 name: Checkout Envoy repository with: - fetch-depth: ${{ inputs.repo_fetch_depth }} + fetch-depth: ${{ ! inputs.trusted && inputs.repo_fetch_depth || 0 }} # WARNING: This allows untrusted code to run!!! # If this is set, then anything before or after in the job should be regarded as # compromised. ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} + + # If we are in a trusted CI run then the provided commit _must_ be either the latest for + # this branch, or an antecdent. + - run: | + if ! git merge-base --is-ancestor "${{ inputs.repo_ref }}" HEAD; then + echo "Provided Envoy ref (${{ inputs.repo_ref }}) is not an ancestor of current branch" >&2 + exit 1 + fi + git checkout "${{ inputs.repo_ref }}" + if: ${{ inputs.trusted }} + name: Check provided ref + - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index 58a366c9bd85..8975169caf0a 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -93,6 +93,7 @@ jobs: run_pre_with: ${{ matrix.run_pre_with }} env: ${{ matrix.env }} trusted: true + repo_ref: ${{ inputs.repo_ref }} publish_docs: # For normal commits to Envoy main this will trigger an update in the website repo, diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index a9dcf195c5db..a1a40d2b5fd4 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -50,4 +50,4 @@ jobs: run_pre_with: ${{ matrix.run_pre_with }} env: ${{ matrix.env }} trusted: ${{ inputs.trusted }} - repo_ref: ${{ ! inputs.trusted && inputs.repo_ref || '' }} + repo_ref: ${{ inputs.repo_ref }} diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 704a2d78ab55..36c346b880ae 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -54,7 +54,7 @@ jobs: trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} version_dev: ${{ needs.env.outputs.version_dev }} given_ref: ${{ inputs.ref }} - repo_ref: ${{ needs.env.outputs.trusted != 'true' && inputs.ref || '' }} + repo_ref: ${{ inputs.ref }} permissions: contents: write secrets: @@ -69,4 +69,4 @@ jobs: with: trusted: ${{ needs.env.outputs.trusted == 'true' && true || false }} given_ref: ${{ inputs.ref }} - repo_ref: ${{ needs.env.outputs.trusted != 'true' && needs.env.outputs.repo_ref || '' }} + repo_ref: ${{ inputs.ref }} diff --git a/CODEOWNERS b/CODEOWNERS index 752b5608c1a5..42413b217617 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,7 +26,7 @@ extensions/filters/common/original_src @klarose @mattklein123 /*/extensions/filters/http/ext_proc @gbrail @stevenzzzz @tyxia @mattklein123 @htuch @yanavlasov /*/extensions/filters/common/mutation_rules @gbrail @tyxia @mattklein123 @htuch @yanavlasov # jwt_authn http filter extension -/*/extensions/filters/http/jwt_authn @qiwzhang @lizan +/*/extensions/filters/http/jwt_authn @taoxuy @lizan # grpc_field_extraction http filter extension /*/extensions/filters/http/grpc_field_extraction @taoxuy @nareddyt @yanavlasov # grpc_http1_reverse_bridge http filter extension @@ -112,7 +112,7 @@ extensions/filters/common/original_src @klarose @mattklein123 /*/extensions/filters/http/grpc_http1_bridge @jose @mattklein123 /*/extensions/filters/http/fault @rshriram @alyssawilk /*/extensions/filters/common/fault @rshriram @alyssawilk -/*/extensions/filters/http/grpc_json_transcoder @qiwzhang @lizan +/*/extensions/filters/http/grpc_json_transcoder @nareddyt @lizan /*/extensions/filters/http/router @alyssawilk @mattklein123 /*/extensions/filters/common/rbac/matchers @conqerAtapple @ggreenway @alyssawilk /*/extensions/filters/http/grpc_web @fengli79 @lizan diff --git a/SECURITY.md b/SECURITY.md index dceab5e0a447..a472a952f661 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -465,11 +465,11 @@ and security team to ensure they still qualify for inclusion on the list. | Organization | End User | Last Review | |:-------------:|:--------:|:-----------:| -| Aspen Mesh | No | 06/21 | | AWS | No | 06/21 | | Cilium | No | 06/21 | | Cloud Foundry | No | 06/21 | | Datawire | No | 06/21 | +| F5 | No | 06/21 | | Google | No | 06/21 | | IBM | No | 06/21 | | Istio | No | 06/21 | diff --git a/api/bazel/api_build_system.bzl b/api/bazel/api_build_system.bzl index 0266f4d03f2d..8832aec434d2 100644 --- a/api/bazel/api_build_system.bzl +++ b/api/bazel/api_build_system.bzl @@ -11,7 +11,7 @@ load( "EXTERNAL_PROTO_PY_BAZEL_DEP_MAP", ) load( - "@envoy//bazel/cc_proto_descriptor_library:builddefs.bzl", + "//bazel/cc_proto_descriptor_library:builddefs.bzl", "cc_proto_descriptor_library", ) diff --git a/bazel/cc_proto_descriptor_library/BUILD b/api/bazel/cc_proto_descriptor_library/BUILD similarity index 100% rename from bazel/cc_proto_descriptor_library/BUILD rename to api/bazel/cc_proto_descriptor_library/BUILD diff --git a/bazel/cc_proto_descriptor_library/builddefs.bzl b/api/bazel/cc_proto_descriptor_library/builddefs.bzl similarity index 100% rename from bazel/cc_proto_descriptor_library/builddefs.bzl rename to api/bazel/cc_proto_descriptor_library/builddefs.bzl diff --git a/bazel/cc_proto_descriptor_library/create_dynamic_message.cc b/api/bazel/cc_proto_descriptor_library/create_dynamic_message.cc similarity index 100% rename from bazel/cc_proto_descriptor_library/create_dynamic_message.cc rename to api/bazel/cc_proto_descriptor_library/create_dynamic_message.cc diff --git a/bazel/cc_proto_descriptor_library/create_dynamic_message.h b/api/bazel/cc_proto_descriptor_library/create_dynamic_message.h similarity index 100% rename from bazel/cc_proto_descriptor_library/create_dynamic_message.h rename to api/bazel/cc_proto_descriptor_library/create_dynamic_message.h diff --git a/bazel/cc_proto_descriptor_library/file_descriptor_generator.cc b/api/bazel/cc_proto_descriptor_library/file_descriptor_generator.cc similarity index 100% rename from bazel/cc_proto_descriptor_library/file_descriptor_generator.cc rename to api/bazel/cc_proto_descriptor_library/file_descriptor_generator.cc diff --git a/bazel/cc_proto_descriptor_library/file_descriptor_generator.h b/api/bazel/cc_proto_descriptor_library/file_descriptor_generator.h similarity index 100% rename from bazel/cc_proto_descriptor_library/file_descriptor_generator.h rename to api/bazel/cc_proto_descriptor_library/file_descriptor_generator.h diff --git a/bazel/cc_proto_descriptor_library/file_descriptor_generator_main.cc b/api/bazel/cc_proto_descriptor_library/file_descriptor_generator_main.cc similarity index 100% rename from bazel/cc_proto_descriptor_library/file_descriptor_generator_main.cc rename to api/bazel/cc_proto_descriptor_library/file_descriptor_generator_main.cc diff --git a/bazel/cc_proto_descriptor_library/file_descriptor_info.cc b/api/bazel/cc_proto_descriptor_library/file_descriptor_info.cc similarity index 100% rename from bazel/cc_proto_descriptor_library/file_descriptor_info.cc rename to api/bazel/cc_proto_descriptor_library/file_descriptor_info.cc diff --git a/bazel/cc_proto_descriptor_library/file_descriptor_info.h b/api/bazel/cc_proto_descriptor_library/file_descriptor_info.h similarity index 100% rename from bazel/cc_proto_descriptor_library/file_descriptor_info.h rename to api/bazel/cc_proto_descriptor_library/file_descriptor_info.h diff --git a/bazel/cc_proto_descriptor_library/testdata/BUILD b/api/bazel/cc_proto_descriptor_library/testdata/BUILD similarity index 100% rename from bazel/cc_proto_descriptor_library/testdata/BUILD rename to api/bazel/cc_proto_descriptor_library/testdata/BUILD diff --git a/bazel/cc_proto_descriptor_library/testdata/create_dynamic_message_test.cc b/api/bazel/cc_proto_descriptor_library/testdata/create_dynamic_message_test.cc similarity index 100% rename from bazel/cc_proto_descriptor_library/testdata/create_dynamic_message_test.cc rename to api/bazel/cc_proto_descriptor_library/testdata/create_dynamic_message_test.cc diff --git a/bazel/cc_proto_descriptor_library/testdata/global_fallback_test.cc b/api/bazel/cc_proto_descriptor_library/testdata/global_fallback_test.cc similarity index 100% rename from bazel/cc_proto_descriptor_library/testdata/global_fallback_test.cc rename to api/bazel/cc_proto_descriptor_library/testdata/global_fallback_test.cc diff --git a/bazel/cc_proto_descriptor_library/testdata/test-extension.proto b/api/bazel/cc_proto_descriptor_library/testdata/test-extension.proto similarity index 100% rename from bazel/cc_proto_descriptor_library/testdata/test-extension.proto rename to api/bazel/cc_proto_descriptor_library/testdata/test-extension.proto diff --git a/bazel/cc_proto_descriptor_library/testdata/test.proto b/api/bazel/cc_proto_descriptor_library/testdata/test.proto similarity index 100% rename from bazel/cc_proto_descriptor_library/testdata/test.proto rename to api/bazel/cc_proto_descriptor_library/testdata/test.proto diff --git a/bazel/cc_proto_descriptor_library/testdata/test1.proto b/api/bazel/cc_proto_descriptor_library/testdata/test1.proto similarity index 66% rename from bazel/cc_proto_descriptor_library/testdata/test1.proto rename to api/bazel/cc_proto_descriptor_library/testdata/test1.proto index d8fcdd5155a9..bb0ad106c317 100644 --- a/bazel/cc_proto_descriptor_library/testdata/test1.proto +++ b/api/bazel/cc_proto_descriptor_library/testdata/test1.proto @@ -2,8 +2,6 @@ syntax = "proto2"; package testdata.dynamic_descriptors; -import "bazel/cc_proto_descriptor_library/testdata/test.proto"; - message FooCopy { optional string bar = 1; diff --git a/bazel/cc_proto_descriptor_library/testdata/text_format_transcoder_test.cc b/api/bazel/cc_proto_descriptor_library/testdata/text_format_transcoder_test.cc similarity index 100% rename from bazel/cc_proto_descriptor_library/testdata/text_format_transcoder_test.cc rename to api/bazel/cc_proto_descriptor_library/testdata/text_format_transcoder_test.cc diff --git a/bazel/cc_proto_descriptor_library/text_format_transcoder.cc b/api/bazel/cc_proto_descriptor_library/text_format_transcoder.cc similarity index 98% rename from bazel/cc_proto_descriptor_library/text_format_transcoder.cc rename to api/bazel/cc_proto_descriptor_library/text_format_transcoder.cc index 059ab94935c2..70b116d33b14 100644 --- a/bazel/cc_proto_descriptor_library/text_format_transcoder.cc +++ b/api/bazel/cc_proto_descriptor_library/text_format_transcoder.cc @@ -93,7 +93,7 @@ std::unique_ptr TextFormatTranscoder::createEmptyDyna absl::string_view type_name, google::protobuf::io::ErrorCollector* error_collector) const { const google::protobuf::Descriptor* descriptor = internals_->descriptor_pool.FindMessageTypeByName(std::string(type_name)); - // If you're built with the full runtime then embeddng the descriptors and + // If you're built with the full runtime then embedding the descriptors and // loading them would be information duplicated by the global descriptor // pool which hurts builds like superroot that are near all the blaze/forge // size limits. Teams that care about not silently falling into this fallback diff --git a/bazel/cc_proto_descriptor_library/text_format_transcoder.h b/api/bazel/cc_proto_descriptor_library/text_format_transcoder.h similarity index 100% rename from bazel/cc_proto_descriptor_library/text_format_transcoder.h rename to api/bazel/cc_proto_descriptor_library/text_format_transcoder.h diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 3577730c1e6d..d128dc6d93d7 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -85,7 +85,7 @@ message QuicProtocolOptions { [(validate.rules).uint32 = {lte: 25165824 gte: 1}]; // The number of timeouts that can occur before port migration is triggered for QUIC clients. - // This defaults to 1. If set to 0, port migration will not occur on path degrading. + // This defaults to 4. If set to 0, port migration will not occur on path degrading. // Timeout here refers to QUIC internal path degrading timeout mechanism, such as PTO. // This has no effect on server sessions. google.protobuf.UInt32Value num_timeouts_to_trigger_port_migration = 4 diff --git a/api/envoy/extensions/filters/http/oauth2/v3/oauth.proto b/api/envoy/extensions/filters/http/oauth2/v3/oauth.proto index 7c933d8726fa..8e0574afe4b1 100644 --- a/api/envoy/extensions/filters/http/oauth2/v3/oauth.proto +++ b/api/envoy/extensions/filters/http/oauth2/v3/oauth.proto @@ -7,6 +7,8 @@ import "envoy/config/route/v3/route_components.proto"; import "envoy/extensions/transport_sockets/tls/v3/secret.proto"; import "envoy/type/matcher/v3/path.proto"; +import "google/protobuf/wrappers.proto"; + import "udpa/annotations/status.proto"; import "validate/validate.proto"; @@ -71,7 +73,7 @@ message OAuth2Credentials { // OAuth config // -// [#next-free-field: 12] +// [#next-free-field: 13] message OAuth2Config { enum AuthType { // The ``client_id`` and ``client_secret`` will be sent in the URL encoded request body. @@ -123,6 +125,11 @@ message OAuth2Config { // Defines how ``client_id`` and ``client_secret`` are sent in OAuth client to OAuth server requests. // RFC https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 AuthType auth_type = 11 [(validate.rules).enum = {defined_only: true}]; + + // If set to true, allows automatic access token refresh using the associated refresh token (see + // `RFC 6749 section 6 `_), provided that the OAuth server supports that. + // Default value is false. + google.protobuf.BoolValue use_refresh_token = 12; } // Filter config. diff --git a/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto b/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto index 9d14b582cc69..d22a35c69fad 100644 --- a/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto +++ b/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto @@ -3,10 +3,12 @@ syntax = "proto3"; package envoy.extensions.filters.udp.udp_proxy.v3; import "envoy/config/accesslog/v3/accesslog.proto"; +import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/udp_socket_config.proto"; import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; import "xds/annotations/v3/status.proto"; import "xds/type/matcher/v3/matcher.proto"; @@ -27,7 +29,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.filters.udp_listener.udp_proxy] // Configuration for the UDP proxy filter. -// [#next-free-field: 12] +// [#next-free-field: 13] message UdpProxyConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.udp.udp_proxy.v2alpha.UdpProxyConfig"; @@ -62,6 +64,104 @@ message UdpProxyConfig { } } + // Configuration for tunneling UDP over other transports or application layers. + // Tunneling is currently supported over HTTP/2. + // [#next-free-field: 10] + message UdpTunnelingConfig { + // Configuration for UDP datagrams buffering. + message BufferOptions { + // If set, the filter will only buffer datagrams up to the requested limit, and will drop + // new UDP datagrams if the buffer contains the max_buffered_datagrams value at the time + // of a new datagram arrival. If not set, the default value is 1024 datagrams. + google.protobuf.UInt32Value max_buffered_datagrams = 1; + + // If set, the filter will only buffer datagrams up to the requested total buffered bytes limit, + // and will drop new UDP datagrams if the buffer contains the max_buffered_datagrams value + // at the time of a new datagram arrival. If not set, the default value is 16,384 (16KB). + google.protobuf.UInt64Value max_buffered_bytes = 2; + } + + message RetryOptions { + // The maximum number of unsuccessful connection attempts that will be made before giving up. + // If the parameter is not specified, 1 connection attempt will be made. + google.protobuf.UInt32Value max_connect_attempts = 1; + } + + // The hostname to send in the synthesized CONNECT headers to the upstream proxy. + // This field evaluates command operators if set, otherwise returns hostname as is. + // + // Example: dynamically set hostname using filter state + // + // .. code-block:: yaml + // + // tunneling_config: + // proxy_host: "%FILTER_STATE(proxy.host.key:PLAIN)%" + // + string proxy_host = 1 [(validate.rules).string = {min_len: 1}]; + + // Optional port value to add to the HTTP request URI. + // This value can be overridden per-session by setting the required port value for + // the filter state key ``udp.connect.proxy_port``. + google.protobuf.UInt32Value proxy_port = 2; + + // The target host to send in the synthesized CONNECT headers to the upstream proxy. + // This field evaluates command operators if set, otherwise returns hostname as is. + // + // Example: dynamically set target host using filter state + // + // .. code-block:: yaml + // + // tunneling_config: + // target_host: "%FILTER_STATE(target.host.key:PLAIN)%" + // + string target_host = 3 [(validate.rules).string = {min_len: 1}]; + + // The default target port to send in the CONNECT headers to the upstream proxy. + // This value can be overridden per-session by setting the required port value for + // the filter state key ``udp.connect.target_port``. + uint32 default_target_port = 4 [(validate.rules).uint32 = {lte: 65535 gt: 0}]; + + // Use POST method instead of CONNECT method to tunnel the UDP stream. + // + // .. note:: + // If use_post is set, the upstream stream does not comply with the connect-udp RFC, and + // instead it will be a POST request. the path used in the headers will be set from the + // post_path field, and the headers will not contain the target host and target port, as + // required by the connect-udp protocol. This flag should be used carefully. + // + bool use_post = 5; + + // The path used with POST method. Default path is ``/``. If post path is specified and + // use_post field isn't true, it will be rejected. + string post_path = 6; + + // Optional retry options, in case connecting to the upstream failed. + RetryOptions retry_options = 7; + + // Additional request headers to upstream proxy. Neither ``:-prefixed`` pseudo-headers + // nor the Host: header can be overridden. Values of the added headers evaluates command + // operators if they are set in the value template. + // + // Example: dynamically set a header with the local port + // + // .. code-block:: yaml + // + // headers_to_add: + // - header: + // key: original_dst_port + // value: "%DOWNSTREAM_LOCAL_PORT%" + // + repeated config.core.v3.HeaderValueOption headers_to_add = 8 + [(validate.rules).repeated = {max_items: 1000}]; + + // If configured, the filter will buffer datagrams in case that it is waiting for the upstream to be + // ready, whether if it is during the connection process or due to upstream buffer watermarks. + // If this field is not configured, there will be no buffering and downstream datagrams that arrive + // while the upstream is not ready will be dropped. In case this field is set but the options + // are not configured, the default values will be applied as described in the ``BufferOptions``. + BufferOptions buffer_options = 9; + } + // The stat prefix used when emitting UDP proxy filter stats. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -129,4 +229,8 @@ message UdpProxyConfig { // Only one of use_per_packet_load_balancing or session_filters can be used. // [#extension-category: envoy.filters.udp.session] repeated SessionFilter session_filters = 11; + + // If set, this configures UDP tunneling. See `Proxying UDP in HTTP `_. + // More information can be found in the UDP Proxy and HTTP upgrade documentation. + UdpTunnelingConfig tunneling_config = 12; } diff --git a/bazel/README.md b/bazel/README.md index 34b0a75239d2..03a8008bcf05 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -285,7 +285,7 @@ Envoy can also be built with the Docker image used for CI, by installing Docker On Linux, run: ``` -./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.dev' +./ci/run_envoy_docker.sh './ci/do_ci.sh dev' ``` From a Windows host with Docker installed, the Windows containers feature enabled, and bash (installed via diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 858d03fc28e8..3219ad847af0 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -3826,10 +3826,7 @@ envoy_quic_cc_library( envoy_quic_cc_library( name = "quic_core_qpack_qpack_stream_sender_delegate_lib", hdrs = ["quiche/quic/core/qpack/qpack_stream_sender_delegate.h"], - deps = [ - ":quic_core_types_lib", - ":quic_platform_base", - ], + deps = [":quic_platform_base"], ) envoy_quic_cc_library( diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 5ba73ed199fe..dc3fb97ec0eb 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1210,12 +1210,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "RE2", project_desc = "RE2, a regular expression library", project_url = "https://github.com/google/re2", - version = "2023-07-01", - sha256 = "18cf85922e27fad3ed9c96a27733037da445f35eb1a2744c306a37c6d11e95c4", + version = "2023-09-01", + sha256 = "5bb6875ae1cd1e9fedde98018c346db7260655f86fdb8837e3075103acd3649b", strip_prefix = "re2-{version}", urls = ["https://github.com/google/re2/archive/{version}.tar.gz"], use_category = ["controlplane", "dataplane_core"], - release_date = "2023-06-30", + release_date = "2023-08-31", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/re2/blob/{version}/LICENSE", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 4da54c63662a..1faf60309de3 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -95,6 +95,10 @@ minor_behavior_changes: The redis network filter :ref:`connection_rate_limit_per_sec ` must be greater than 0. A config that sets this value to 0 will be rejected. +- area: http + change: | + change the proxy status for UpstreamRequestTimeout to HttpResponseTimeout. + It can be disabled by the runtime guard ``envoy.reloadable_features.proxy_status_upstream_request_timeout``. - area: local_rate_limit change: | Added new configuration field :ref:`always_consume_default_token_bucket @@ -237,6 +241,10 @@ new_features: change: | added support for QUIC listener filters with ECDS support reusing the same config API :ref:`listener_filters ` as TCP does. +- area: oauth2 + change: | + added :ref:`use_refresh_token ` + to support updating an access token via a refresh token if that is provided by authorization server. - area: redis change: | added support for time command (returns a local response). diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 572a4f460124..aa29a46fb4b6 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -59,7 +59,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless # gcr.io/distroless/base-nossl-debian11:nonroot -FROM gcr.io/distroless/base-nossl-debian11:nonroot@sha256:a156aae8df01d39f2390021016c672bee4fb34f3a90759f4d9aa74c116ec142a AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian11:nonroot@sha256:1311462d37ff75d7eafd7b3656f029a47fa465d5a7c8b4ce7956028e8b8fa5a8 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] diff --git a/ci/README.md b/ci/README.md index 2282bc57ca56..12f6fae55442 100644 --- a/ci/README.md +++ b/ci/README.md @@ -119,7 +119,7 @@ To leverage a [bazel remote cache](https://github.com/envoyproxy/envoy/tree/main the BAZEL_BUILD_EXTRA_OPTIONS environment variable ```bash -./ci/run_envoy_docker.sh "BAZEL_BUILD_EXTRA_OPTIONS='--remote_cache=http://127.0.0.1:28080' ./ci/do_ci.sh bazel.release" +./ci/run_envoy_docker.sh "BAZEL_BUILD_EXTRA_OPTIONS='--remote_cache=http://127.0.0.1:28080' ./ci/do_ci.sh release" ``` The `./ci/run_envoy_docker.sh './ci/do_ci.sh '` targets are: diff --git a/ci/do_ci.sh b/ci/do_ci.sh index c3a6360a7b40..946070f72ec8 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -565,11 +565,17 @@ case $CI_TARGET in -- --stdout \ -d "$ENVOY_RELEASE_TARBALL" \ | tar xfO - envoy > distribution/custom/envoy + bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ + //tools/zstd \ + -- --stdout \ + -d "$ENVOY_RELEASE_TARBALL" \ + | tar xfO - envoy-contrib > distribution/custom/envoy-contrib # Build the packages bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ --remote_download_toplevel \ -c opt \ --//distribution:envoy-binary=//distribution:custom/envoy \ + --//distribution:envoy-contrib-binary=//distribution:custom/envoy-contrib \ //distribution:packages.tar.gz if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then cp -a bazel-bin/distribution/packages.tar.gz "${ENVOY_BUILD_DIR}/packages.x64.tar.gz" diff --git a/configs/raw_udp_tunneling_http2.yaml b/configs/raw_udp_tunneling_http2.yaml new file mode 100644 index 000000000000..30adcd619b7d --- /dev/null +++ b/configs/raw_udp_tunneling_http2.yaml @@ -0,0 +1,72 @@ +# This configuration takes incoming data on port 10000 and encapsulates it in a CONNECT +# request which is sent upstream port 10001. +# It can be used to test UDP tunneling as described in +# https://envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/upgrades + +admin: + address: + socket_address: + protocol: TCP + address: 127.0.0.1 + port_value: 9903 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: UDP + address: 127.0.0.1 + port_value: 10000 + listener_filters: + - name: envoy.filters.udp_listener.udp_proxy + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + stat_prefix: foo + matcher: + on_no_match: + action: + name: route + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: cluster_0 + session_filters: + - name: envoy.filters.udp.session.http_capsule + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.session.http_capsule.v3.FilterConfig + tunneling_config: + # note: proxy_host supports string substitution, for example setting "%FILTER_STATE(proxy.host.key:PLAIN)%" + # will take the target host value from the session's filter state. + proxy_host: proxy.host.com + # note: target_host supports string substitution, for example setting "%FILTER_STATE(target.host.key:PLAIN)%" + # will take the target host value from the session's filter state. + target_host: target.host.com + # note: The target port value can be overridden per-session by setting the required port value for + # the filter state key ``udp.connect.target_port``. + default_target_port: 443 + retry_options: + max_connect_attempts: 2 + buffer_options: + max_buffered_datagrams: 1024 + max_buffered_bytes: 16384 + headers_to_add: + - header: + key: original_dst_port + value: "%DOWNSTREAM_LOCAL_PORT%" + + clusters: + - name: cluster_0 + connect_timeout: 5s + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http2_protocol_options: {} + load_assignment: + cluster_name: cluster_0 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 diff --git a/contrib/golang/common/dso/dso.cc b/contrib/golang/common/dso/dso.cc index f14e8aefb0d5..06d1550fd4a3 100644 --- a/contrib/golang/common/dso/dso.cc +++ b/contrib/golang/common/dso/dso.cc @@ -196,17 +196,15 @@ GoUint64 NetworkFilterDsoImpl::envoyGoFilterOnDownstreamWrite(void* w, GoUint64 return envoy_go_filter_on_downstream_write_(w, data_size, data_ptr, slice_num, end_of_stream); } -void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamConnectionReady( - void* w, GoUint64 connID) { // NOLINT(readability-identifier-naming) +void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamConnectionReady(void* w, GoUint64 conn_id) { ASSERT(envoy_go_filter_on_upstream_connection_ready_ != nullptr); - envoy_go_filter_on_upstream_connection_ready_(w, connID); + envoy_go_filter_on_upstream_connection_ready_(w, conn_id); } -void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamConnectionFailure( - void* w, GoInt reason, - GoUint64 connID) { // NOLINT(readability-identifier-naming) +void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason, + GoUint64 conn_id) { ASSERT(envoy_go_filter_on_upstream_connection_failure_ != nullptr); - envoy_go_filter_on_upstream_connection_failure_(w, reason, connID); + envoy_go_filter_on_upstream_connection_failure_(w, reason, conn_id); } void NetworkFilterDsoImpl::envoyGoFilterOnUpstreamData(void* w, GoUint64 data_size, diff --git a/contrib/golang/common/dso/dso.h b/contrib/golang/common/dso/dso.h index fcb2f120e5ef..f05498375271 100644 --- a/contrib/golang/common/dso/dso.h +++ b/contrib/golang/common/dso/dso.h @@ -123,11 +123,9 @@ class NetworkFilterDso : public Dso { virtual GoUint64 envoyGoFilterOnDownstreamWrite(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) PURE; - virtual void envoyGoFilterOnUpstreamConnectionReady( - void* w, GoUint64 connID) PURE; // NOLINT(readability-identifier-naming) - virtual void envoyGoFilterOnUpstreamConnectionFailure( - void* w, GoInt reason, - GoUint64 connID) PURE; // NOLINT(readability-identifier-naming) + virtual void envoyGoFilterOnUpstreamConnectionReady(void* w, GoUint64 conn_id) PURE; + virtual void envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason, + GoUint64 conn_id) PURE; virtual void envoyGoFilterOnUpstreamData(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) PURE; virtual void envoyGoFilterOnUpstreamEvent(void* w, GoInt event) PURE; @@ -151,10 +149,8 @@ class NetworkFilterDsoImpl : public NetworkFilterDso { GoUint64 envoyGoFilterOnDownstreamWrite(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) override; - void envoyGoFilterOnUpstreamConnectionReady( - void* w, GoUint64 connID) override; // NOLINT(readability-identifier-naming) - void envoyGoFilterOnUpstreamConnectionFailure( - void* w, GoInt reason, GoUint64 connID) override; // NOLINT(readability-identifier-naming) + void envoyGoFilterOnUpstreamConnectionReady(void* w, GoUint64 conn_id) override; + void envoyGoFilterOnUpstreamConnectionFailure(void* w, GoInt reason, GoUint64 conn_id) override; void envoyGoFilterOnUpstreamData(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) override; void envoyGoFilterOnUpstreamEvent(void* w, GoInt event) override; @@ -176,13 +172,9 @@ class NetworkFilterDsoImpl : public NetworkFilterDso { GoInt slice_num, GoInt end_of_stream) = {nullptr}; - // NOLINTNEXTLINE(readability-identifier-naming) - void (*envoy_go_filter_on_upstream_connection_ready_)(void* w, GoUint64 connID) = {nullptr}; - // NOLINTNEXTLINE(readability-identifier-naming) - void (*envoy_go_filter_on_upstream_connection_failure_)( - void* w, GoInt reason, - // NOLINTNEXTLINE(readability-identifier-naming) - GoUint64 connID) = {nullptr}; + void (*envoy_go_filter_on_upstream_connection_ready_)(void* w, GoUint64 conn_id) = {nullptr}; + void (*envoy_go_filter_on_upstream_connection_failure_)(void* w, GoInt reason, + GoUint64 conn_id) = {nullptr}; void (*envoy_go_filter_on_upstream_data_)(void* w, GoUint64 data_size, GoUint64 data_ptr, GoInt slice_num, GoInt end_of_stream) = {nullptr}; void (*envoy_go_filter_on_upstream_event_)(void* w, GoInt event) = {nullptr}; diff --git a/contrib/golang/common/dso/libgolang.h b/contrib/golang/common/dso/libgolang.h index 74b704b64c02..c9f54b2ed448 100644 --- a/contrib/golang/common/dso/libgolang.h +++ b/contrib/golang/common/dso/libgolang.h @@ -105,23 +105,17 @@ extern void envoyGoFilterDestroyHttpPluginConfig(GoUint64 id); // go:linkname envoyGoFilterMergeHttpPluginConfig // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterMergeHttpPluginConfig -extern GoUint64 envoyGoFilterMergeHttpPluginConfig( - GoUint64 namePtr, GoUint64 nameLen, // NOLINT(readability-identifier-naming) - GoUint64 parentId, GoUint64 childId); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterMergeHttpPluginConfig(GoUint64 name_ptr, GoUint64 name_len, + GoUint64 parent_id, GoUint64 child_id); // go:linkname envoyGoFilterOnHttpHeader // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpHeader -extern GoUint64 -envoyGoFilterOnHttpHeader(httpRequest* r, - GoUint64 endStream, // NOLINT(readability-identifier-naming) - GoUint64 headerNum, // NOLINT(readability-identifier-naming) - GoUint64 headerBytes); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnHttpHeader(httpRequest* r, GoUint64 end_stream, GoUint64 header_num, + GoUint64 header_bytes); // go:linkname envoyGoFilterOnHttpData // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpData -extern GoUint64 envoyGoFilterOnHttpData(httpRequest* r, - GoUint64 endStream, // NOLINT(readability-identifier-naming) - GoUint64 buffer, // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnHttpData(httpRequest* r, GoUint64 end_stream, GoUint64 buffer, GoUint64 length); // go:linkname envoyGoFilterOnHttpLog @@ -138,41 +132,32 @@ extern void envoyGoRequestSemaDec(httpRequest* r); // go:linkname envoyGoOnClusterSpecify // github.com/envoyproxy/envoy/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier.envoyGoOnClusterSpecify -extern GoInt64 envoyGoOnClusterSpecify(GoUint64 pluginPtr, // NOLINT(readability-identifier-naming) - GoUint64 headerPtr, // NOLINT(readability-identifier-naming) - GoUint64 pluginId, // NOLINT(readability-identifier-naming) - GoUint64 bufferPtr, // NOLINT(readability-identifier-naming) - GoUint64 bufferLen); // NOLINT(readability-identifier-naming) +extern GoInt64 envoyGoOnClusterSpecify(GoUint64 plugin_ptr, GoUint64 header_ptr, GoUint64 plugin_id, + GoUint64 buffer_ptr, GoUint64 buffer_len); // go:linkname envoyGoClusterSpecifierNewPlugin // github.com/envoyproxy/envoy/contrib/golang/router/cluster_specifier/source/go/pkg/cluster_specifier.envoyGoClusterSpecifierNewPlugin -extern GoUint64 -envoyGoClusterSpecifierNewPlugin(GoUint64 configPtr, // NOLINT(readability-identifier-naming) - GoUint64 configLen); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoClusterSpecifierNewPlugin(GoUint64 config_ptr, GoUint64 config_len); // go:linkname envoyGoFilterOnNetworkFilterConfig // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnNetworkFilterConfig -extern GoUint64 envoyGoFilterOnNetworkFilterConfig( - GoUint64 libraryIDPtr, GoUint64 libraryIDLen, // NOLINT(readability-identifier-naming) - GoUint64 configPtr, GoUint64 configLen); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnNetworkFilterConfig(GoUint64 library_id_ptr, GoUint64 library_id_len, + GoUint64 config_ptr, GoUint64 config_len); // go:linkname envoyGoFilterOnDownstreamConnection // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnDownstreamConnection -extern GoUint64 envoyGoFilterOnDownstreamConnection( - void* f, GoUint64 pluginNamePtr, // NOLINT(readability-identifier-naming) - GoUint64 pluginNameLen, GoUint64 configID); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnDownstreamConnection(void* f, GoUint64 plugin_name_ptr, + GoUint64 plugin_name_len, GoUint64 config_id); // go:linkname envoyGoFilterOnDownstreamData // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnDownstreamData -extern GoUint64 envoyGoFilterOnDownstreamData( - void* f, GoUint64 dataSize, GoUint64 dataPtr, // NOLINT(readability-identifier-naming) - GoInt sliceNum, GoInt endOfStream); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnDownstreamData(void* f, GoUint64 data_size, GoUint64 data_ptr, + GoInt slice_num, GoInt end_of_stream); // go:linkname envoyGoFilterOnDownstreamWrite // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnDownstreamWrite -extern GoUint64 envoyGoFilterOnDownstreamWrite( - void* f, GoUint64 dataSize, GoUint64 dataPtr, // NOLINT(readability-identifier-naming) - GoInt sliceNum, GoInt endOfStream); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnDownstreamWrite(void* f, GoUint64 data_size, GoUint64 data_ptr, + GoInt slice_num, GoInt end_of_stream); // go:linkname envoyGoFilterOnDownstreamEvent // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnDownstreamEvent @@ -180,21 +165,16 @@ extern void envoyGoFilterOnDownstreamEvent(void* f, GoInt event); // go:linkname envoyGoFilterOnUpstreamConnectionReady // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnUpstreamConnectionReady -extern void -envoyGoFilterOnUpstreamConnectionReady(void* f, - GoUint64 connID); // NOLINT(readability-identifier-naming) +extern void envoyGoFilterOnUpstreamConnectionReady(void* f, GoUint64 conn_id); // go:linkname envoyGoFilterOnUpstreamConnectionFailure // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnUpstreamConnectionFailure -extern void -envoyGoFilterOnUpstreamConnectionFailure(void* f, GoInt reason, - GoUint64 connID); // NOLINT(readability-identifier-naming) +extern void envoyGoFilterOnUpstreamConnectionFailure(void* f, GoInt reason, GoUint64 conn_id); // go:linkname envoyGoFilterOnUpstreamData // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnUpstreamData -extern GoUint64 envoyGoFilterOnUpstreamData( - void* f, GoUint64 dataSize, GoUint64 dataPtr, // NOLINT(readability-identifier-naming) - GoInt sliceNum, GoInt endOfStream); // NOLINT(readability-identifier-naming) +extern GoUint64 envoyGoFilterOnUpstreamData(void* f, GoUint64 data_size, GoUint64 data_ptr, + GoInt slice_num, GoInt end_of_stream); // go:linkname envoyGoFilterOnUpstreamEvent // github.com/envoyproxy/envoy/contrib/golang/filters/network/source/go/pkg/network.envoyGoFilterOnUpstreamEvent diff --git a/contrib/golang/common/dso/test/mocks.h b/contrib/golang/common/dso/test/mocks.h index ab688967135d..d72fbf60983f 100644 --- a/contrib/golang/common/dso/test/mocks.h +++ b/contrib/golang/common/dso/test/mocks.h @@ -42,9 +42,9 @@ class MockNetworkFilterDsoImpl : public NetworkFilterDso { MOCK_METHOD(GoUint64, envoyGoFilterOnDownstreamWrite, (void* w, GoUint64 dataSize, GoUint64 dataPtr, GoInt sliceNum, GoInt endOfStream)); - MOCK_METHOD(void, envoyGoFilterOnUpstreamConnectionReady, (void* w, GoUint64 connID)); + MOCK_METHOD(void, envoyGoFilterOnUpstreamConnectionReady, (void* w, GoUint64 conn_id)); MOCK_METHOD(void, envoyGoFilterOnUpstreamConnectionFailure, - (void* w, GoInt reason, GoUint64 connID)); + (void* w, GoInt reason, GoUint64 conn_id)); MOCK_METHOD(void, envoyGoFilterOnUpstreamData, (void* w, GoUint64 dataSize, GoUint64 dataPtr, GoInt sliceNum, GoInt endOfStream)); MOCK_METHOD(void, envoyGoFilterOnUpstreamEvent, (void* w, GoInt event)); diff --git a/contrib/golang/common/go/api/api.h b/contrib/golang/common/go/api/api.h index 16bed05036a7..b9f73b7718fa 100644 --- a/contrib/golang/common/go/api/api.h +++ b/contrib/golang/common/go/api/api.h @@ -94,23 +94,14 @@ CAPIStatus envoyGoFilterHttpGetMetric(void* c, uint32_t metric_id, void* value); CAPIStatus envoyGoFilterHttpRecordMetric(void* c, uint32_t metric_id, uint64_t value); // downstream -CAPIStatus envoyGoFilterDownstreamClose(void* wrapper, - int closeType); // NOLINT(readability-identifier-naming) -CAPIStatus envoyGoFilterDownstreamWrite(void* wrapper, void* buffers, - int buffersNum, // NOLINT(readability-identifier-naming) - int endStream); // NOLINT(readability-identifier-naming) +CAPIStatus envoyGoFilterDownstreamClose(void* wrapper, int close_type); +CAPIStatus envoyGoFilterDownstreamWrite(void* f, void* buffer_ptr, int buffer_len, int end_stream); void envoyGoFilterDownstreamFinalize(void* wrapper, int reason); CAPIStatus envoyGoFilterDownstreamInfo(void* wrapper, int t, void* ret); -// upstream -void* envoyGoFilterUpstreamConnect( - void* libraryID, void* addr, // NOLINT(readability-identifier-naming) - unsigned long long int connID); // NOLINT(readability-identifier-naming) -CAPIStatus envoyGoFilterUpstreamWrite(void* wrapper, void* buffers, - int buffersNum, // NOLINT(readability-identifier-naming) - int endStream); // NOLINT(readability-identifier-naming) -CAPIStatus envoyGoFilterUpstreamClose(void* wrapper, - int closeType); // NOLINT(readability-identifier-naming) +void* envoyGoFilterUpstreamConnect(void* library_id, void* addr, unsigned long long int conn_id); +CAPIStatus envoyGoFilterUpstreamWrite(void* u, void* buffer_ptr, int buffer_len, int end_stream); +CAPIStatus envoyGoFilterUpstreamClose(void* wrapper, int close_type); void envoyGoFilterUpstreamFinalize(void* wrapper, int reason); CAPIStatus envoyGoFilterUpstreamInfo(void* wrapper, int t, void* ret); diff --git a/contrib/golang/filters/network/source/cgo.cc b/contrib/golang/filters/network/source/cgo.cc index 2f6a6e610e04..e6411260813e 100644 --- a/contrib/golang/filters/network/source/cgo.cc +++ b/contrib/golang/filters/network/source/cgo.cc @@ -116,13 +116,11 @@ CAPIStatus envoyGoFilterDownstreamInfo(void* f, int info_type, void* ret) { // Upstream // -void* envoyGoFilterUpstreamConnect( - void* library_id, void* addr, - unsigned long long int connID) { // NOLINT(readability-identifier-naming) +void* envoyGoFilterUpstreamConnect(void* library_id, void* addr, unsigned long long int conn_id) { std::string id = copyGoString(library_id); auto dynamic_lib = Dso::DsoManager::getDsoByID(id); UpstreamConnPtr conn_ptr = - std::make_shared(copyGoString(addr), dynamic_lib, connID); + std::make_shared(copyGoString(addr), dynamic_lib, conn_id); // the upstream connect wrapper will be deleted by envoyGoFilterUpstreamFinalize UpstreamConnWrapper* wrapper = new UpstreamConnWrapper(conn_ptr); conn_ptr->setWrapper(wrapper); diff --git a/contrib/golang/filters/network/source/upstream.cc b/contrib/golang/filters/network/source/upstream.cc index 973f1dae7c40..f9f1ddec5e19 100644 --- a/contrib/golang/filters/network/source/upstream.cc +++ b/contrib/golang/filters/network/source/upstream.cc @@ -46,9 +46,8 @@ void UpstreamConn::initThreadLocalStorage(Server::Configuration::FactoryContext& } UpstreamConn::UpstreamConn(std::string addr, Dso::NetworkFilterDsoPtr dynamic_lib, - unsigned long long int goConnID, // NOLINT(readability-identifier-naming) - Event::Dispatcher* dispatcher) - : dynamic_lib_(dynamic_lib), goConnID_(goConnID), dispatcher_(dispatcher), addr_(addr) { + unsigned long long int go_conn_id, Event::Dispatcher* dispatcher) + : dynamic_lib_(dynamic_lib), go_conn_id_(go_conn_id), dispatcher_(dispatcher), addr_(addr) { if (dispatcher_ == nullptr) { DispatcherStore& store = dispatcherStore(); Thread::LockGuard guard(store.lock_); @@ -130,7 +129,7 @@ void UpstreamConn::onPoolReady(Tcp::ConnectionPool::ConnectionDataPtr&& conn, conn_->addUpstreamCallbacks(*this); remote_addr_ = conn_->connection().connectionInfoProvider().directRemoteAddress()->asString(); - dynamic_lib_->envoyGoFilterOnUpstreamConnectionReady(wrapper_, goConnID_); + dynamic_lib_->envoyGoFilterOnUpstreamConnectionReady(wrapper_, go_conn_id_); } void UpstreamConn::onPoolFailure(Tcp::ConnectionPool::PoolFailureReason reason, @@ -143,7 +142,7 @@ void UpstreamConn::onPoolFailure(Tcp::ConnectionPool::PoolFailureReason reason, } dynamic_lib_->envoyGoFilterOnUpstreamConnectionFailure(wrapper_, static_cast(reason), - goConnID_); + go_conn_id_); } void UpstreamConn::onEvent(Network::ConnectionEvent event) { diff --git a/contrib/golang/filters/network/source/upstream.h b/contrib/golang/filters/network/source/upstream.h index 272e014afb13..8ee09d3f89c2 100644 --- a/contrib/golang/filters/network/source/upstream.h +++ b/contrib/golang/filters/network/source/upstream.h @@ -36,8 +36,7 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, Logger::Loggable { public: UpstreamConn(std::string addr, Dso::NetworkFilterDsoPtr dynamic_lib, - unsigned long long int goConnID, // NOLINT(readability-identifier-naming) - Event::Dispatcher* dispatcher = nullptr); + unsigned long long int go_conn_id, Event::Dispatcher* dispatcher = nullptr); ~UpstreamConn() override { if (handler_) { handler_->cancel(Tcp::ConnectionPool::CancelPolicy::Default); @@ -96,7 +95,7 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, } Dso::NetworkFilterDsoPtr dynamic_lib_{nullptr}; - unsigned long long int goConnID_{0}; // NOLINT(readability-identifier-naming) + unsigned long long int go_conn_id_{0}; UpstreamConnWrapper* wrapper_{nullptr}; Event::Dispatcher* dispatcher_{nullptr}; std::unique_ptr stream_info_{nullptr}; diff --git a/distribution/debian/packages.bzl b/distribution/debian/packages.bzl index ac1834e69021..19ecc8a3fa5b 100644 --- a/distribution/debian/packages.bzl +++ b/distribution/debian/packages.bzl @@ -10,7 +10,7 @@ def envoy_pkg_deb( description = "Envoy built for Debian/Ubuntu", preinst = "//distribution/debian:preinst", postinst = "//distribution/debian:postinst", - supported_distributions = "bullseye focal jammy", + supported_distributions = "bookworm bullseye focal jammy", architecture = select({ "//bazel:x86": "amd64", "//conditions:default": "arm64", @@ -49,7 +49,7 @@ def envoy_pkg_deb( output_group = "deb", ) -def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":envoy-bin-files", config = ":envoy-config"): +def envoy_pkg_debs(name, version, release_version, maintainer, bin_files, contrib_bin_files, config = ":envoy-config"): """Package the Envoy .debs with their .changes files. Packages are created for the version *and* the release version, eg @@ -57,7 +57,8 @@ def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":env - envoy_1.21.0_amd64.deb - envoy-1.21_1.21.0_amd64.deb - This way packages are available for both "envoy" and "envoy-1.21" in package managers. + This way packages are available for both "envoy" and "envoy-1.21" in package managers, and users can install either + a specifically versioned package, or the latest for that minor version. """ # generate deb data for all packages @@ -71,6 +72,17 @@ def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":env remap_paths = {"/copyright": "/usr/share/doc/envoy/copyright"}, ) + # generate deb data for all contrib packages + pkg_tar( + name = "contrib-deb-data", + srcs = [ + "//distribution/debian:copyright", + config, + contrib_bin_files, + ], + remap_paths = {"/copyright": "/usr/share/doc/envoy/copyright"}, + ) + # generate package for this patch version envoy_pkg_deb( name = "envoy", @@ -89,6 +101,24 @@ def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":env maintainer = maintainer, ) + # generate contrib package for this patch version + envoy_pkg_deb( + name = "envoy-contrib", + data = ":contrib-deb-data", + version = version, + maintainer = maintainer, + ) + + # generate contrib package for this minor version + envoy_pkg_deb( + name = "envoy-contrib-%s" % release_version, + data = ":contrib-deb-data", + version = version, + conflicts = ["envoy"], + provides = ["envoy"], + maintainer = maintainer, + ) + pkg_tar( name = name, srcs = [ @@ -96,6 +126,10 @@ def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":env "envoy.deb", "envoy-%s.changes" % release_version, "envoy-%s.deb" % release_version, + "envoy-contrib.changes", + "envoy-contrib.deb", + "envoy-contrib-%s.changes" % release_version, + "envoy-contrib-%s.deb" % release_version, ], extension = "tar", package_dir = "deb", diff --git a/distribution/distros.yaml b/distribution/distros.yaml index 6dc239ad27a1..40c54e657a50 100644 --- a/distribution/distros.yaml +++ b/distribution/distros.yaml @@ -2,6 +2,10 @@ debian_bullseye: image: debian:bullseye-slim ext: bullseye.changes +debian_bookworm: + image: debian:bookworm-slim + ext: bookworm.changes + ubuntu_focal: image: ubuntu:20.04 ext: focal.changes diff --git a/distribution/packages.bzl b/distribution/packages.bzl index dacd54829fb8..4690bb7ee90f 100644 --- a/distribution/packages.bzl +++ b/distribution/packages.bzl @@ -12,6 +12,7 @@ def _release_version_for(version): def envoy_pkg_distros( name, envoy_bin = ":envoy-binary", + envoy_contrib_bin = ":envoy-contrib-binary", version = None, maintainer = None, config = "//configs:envoyproxy_io_proxy.yaml"): @@ -31,10 +32,19 @@ def envoy_pkg_distros( renames = {envoy_bin: "/usr/bin/envoy"}, ) + pkg_files( + name = "envoy-contrib-bin-files", + srcs = [envoy_contrib_bin], + attributes = pkg_attributes(mode = "0755"), + renames = {envoy_contrib_bin: "/usr/bin/envoy"}, + ) + # build debs envoy_pkg_debs( name = "debs", version = version, + bin_files = ":envoy-bin-files", + contrib_bin_files = ":envoy-contrib-bin-files", release_version = _release_version_for(version), maintainer = maintainer, ) @@ -43,9 +53,7 @@ def envoy_pkg_distros( pkg_tar( name = "distro_packages", extension = "tar", - deps = [ - ":debs", - ], + deps = [":debs"], ) # sign the packages diff --git a/docs/root/configuration/advanced/well_known_filter_state.rst b/docs/root/configuration/advanced/well_known_filter_state.rst index dfe6b104c47d..952cf662bb4d 100644 --- a/docs/root/configuration/advanced/well_known_filter_state.rst +++ b/docs/root/configuration/advanced/well_known_filter_state.rst @@ -5,60 +5,60 @@ Well Known Filter State Objects The following list of filter state objects are consumed by Envoy extensions: -.. list-table:: - :widths: auto - :header-rows: 1 - :stub-columns: 1 - - * - **Filter state key** - - **Purpose** - * - ``envoy.network.upstream_server_name`` - - | Sets the transport socket option to override the - | `SNI ` - | in the upstream connections. - | Accepts a host name as a constructor, e.g. "lyft.com". - * - ``envoy.network.application_protocols`` - - | Sets the transport socket option to override the - | `ALPN ` list - | in the upstream connections. This setting takes precedence over the upstream cluster - | configuration. - | Accepts a comma-separated list of protocols as a constructor, e.g. "h2,http/1.1". - * - ``envoy.network.upstream_subject_alt_names`` - - | Enables additional verification of the upstream peer certificate SAN names. - | Accepts a comma-separated list of SAN names as a constructor. - * - ``envoy.tcp_proxy.cluster`` - - | :ref:`TCP proxy ` dynamic cluster name selection - | on a per-connection basis. - | Accepts a cluster name as a constructor. - * - ``envoy.network.transport_socket.original_dst_address`` - - | :ref:`Original destination cluster ` dynamic address selection. - | Accepts an `IP:PORT` string as a constructor. - | Fields: - | - ``ip``: IP address value as a string; - | - ``port``: port value as a number. - * - ``envoy.filters.listener.original_dst.local_ip`` - - | :ref:`Original destination listener filter ` - | destination address selection for the internal listeners. - | Accepts an `IP:PORT` string as a constructor. - | Fields: - | - ``ip``: IP address value as a string; - | - ``port``: port value as a number. - * - ``envoy.filters.listener.original_dst.remote_ip`` - - | :ref:`Original destination listener filter ` - | source address selection for the internal listeners. - | Accepts an `IP:PORT` string as a constructor. - | Fields: - | - ``ip``: IP address value as a string; - | - ``port``: port value as a number. - * - ``envoy.upstream.dynamic_host`` - - | :ref:`Dynamic forward proxy ` - | upstream host override on a per-connection basis. - | Accepts a host string as a constructor. - * - ``envoy.upstream.dynamic_port`` - - | :ref:`Dynamic forward proxy ` - | upstream port override on a per-connection basis. - | Accepts a port number string as a constructor. +``envoy.network.upstream_server_name`` + Sets the transport socket option to override the `SNI `_ in + the upstream connections. Accepts a host name as a constructor, e.g. "lyft.com". +``envoy.network.application_protocols`` + Sets the transport socket option to override the `ALPN `_ list in the upstream connections. This setting takes precedence over the upstream cluster configuration. + Accepts a comma-separated list of protocols as a constructor, e.g. "h2,http/1.1". + +``envoy.network.upstream_subject_alt_names`` + Enables additional verification of the upstream peer certificate SAN names. Accepts a comma-separated list of SAN + names as a constructor. + +``envoy.tcp_proxy.cluster`` + :ref:`TCP proxy ` dynamic cluster name selection on a per-connection basis. Accepts + a cluster name as a constructor. + +``envoy.network.transport_socket.original_dst_address`` + :ref:`Original destination cluster ` dynamic address + selection. Accepts an `IP:PORT` string as a constructor. Fields: + + * ``ip``: IP address value as a string; + * ``port``: port value as a number. + +``envoy.filters.listener.original_dst.local_ip`` + :ref:`Original destination listener filter ` destination address selection for + the internal listeners. Accepts an `IP:PORT` string as a constructor. Fields: + + * ``ip``: IP address value as a string; + * ``port``: port value as a number. + +``envoy.filters.listener.original_dst.remote_ip`` + :ref:`Original destination listener filter ` source address selection for the + internal listeners. Accepts an `IP:PORT` string as a constructor. Fields: + + * ``ip``: IP address value as a string; + * ``port``: port value as a number. + +``envoy.upstream.dynamic_host`` + :ref:`Dynamic forward proxy ` upstream + host override on a per-connection basis. Accepts a host string as a constructor. + +``envoy.upstream.dynamic_port`` + :ref:`Dynamic forward proxy ` upstream + port override on a per-connection basis. Accepts a port number string as a constructor. + +``envoy.tcp_proxy.disable_tunneling`` + :ref:`TCP proxy tunneling override + ` to disable tunneling on a + per-connection bases. Accepts values "true" and "false". + + +Filter state object fields +-------------------------- The filter state object fields can be used in the format strings. For example, the following format string references the port number in the original diff --git a/docs/root/configuration/http/http_filters/oauth2_filter.rst b/docs/root/configuration/http/http_filters/oauth2_filter.rst index 3c1a3975affc..1daa009cf7f9 100644 --- a/docs/root/configuration/http/http_filters/oauth2_filter.rst +++ b/docs/root/configuration/http/http_filters/oauth2_filter.rst @@ -246,6 +246,11 @@ sending the user to the configured auth endpoint. an interface for users to provide specific header matching criteria such that, when applicable, the OAuth flow is entirely skipped. When this occurs, the ``oauth_passthrough`` metric is incremented but ``success`` is not. +:ref:`use_refresh_token ` provides +possibility to update access token by using a refresh token. By default after expiration the user is always redirected to the authorization endpoint to log in again. +By enabling this flag a new access token is obtained using by a refresh token without redirecting the user to log in again. This requires the refresh token to be provided by authorization_endpoint when the user logs in. +If the attempt to get an access token by using a refresh token fails then the user is redirected to the authorization endpoint as usual. + Generally, allowlisting is inadvisable from a security standpoint. Statistics @@ -261,3 +266,5 @@ The OAuth2 filter outputs statistics in the ``.`` namespace. oauth_passthrough, Counter, Total request that matched a passthrough header. oauth_success, Counter, Total requests that were allowed. oauth_unauthorization_rq, Counter, Total unauthorized requests. + oauth_refreshtoken_success, Counter, Total successfull requests for update access token using by refresh token + oauth_refreshtoken_failure, Counter, Total failed requests for update access token using by refresh token diff --git a/docs/root/configuration/listeners/udp_filters/udp_proxy.rst b/docs/root/configuration/listeners/udp_filters/udp_proxy.rst index 42d8b2179868..79fc13f01788 100644 --- a/docs/root/configuration/listeners/udp_filters/udp_proxy.rst +++ b/docs/root/configuration/listeners/udp_filters/udp_proxy.rst @@ -74,6 +74,8 @@ remaining datagrams to different clusters according to their source ports. :lines: 14-53 :caption: :download:`udp-proxy-router.yaml <_include/udp-proxy-router.yaml>` +.. _config_udp_listener_filters_udp_proxy_session_filters: + Session filters --------------- @@ -96,6 +98,19 @@ Envoy has the following builtin UDP session filters. session_filters/http_capsule +.. _config_udp_listener_filters_udp_proxy_tunneling_over_http: + +UDP Tunneling over HTTP +----------------------- + +The UDP proxy filter can be used to tunnel raw UDP over HTTP requests. Refer to :ref:`HTTP upgrades ` for more information. + +UDP tunneling configuration can be used by setting :ref:`tunneling_config ` + +.. note:: + Since :ref:`per packet load balancing ` require + choosing the upstream host for each received datagram, tunneling can't be used when this option is enabled. + Example configuration --------------------- @@ -156,3 +171,6 @@ The UDP proxy filter also emits custom upstream cluster stats prefixed with sess_rx_errors, Counter, Number of datagram receive errors sess_tx_datagrams, Counter, Number of datagrams transmitted sess_tx_errors, Counter, Number of datagrams transmitted + sess_tunnel_success, Counter, Number of successfully established UDP tunnels + sess_tunnel_failure, Counter, Number of UDP tunnels failed to establish + sess_tunnel_buffer_overflow, Counter, Number of datagrams dropped due to full tunnel buffer diff --git a/docs/root/intro/arch_overview/http/upgrades.rst b/docs/root/intro/arch_overview/http/upgrades.rst index 3e06357f9f93..a229c21236cc 100644 --- a/docs/root/intro/arch_overview/http/upgrades.rst +++ b/docs/root/intro/arch_overview/http/upgrades.rst @@ -182,6 +182,52 @@ must be set to terminate ``CONNECT-UDP`` requests. :emphasize-lines: 15-16, 19-22, 29-30 :caption: :download:`terminate_http3_connect_udp.yaml ` +.. _tunneling-udp-over-http: + +Tunneling UDP over HTTP +^^^^^^^^^^^^^^^^^^^^^^^ + +.. note:: + Raw UDP tunneling is in an alpha status and may not be stable enough for use in production. + We recommend to use this feature with caution. + +Apart from ``CONNECT-UDP`` termination, as described in the section above, Envoy also has support for tunneling raw UDP over HTTP +``CONNECT`` or HTTP ``POST`` requests, by utilizing the UDP Proxy listener filter. By default, UDP tunneling is disabled, and can be +enabled by setting the configuration for :ref:`tunneling_config `. + +.. note:: + Currently, Envoy only supports UDP tunneling over HTTP/2 streams. + +By default, the ``tunneling_config`` will upgrade the connection to create HTTP/2 streams for each UDP session (a UDP session is identified by the datagrams 5-tuple), according +to the `Proxying UDP in HTTP RFC `_. Since this upgrade protocol requires an encapsulation mechanism to preserve the boundaries of the original datagram, +it's required to apply the :ref:`HTTP Capsule ` session filter. +The HTTP/2 streams will be multiplexed over the upstream connection. + +As opposed to TCP tunneling, where downstream flow control can be applied by alternately disabling the read from the connection socket, for UDP datagrams, this mechanism is not supported. +Therefore, when tunneling UDP and a new datagram is received from the downstream, it is either streamed upstream, if the upstream is ready or halted by the UDP Proxy. +In case the upstream is not ready (for example, when waiting for HTTP response headers), the datagram can either be dropped or buffered until the upstream is ready. +In such cases, by default, downstream datagrams will be dropped, unless :ref:`buffer_options ` +is set by the ``tunneling_config``. The default buffer limits are modest to try and prevent a lot of unwanted buffered memory, but can and should be adjusted per the required use-case. +When the upstream becomes ready, the UDP Proxy will first flush all the previously buffered datagrams. + +.. note:: + If ``POST`` is set, the upstream stream does not comply with the connect-udp RFC, and instead it will be a POST request. + The path used in the headers will be set from the post_path field, and the headers will not contain the target host and + target port, as required by the connect-udp protocol. This option should be used carefully. + +Example Configuration +--------------------- + +The following example configuration makes Envoy tunnel raw UDP datagrams over an upgraded ``CONNECT-UDP`` requests to the upstream. + +.. literalinclude:: /_configs/repo/raw_udp_tunneling_http2.yaml + :language: yaml + :linenos: + :lines: 32-53 + :lineno-start: 32 + :emphasize-lines: 4-4, 15-17 + :caption: :download:`raw_udp_tunneling_http2.yaml ` + .. _tunneling-tcp-over-http: Tunneling TCP over HTTP diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index c25b1ad04486..7349c4a9726d 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -12,120 +12,138 @@ charset-normalizer==2.0.6 \ --hash=sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6 \ --hash=sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f # via requests -grpcio==1.58.0 \ - --hash=sha256:002f228d197fea12797a14e152447044e14fb4fdb2eb5d6cfa496f29ddbf79ef \ - --hash=sha256:039003a5e0ae7d41c86c768ef8b3ee2c558aa0a23cf04bf3c23567f37befa092 \ - --hash=sha256:09206106848462763f7f273ca93d2d2d4d26cab475089e0de830bb76be04e9e8 \ - --hash=sha256:128eb1f8e70676d05b1b0c8e6600320fc222b3f8c985a92224248b1367122188 \ - --hash=sha256:1c1c5238c6072470c7f1614bf7c774ffde6b346a100521de9ce791d1e4453afe \ - --hash=sha256:1ed979b273a81de36fc9c6716d9fb09dd3443efa18dcc8652501df11da9583e9 \ - --hash=sha256:201e550b7e2ede113b63e718e7ece93cef5b0fbf3c45e8fe4541a5a4305acd15 \ - --hash=sha256:212f38c6a156862098f6bdc9a79bf850760a751d259d8f8f249fc6d645105855 \ - --hash=sha256:24765a627eb4d9288ace32d5104161c3654128fe27f2808ecd6e9b0cfa7fc8b9 \ - --hash=sha256:24edec346e69e672daf12b2c88e95c6f737f3792d08866101d8c5f34370c54fd \ - --hash=sha256:2ef8d4a76d2c7d8065aba829f8d0bc0055495c998dce1964ca5b302d02514fb3 \ - --hash=sha256:2f85f87e2f087d9f632c085b37440a3169fda9cdde80cb84057c2fc292f8cbdf \ - --hash=sha256:3886b4d56bd4afeac518dbc05933926198aa967a7d1d237a318e6fbc47141577 \ - --hash=sha256:3e6bebf1dfdbeb22afd95650e4f019219fef3ab86d3fca8ebade52e4bc39389a \ - --hash=sha256:458899d2ebd55d5ca2350fd3826dfd8fcb11fe0f79828ae75e2b1e6051d50a29 \ - --hash=sha256:4891bbb4bba58acd1d620759b3be11245bfe715eb67a4864c8937b855b7ed7fa \ - --hash=sha256:4b12754af201bb993e6e2efd7812085ddaaef21d0a6f0ff128b97de1ef55aa4a \ - --hash=sha256:532410c51ccd851b706d1fbc00a87be0f5312bd6f8e5dbf89d4e99c7f79d7499 \ - --hash=sha256:5b23d75e5173faa3d1296a7bedffb25afd2fddb607ef292dfc651490c7b53c3d \ - --hash=sha256:62831d5e251dd7561d9d9e83a0b8655084b2a1f8ea91e4bd6b3cedfefd32c9d2 \ - --hash=sha256:652978551af02373a5a313e07bfef368f406b5929cf2d50fa7e4027f913dbdb4 \ - --hash=sha256:6801ff6652ecd2aae08ef994a3e49ff53de29e69e9cd0fd604a79ae4e545a95c \ - --hash=sha256:6cba491c638c76d3dc6c191d9c75041ca5b8f5c6de4b8327ecdcab527f130bb4 \ - --hash=sha256:7e473a7abad9af48e3ab5f3b5d237d18208024d28ead65a459bd720401bd2f8f \ - --hash=sha256:8774219e21b05f750eef8adc416e9431cf31b98f6ce9def288e4cea1548cbd22 \ - --hash=sha256:8f061722cad3f9aabb3fbb27f3484ec9d4667b7328d1a7800c3c691a98f16bb0 \ - --hash=sha256:92ae871a902cf19833328bd6498ec007b265aabf2fda845ab5bd10abcaf4c8c6 \ - --hash=sha256:9f13a171281ebb4d7b1ba9f06574bce2455dcd3f2f6d1fbe0fd0d84615c74045 \ - --hash=sha256:a2d67ff99e70e86b2be46c1017ae40b4840d09467d5455b2708de6d4c127e143 \ - --hash=sha256:b5e8db0aff0a4819946215f156bd722b6f6c8320eb8419567ffc74850c9fd205 \ - --hash=sha256:ba0af11938acf8cd4cf815c46156bcde36fa5850518120920d52620cc3ec1830 \ - --hash=sha256:bc325fed4d074367bebd465a20763586e5e1ed5b943e9d8bc7c162b1f44fd602 \ - --hash=sha256:bc7ffef430b80345729ff0a6825e9d96ac87efe39216e87ac58c6c4ef400de93 \ - --hash=sha256:cde11577d5b6fd73a00e6bfa3cf5f428f3f33c2d2878982369b5372bbc4acc60 \ - --hash=sha256:d4cef77ad2fed42b1ba9143465856d7e737279854e444925d5ba45fc1f3ba727 \ - --hash=sha256:d79b660681eb9bc66cc7cbf78d1b1b9e335ee56f6ea1755d34a31108b80bd3c8 \ - --hash=sha256:d81c2b2b24c32139dd2536972f1060678c6b9fbd106842a9fcdecf07b233eccd \ - --hash=sha256:dc72e04620d49d3007771c0e0348deb23ca341c0245d610605dddb4ac65a37cb \ - --hash=sha256:dcfba7befe3a55dab6fe1eb7fc9359dc0c7f7272b30a70ae0af5d5b063842f28 \ - --hash=sha256:e9f995a8a421405958ff30599b4d0eec244f28edc760de82f0412c71c61763d2 \ - --hash=sha256:eb6b92036ff312d5b4182fa72e8735d17aceca74d0d908a7f08e375456f03e07 \ - --hash=sha256:f0241f7eb0d2303a545136c59bc565a35c4fc3b924ccbd69cb482f4828d6f31c \ - --hash=sha256:fad9295fe02455d4f158ad72c90ef8b4bcaadfdb5efb5795f7ab0786ad67dd58 \ - --hash=sha256:fbcecb6aedd5c1891db1d70efbfbdc126c986645b5dd616a045c07d6bd2dfa86 \ - --hash=sha256:fe643af248442221db027da43ed43e53b73e11f40c9043738de9a2b4b6ca7697 +grpcio==1.59.0 \ + --hash=sha256:0ae444221b2c16d8211b55326f8ba173ba8f8c76349bfc1768198ba592b58f74 \ + --hash=sha256:0b84445fa94d59e6806c10266b977f92fa997db3585f125d6b751af02ff8b9fe \ + --hash=sha256:14890da86a0c0e9dc1ea8e90101d7a3e0e7b1e71f4487fab36e2bfd2ecadd13c \ + --hash=sha256:15f03bd714f987d48ae57fe092cf81960ae36da4e520e729392a59a75cda4f29 \ + --hash=sha256:1a839ba86764cc48226f50b924216000c79779c563a301586a107bda9cbe9dcf \ + --hash=sha256:225e5fa61c35eeaebb4e7491cd2d768cd8eb6ed00f2664fa83a58f29418b39fd \ + --hash=sha256:228b91ce454876d7eed74041aff24a8f04c0306b7250a2da99d35dd25e2a1211 \ + --hash=sha256:2ea95cd6abbe20138b8df965b4a8674ec312aaef3147c0f46a0bac661f09e8d0 \ + --hash=sha256:2f120d27051e4c59db2f267b71b833796770d3ea36ca712befa8c5fff5da6ebd \ + --hash=sha256:34341d9e81a4b669a5f5dca3b2a760b6798e95cdda2b173e65d29d0b16692857 \ + --hash=sha256:3859917de234a0a2a52132489c4425a73669de9c458b01c9a83687f1f31b5b10 \ + --hash=sha256:38823bd088c69f59966f594d087d3a929d1ef310506bee9e3648317660d65b81 \ + --hash=sha256:38da5310ef84e16d638ad89550b5b9424df508fd5c7b968b90eb9629ca9be4b9 \ + --hash=sha256:3b8ff795d35a93d1df6531f31c1502673d1cebeeba93d0f9bd74617381507e3f \ + --hash=sha256:50eff97397e29eeee5df106ea1afce3ee134d567aa2c8e04fabab05c79d791a7 \ + --hash=sha256:5711c51e204dc52065f4a3327dca46e69636a0b76d3e98c2c28c4ccef9b04c52 \ + --hash=sha256:598f3530231cf10ae03f4ab92d48c3be1fee0c52213a1d5958df1a90957e6a88 \ + --hash=sha256:611d9aa0017fa386809bddcb76653a5ab18c264faf4d9ff35cb904d44745f575 \ + --hash=sha256:61bc72a00ecc2b79d9695220b4d02e8ba53b702b42411397e831c9b0589f08a3 \ + --hash=sha256:63982150a7d598281fa1d7ffead6096e543ff8be189d3235dd2b5604f2c553e5 \ + --hash=sha256:6c4b1cc3a9dc1924d2eb26eec8792fedd4b3fcd10111e26c1d551f2e4eda79ce \ + --hash=sha256:81d86a096ccd24a57fa5772a544c9e566218bc4de49e8c909882dae9d73392df \ + --hash=sha256:849c47ef42424c86af069a9c5e691a765e304079755d5c29eff511263fad9c2a \ + --hash=sha256:871371ce0c0055d3db2a86fdebd1e1d647cf21a8912acc30052660297a5a6901 \ + --hash=sha256:8cd2d38c2d52f607d75a74143113174c36d8a416d9472415eab834f837580cf7 \ + --hash=sha256:936b2e04663660c600d5173bc2cc84e15adbad9c8f71946eb833b0afc205b996 \ + --hash=sha256:93e9cb546e610829e462147ce724a9cb108e61647a3454500438a6deef610be1 \ + --hash=sha256:956f0b7cb465a65de1bd90d5a7475b4dc55089b25042fe0f6c870707e9aabb1d \ + --hash=sha256:986de4aa75646e963466b386a8c5055c8b23a26a36a6c99052385d6fe8aaf180 \ + --hash=sha256:aca8a24fef80bef73f83eb8153f5f5a0134d9539b4c436a716256b311dda90a6 \ + --hash=sha256:acf70a63cf09dd494000007b798aff88a436e1c03b394995ce450be437b8e54f \ + --hash=sha256:b34c7a4c31841a2ea27246a05eed8a80c319bfc0d3e644412ec9ce437105ff6c \ + --hash=sha256:b95ec8ecc4f703f5caaa8d96e93e40c7f589bad299a2617bdb8becbcce525539 \ + --hash=sha256:ba0ca727a173ee093f49ead932c051af463258b4b493b956a2c099696f38aa66 \ + --hash=sha256:c041a91712bf23b2a910f61e16565a05869e505dc5a5c025d429ca6de5de842c \ + --hash=sha256:c0488c2b0528e6072010182075615620071371701733c63ab5be49140ed8f7f0 \ + --hash=sha256:c173a87d622ea074ce79be33b952f0b424fa92182063c3bda8625c11d3585d09 \ + --hash=sha256:c251d22de8f9f5cca9ee47e4bade7c5c853e6e40743f47f5cc02288ee7a87252 \ + --hash=sha256:c4dfdb49f4997dc664f30116af2d34751b91aa031f8c8ee251ce4dcfc11277b0 \ + --hash=sha256:ca87ee6183421b7cea3544190061f6c1c3dfc959e0b57a5286b108511fd34ff4 \ + --hash=sha256:ceb1e68135788c3fce2211de86a7597591f0b9a0d2bb80e8401fd1d915991bac \ + --hash=sha256:d09bd2a4e9f5a44d36bb8684f284835c14d30c22d8ec92ce796655af12163588 \ + --hash=sha256:d0fcf53df684fcc0154b1e61f6b4a8c4cf5f49d98a63511e3f30966feff39cd0 \ + --hash=sha256:d74f7d2d7c242a6af9d4d069552ec3669965b74fed6b92946e0e13b4168374f9 \ + --hash=sha256:de2599985b7c1b4ce7526e15c969d66b93687571aa008ca749d6235d056b7205 \ + --hash=sha256:e5378785dce2b91eb2e5b857ec7602305a3b5cf78311767146464bfa365fc897 \ + --hash=sha256:ec78aebb9b6771d6a1de7b6ca2f779a2f6113b9108d486e904bde323d51f5589 \ + --hash=sha256:f1feb034321ae2f718172d86b8276c03599846dc7bb1792ae370af02718f91c5 \ + --hash=sha256:f21917aa50b40842b51aff2de6ebf9e2f6af3fe0971c31960ad6a3a2b24988f4 \ + --hash=sha256:f367e4b524cb319e50acbdea57bb63c3b717c5d561974ace0b065a648bb3bad3 \ + --hash=sha256:f6cfe44a5d7c7d5f1017a7da1c8160304091ca5dc64a0f85bca0d63008c3137a \ + --hash=sha256:fa66cac32861500f280bb60fe7d5b3e22d68c51e18e65367e38f8669b78cea3b \ + --hash=sha256:fc8bf2e7bc725e76c0c11e474634a08c8f24bcf7426c0c6d60c8f9c6e70e4d4a \ + --hash=sha256:fe976910de34d21057bcb53b2c5e667843588b48bf11339da2a75f5c4c5b4055 # via # -r requirements.in # grpcio-tools -grpcio-tools==1.58.0 \ - --hash=sha256:1086fe240c4c879b9721952b47d46996deb283c2d9355a8dc24a804811aacf70 \ - --hash=sha256:149fb48f53cb691a6328f68bed8e4036c730f7106b7f98e92c2c0403f0b9e93c \ - --hash=sha256:1852e798f31e5437ca7b37abc910e028b34732fb19364862cedb87b1dab66fad \ - --hash=sha256:1cb6e24194786687d4f23c64de1f0ce553af51de22746911bc37340f85f9783e \ - --hash=sha256:28eefebddec3d3adf19baca78f8b82a2287d358e1b1575ae018cdca8eacc6269 \ - --hash=sha256:2c2221123d010dc6231799e63a37f2f4786bf614ef65b23009c387cd20d8b193 \ - --hash=sha256:2ef8c696e9d78676cc3f583a92bbbf2c84e94e350f7ad22f150a52559f4599d1 \ - --hash=sha256:32d51e933c3565414dd0835f930bb28a1cdeba435d9d2c87fa3cf8b1d284db3c \ - --hash=sha256:343f572312039059a8797d6e29a7fc62196e73131ab01755660a9d48202267c1 \ - --hash=sha256:3f8904ac7fc3da2e874f00b3a986e8b7e004f499344a8e7eb213c26dfb025041 \ - --hash=sha256:43cc23908b63fcaefe690b10f68a2d8652c994b5b36ab77d2271d9608c895320 \ - --hash=sha256:453023120114c35d3d9d6717ea0820e5d5c140f51f9d0b621de4397ff854471b \ - --hash=sha256:46628247fbce86d18232eead24bd22ed0826c79f3fe2fc2fbdbde45971361049 \ - --hash=sha256:4882382631e6352819059278a5c878ce0b067008dd490911d16d5616e8a36d85 \ - --hash=sha256:4be49ed320b0ebcbc21d19ef555fbf229c1c452105522b728e1171ee2052078e \ - --hash=sha256:4ee26e9253a721fff355737649678535f76cf5d642aa3ac0cd937832559b90af \ - --hash=sha256:51587842a54e025a3d0d37afcf4ef2b7ac1def9a5d17448665cb424b53d6c287 \ - --hash=sha256:579c11a9f198847ed48dbc4f211c67fe96a73320b87c81f01b044b72e24a7d77 \ - --hash=sha256:60c874908f3b40f32f1bb0221f7b3ab65ecb53a4d0a9f0a394f031f1b292c177 \ - --hash=sha256:6997511e9d2979f7a2389479682dbb06823f21a904e8fb0a5c6baaf1b4b4a863 \ - --hash=sha256:6997df6e7c5cf4d3ddc764240c1ff6a04b45d70ec28913b38fbc6396ef743e12 \ - --hash=sha256:6ca2fc1dd8049d417a5034d944c9df05cee76f855b3e431627ab4292e7c01c47 \ - --hash=sha256:6ec43909095c630df3e479e77469bdad367067431f4af602f6ccb978a3b78afd \ - --hash=sha256:6f4d80ceb591e31ca4dceec747dbe56132e1392a0a9bb1c8fe001d1b5cac898a \ - --hash=sha256:6f7144aad9396d35fb1b80429600a970b559c2ad4d07020eeb180fe83cea2bee \ - --hash=sha256:7371d8ea80234b29affec145e25569523f549520ed7e53b2aa92bed412cdecfd \ - --hash=sha256:85ac28a9621e9b92a3fc416288c4ce45542db0b4c31b3e23031dd8e0a0ec5590 \ - --hash=sha256:88e8191d0dd789bebf42533808728f5ce75d2c51e2a72bdf20abe5b5e3fbec42 \ - --hash=sha256:8ad9d77f25514584b1ddc981d70c9e50dfcfc388aa5ba943eee67520c5267ed9 \ - --hash=sha256:8de0b701da479643f71fad71fe66885cddd89441ae16e2c724939b47742dc72e \ - --hash=sha256:9aeb5949e46558d21c51fd3ec3eeecc59c94dbca76c67c0a80d3da6b7437930c \ - --hash=sha256:a062ae3072a2a39a3c057f4d68b57b021f1dd2956cd09aab39709f6af494e1de \ - --hash=sha256:a3dbece2a121761499a659b799979d4b738586d1065439053de553773eee11ca \ - --hash=sha256:a7ae3dca059d5b358dd03fb63277428fa7d771605d4074a019138dd38d70719a \ - --hash=sha256:aadbd8393ae332e49731adb31e741f2e689989150569b7acc939f5ea43124e2d \ - --hash=sha256:ac65b8d6e3acaf88b815edf9af88ff844b6600ff3d2591c05ba4f655b45d5fb4 \ - --hash=sha256:b63f823ac991ff77104da614d2a2485a59d37d57830eb2e387a6e2a3edc7fa2b \ - --hash=sha256:b6c896f1df99c35cf062d4803c15663ff00a33ff09add28baa6e475cf6b5e258 \ - --hash=sha256:b6ea5578712cdb29b0ff60bfc6405bf0e8d681b9c71d106dd1cda54fe7fe4e55 \ - --hash=sha256:ba3d383e5ca93826038b70f326fce8e8d12dd9b2f64d363a3d612f7475f12dd2 \ - --hash=sha256:c29880f491581c83181c0a84a4d11402af2b13166a5266f64e246adf1da7aa66 \ - --hash=sha256:cd7acfbb43b7338a78cf4a67528d05530d574d92b7c829d185b78dfc451d158f \ - --hash=sha256:d84091a189d848d94645b7c48b61734c12ec03b0d46e5fc0049343a26989ac5c \ - --hash=sha256:df2788736bdf58abe7b0e4d6b1ff806f7686c98c5ad900da312252e3322d91c4 \ - --hash=sha256:eec3c93a08df11c80ef1c29a616bcbb0d83dbc6ea41b48306fcacc720416dfa7 +grpcio-tools==1.59.0 \ + --hash=sha256:0548e901894399886ff4a4cd808cb850b60c021feb4a8977a0751f14dd7e55d9 \ + --hash=sha256:05bf7b3ed01c8a562bb7e840f864c58acedbd6924eb616367c0bd0a760bdf483 \ + --hash=sha256:1d551ff42962c7c333c3da5c70d5e617a87dee581fa2e2c5ae2d5137c8886779 \ + --hash=sha256:1df755951f204e65bf9232a9cac5afe7d6b8e4c87ac084d3ecd738fdc7aa4174 \ + --hash=sha256:204e08f807b1d83f5f0efea30c4e680afe26a43dec8ba614a45fa698a7ef0a19 \ + --hash=sha256:240a7a3c2c54f77f1f66085a635bca72003d02f56a670e7db19aec531eda8f78 \ + --hash=sha256:26eb2eebf150a33ebf088e67c1acf37eb2ac4133d9bfccbaa011ad2148c08b42 \ + --hash=sha256:27a7f226b741b2ebf7e2d0779d2c9b17f446d1b839d59886c1619e62cc2ae472 \ + --hash=sha256:2d970aa26854f535ffb94ea098aa8b43de020d9a14682e4a15dcdaeac7801b27 \ + --hash=sha256:2ee960904dde12a7fa48e1591a5b3eeae054bdce57bacf9fd26685a98138f5bf \ + --hash=sha256:335e2f355a0c544a88854e2c053aff8a3f398b84a263a96fa19d063ca1fe513a \ + --hash=sha256:387662bee8e4c0b52cc0f61eaaca0ca583f5b227103f685b76083a3590a71a3e \ + --hash=sha256:40cbf712769242c2ba237745285ef789114d7fcfe8865fc4817d87f20015e99a \ + --hash=sha256:4499d4bc5aa9c7b645018d8b0db4bebd663d427aabcd7bee7777046cb1bcbca7 \ + --hash=sha256:498e7be0b14385980efa681444ba481349c131fc5ec88003819f5d929646947c \ + --hash=sha256:4a10e59cca462208b489478340b52a96d64e8b8b6f1ac097f3e8cb211d3f66c0 \ + --hash=sha256:4ee443abcd241a5befb05629013fbf2eac637faa94aaa3056351aded8a31c1bc \ + --hash=sha256:51d9595629998d8b519126c5a610f15deb0327cd6325ed10796b47d1d292e70b \ + --hash=sha256:520c0c83ea79d14b0679ba43e19c64ca31d30926b26ad2ca7db37cbd89c167e2 \ + --hash=sha256:5b2d6da553980c590487f2e7fd3ec9c1ad8805ff2ec77977b92faa7e3ca14e1f \ + --hash=sha256:6119f62c462d119c63227b9534210f0f13506a888151b9bf586f71e7edf5088b \ + --hash=sha256:6aec8a4ed3808b7dfc1276fe51e3e24bec0eeaf610d395bcd42934647cf902a3 \ + --hash=sha256:71cc6db1d66da3bc3730d9937bddc320f7b1f1dfdff6342bcb5741515fe4110b \ + --hash=sha256:784aa52965916fec5afa1a28eeee6f0073bb43a2a1d7fedf963393898843077a \ + --hash=sha256:821dba464d84ebbcffd9d420302404db2fa7a40c7ff4c4c4c93726f72bfa2769 \ + --hash=sha256:868892ad9e00651a38dace3e4924bae82fc4fd4df2c65d37b74381570ee8deb1 \ + --hash=sha256:882b809b42b5464bee55288f4e60837297f9618e53e69ae3eea6d61b05ce48fa \ + --hash=sha256:8c4634b3589efa156a8d5860c0a2547315bd5c9e52d14c960d716fe86e0927be \ + --hash=sha256:8f0da5861ee276ca68493b217daef358960e8527cc63c7cb292ca1c9c54939af \ + --hash=sha256:962d1a3067129152cee3e172213486cb218a6bad703836991f46f216caefcf00 \ + --hash=sha256:99b3bde646720bbfb77f263f5ba3e1a0de50632d43c38d405a0ef9c7e94373cd \ + --hash=sha256:9af7e138baa9b2895cf1f3eb718ac96fc5ae2f8e31fca405e21e0e5cd1643c52 \ + --hash=sha256:9ed05197c5ab071e91bcef28901e97ca168c4ae94510cb67a14cb4931b94255a \ + --hash=sha256:9fc02a6e517c34dcf885ff3b57260b646551083903e3d2c780b4971ce7d4ab7c \ + --hash=sha256:a4f6cae381f21fee1ef0a5cbbbb146680164311157ae618edf3061742d844383 \ + --hash=sha256:aa4018f2d8662ac4d9830445d3d253a11b3e096e8afe20865547137aa1160e93 \ + --hash=sha256:b519f2ecde9a579cad2f4a7057d5bb4e040ad17caab8b5e691ed7a13b9db0be9 \ + --hash=sha256:b8e95d921cc2a1521d4750eedefec9f16031457920a6677edebe9d1b2ad6ae60 \ + --hash=sha256:bb87158dbbb9e5a79effe78d54837599caa16df52d8d35366e06a91723b587ae \ + --hash=sha256:bfa4b2b7d21c5634b62e5f03462243bd705adc1a21806b5356b8ce06d902e160 \ + --hash=sha256:c683be38a9bf4024c223929b4cd2f0a0858c94e9dc8b36d7eaa5a48ce9323a6f \ + --hash=sha256:cb63055739808144b541986291679d643bae58755d0eb082157c4d4c04443905 \ + --hash=sha256:d0f0806de1161c7f248e4c183633ee7a58dfe45c2b77ddf0136e2e7ad0650b1b \ + --hash=sha256:db030140d0da2368319e2f23655df3baec278c7e0078ecbe051eaf609a69382c \ + --hash=sha256:de156c18b0c638aaee3be6ad650c8ba7dec94ed4bac26403aec3dce95ffe9407 \ + --hash=sha256:df85096fcac7cea8aa5bd84b7a39c4cdbf556b93669bb4772eb96aacd3222a4e \ + --hash=sha256:e312ddc2d8bec1a23306a661ad52734f984c9aad5d8f126ebb222a778d95407d \ + --hash=sha256:eeed386971bb8afc3ec45593df6a1154d680d87be1209ef8e782e44f85f47e64 \ + --hash=sha256:ef3e8aca2261f7f07436d4e2111556c1fb9bf1f9cfcdf35262743ccdee1b6ce9 \ + --hash=sha256:f14a6e4f700dfd30ff8f0e6695f944affc16ae5a1e738666b3fae4e44b65637e \ + --hash=sha256:f1c684c0d9226d04cadafced620a46ab38c346d0780eaac7448da96bf12066a3 \ + --hash=sha256:f381ae3ad6a5eb27aad8d810438937d8228977067c54e0bd456fce7e11fdbf3d \ + --hash=sha256:f6263b85261b62471cb97b7505df72d72b8b62e5e22d8184924871a6155b4dbf \ + --hash=sha256:f965707da2b48a33128615bcfebedd215a3a30e346447e885bb3da37a143177a # via -r requirements.in idna==3.2 \ --hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \ --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3 # via requests -protobuf==4.24.3 \ - --hash=sha256:067f750169bc644da2e1ef18c785e85071b7c296f14ac53e0900e605da588719 \ - --hash=sha256:12e9ad2ec079b833176d2921be2cb24281fa591f0b119b208b788adc48c2561d \ - --hash=sha256:1b182c7181a2891e8f7f3a1b5242e4ec54d1f42582485a896e4de81aa17540c2 \ - --hash=sha256:20651f11b6adc70c0f29efbe8f4a94a74caf61b6200472a9aea6e19898f9fcf4 \ - --hash=sha256:2da777d34b4f4f7613cdf85c70eb9a90b1fbef9d36ae4a0ccfe014b0b07906f1 \ - --hash=sha256:3d42e9e4796a811478c783ef63dc85b5a104b44aaaca85d4864d5b886e4b05e3 \ - --hash=sha256:6e514e8af0045be2b56e56ae1bb14f43ce7ffa0f68b1c793670ccbe2c4fc7d2b \ - --hash=sha256:b0271a701e6782880d65a308ba42bc43874dabd1a0a0f41f72d2dac3b57f8e76 \ - --hash=sha256:ba53c2f04798a326774f0e53b9c759eaef4f6a568ea7072ec6629851c8435959 \ - --hash=sha256:e29d79c913f17a60cf17c626f1041e5288e9885c8579832580209de8b75f2a52 \ - --hash=sha256:f631bb982c5478e0c1c70eab383af74a84be66945ebf5dd6b06fc90079668d0b \ - --hash=sha256:f6ccbcf027761a2978c1406070c3788f6de4a4b2cc20800cc03d52df716ad675 \ - --hash=sha256:f6f8dc65625dadaad0c8545319c2e2f0424fede988368893ca3844261342c11a +protobuf==4.24.4 \ + --hash=sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe \ + --hash=sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085 \ + --hash=sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b \ + --hash=sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667 \ + --hash=sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37 \ + --hash=sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92 \ + --hash=sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4 \ + --hash=sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b \ + --hash=sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9 \ + --hash=sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd \ + --hash=sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e \ + --hash=sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46 \ + --hash=sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb # via # -r requirements.in # grpcio-tools @@ -133,7 +151,7 @@ requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via -r requirements.in -urllib3==1.26.7 \ - --hash=sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece \ - --hash=sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844 +urllib3==1.26.17 \ + --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \ + --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b # via requests diff --git a/examples/mysql/Dockerfile-mysql b/examples/mysql/Dockerfile-mysql index 275fc303cdeb..57e78b33da18 100644 --- a/examples/mysql/Dockerfile-mysql +++ b/examples/mysql/Dockerfile-mysql @@ -1 +1 @@ -FROM mysql:8.1.0@sha256:566007208a3f1cc8f9df6b767665b5c9b800fc4fb5f863d17aa1df362880ed04 +FROM mysql:8.1.0@sha256:44056c45e214c26c37b6f244534c6fb5f8a40eacbc28e870a2652b19d7a8a814 diff --git a/examples/redis/Dockerfile-redis b/examples/redis/Dockerfile-redis index 248ced0320d3..e5c869b218dd 100644 --- a/examples/redis/Dockerfile-redis +++ b/examples/redis/Dockerfile-redis @@ -1 +1 @@ -FROM redis@sha256:ae51486efeea8a9b3f85542e408f79a5012d5b7fa35ae19733104ecc6992a248 +FROM redis@sha256:b68c6efe2c5f2d7d7d14a2749f66d6d81645ec0cacb92572b2fb7d5c42c82031 diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index e92f131e8ccc..ec7f1899f102 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.7-bullseye-slim@sha256:86ed0f70880231adc0fb66c2edbba5de350d8587999e2fe4e1f59c11a4cbb3b4 as node-base +FROM node:20.8-bullseye-slim@sha256:ae31e40fdecf15751ee23055b60717e2ce6e03acc4ee7ffd8f87e76813d8010f as node-base FROM node-base as node-http-auth diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index a84f4e37597b..63ac4fd3039d 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:379b7a1223b394106cc20d18a5177ed77738003416057e8898cde10e6b7a082a +FROM postgres:latest@sha256:f1aaf6f8be5552bef66c5580efbd2942c37d7277cd0416ef4939fa34bf0baf31 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 5c697831d6ce..792856d0959c 100644 --- a/examples/shared/python/Dockerfile +++ b/examples/shared/python/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.5-slim-bullseye@sha256:9f35f3a6420693c209c11bba63dcf103d88e47ebe0b205336b5168c122967edf as python-base +FROM python:3.12.0-slim-bullseye@sha256:8c5ca5725a49e3ab3a1e76bd7e93fff1aeec2fdfd91288627f4510eea5a72e09 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 3bcd67470c03..272d6720b807 100644 --- a/examples/shared/python/postgres/requirements.txt +++ b/examples/shared/python/postgres/requirements.txt @@ -4,65 +4,74 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -psycopg2-binary==2.9.8 \ - --hash=sha256:01f9731761f711e42459f87bd2ad5d744b9773b5dd05446f3b579a0f077e78e3 \ - --hash=sha256:0e3071c947bda6afc6fe2e7b64ebd64fb2cad1bc0e705a3594cb499291f2dfec \ - --hash=sha256:14f85ff2d5d826a7ce9e6c31e803281ed5a096789f47f52cb728c88f488de01b \ - --hash=sha256:15458c81b0d199ab55825007115f697722831656e6477a427783fe75c201c82b \ - --hash=sha256:19d40993701e39c49b50e75cd690a6af796d7e7210941ee0fe49cf12b25840e5 \ - --hash=sha256:1d669887df169a9b0c09e0f5b46891511850a9ddfcde3593408af9d9774c5c3a \ - --hash=sha256:1dbad789ebd1e61201256a19dc2e90fed4706bc966ccad4f374648e5336b1ab4 \ - --hash=sha256:1f279ba74f0d6b374526e5976c626d2ac3b8333b6a7b08755c513f4d380d3add \ - --hash=sha256:205cecdd81ff4f1ddd687ce7d06879b9b80cccc428d8d6ebf36fcba08bb6d361 \ - --hash=sha256:278ebd63ced5a5f3af5394cb75a9a067243eee21f42f0126c6f1cf85eaeb90f9 \ - --hash=sha256:3723c3f009e2b2771f2491b330edb7091846f1aad0c08fbbd9a1383d6a0c0841 \ - --hash=sha256:395c217156723fe21809dfe8f7a433c5bf8e9bce229944668e4ec709c37c5442 \ - --hash=sha256:3ae22a0fa5c516b84ddb189157fabfa3f12eded5d630e1ce260a18e1771f8707 \ - --hash=sha256:3b6928a502af71ca2ac9aad535e78c8309892ed3bfa7933182d4c760580c8af4 \ - --hash=sha256:3b6c607ecb6a9c245ebe162d63ccd9222d38efa3c858bbe38d32810b08b8f87e \ - --hash=sha256:3fd44b52bc9c74c1512662e8da113a1c55127adeeacebaf460babe766517b049 \ - --hash=sha256:4336afc0e81726350bd5863e3c3116d8c12aa7f457d3d0b3b3dc36137fec6feb \ - --hash=sha256:4879ee1d07a6b2c232ae6a74570f4788cd7a29b3cd38bc39bf60225b1d075c78 \ - --hash=sha256:4960c881471ca710b81a67ef148c33ee121c1f8e47a639cf7e06537fe9fee337 \ - --hash=sha256:4b8b2cdf3bce4dd91dc035fbff4eb812f5607dda91364dc216b0920b97b521c7 \ - --hash=sha256:4bfabbd7e70785af726cc0209e8e64b926abf91741eca80678b221aad9e72135 \ - --hash=sha256:4d6b592ecc8667e608b9e7344259fbfb428cc053df0062ec3ac75d8270cd5a9f \ - --hash=sha256:5262713988d97a9d4cd54b682dec4a413b87b76790e5b16f480450550d11a8f7 \ - --hash=sha256:54bf5c27bd5867a5fa5341fad29f0d5838e2fed617ef5346884baf8b8b16dd82 \ - --hash=sha256:565edaf9f691b17a7fdbabd368b5b3e67d0fdc8f7f6b52177c1d3289f4e763fd \ - --hash=sha256:59421806c1a0803ea7de9ed061d656c041a84db0da7e73266b98db4c7ba263da \ - --hash=sha256:59f45cca0765aabb52a5822c72d5ff2ec46a28b1c1702de90dc0d306ec5c2001 \ - --hash=sha256:5a0a6e4004697ec98035ff3b8dfc4dba8daa477b23ee891d831cd3cd65ace6be \ - --hash=sha256:5aa0c99c12075c593dcdccbb8a7aaa714b716560cc99ef9206f9e75b77520801 \ - --hash=sha256:5aef3296d44d05805e634dbbd2972aa8eb7497926dd86047f5e39a79c3ecc086 \ - --hash=sha256:5cbe1e19f59950afd66764e3c905ecee9f2aee9f8df2ef35af6f7948ad93f620 \ - --hash=sha256:5debcb23a052f3fb4c165789ea513b562b2fac0f0f4f53eaf3cf4dc648907ff8 \ - --hash=sha256:5f955fe6301b84b6fd13970a05f3640fbb62ca3a0d19342356585006c830e038 \ - --hash=sha256:6369f4bd4d27944498094dccced1ae7ca43376a59dbfe4c8b6a16e9e3dc3ccce \ - --hash=sha256:63ce1dccfd08d9c5341ac82d62aa04345bc4bf41b5e5b7b2c6c172a28e0eda27 \ - --hash=sha256:65403113ac3a4813a1409fb6a1e43c658b459cc8ed8afcc5f4baf02ec8be4334 \ - --hash=sha256:673eafbdaa4ed9f5164c90e191c3895cc5f866b9b379fdb59f3a2294e914d9bd \ - --hash=sha256:693a4e7641556f0b421a7d6c6a74058aead407d860ac1cb9d0bf25be0ca73de8 \ - --hash=sha256:6e1bb4eb0d9925d65dabaaabcbb279fab444ba66d73f86d4c07dfd11f0139c06 \ - --hash=sha256:6f5e70e40dae47a4dc7f8eb390753bb599b0f4ede314580e6faa3b7383695d19 \ - --hash=sha256:80451e6b6b7c486828d5c7ed50769532bbb04ec3a411f1e833539d5c10eb691c \ - --hash=sha256:8c84ff9682bc4520504c474e189b3de7c4a4029e529c8b775e39c95c33073767 \ - --hash=sha256:91719f53ed2a95ebecefac48d855d811cba9d9fe300acc162993bdfde9bc1c3b \ - --hash=sha256:9a971086db0069aef2fd22ccffb670baac427f4ee2174c4f5c7206254f1e6794 \ - --hash=sha256:aeb09db95f38e75ae04e947d283e07be34d03c4c2ace4f0b73dbb9143d506e67 \ - --hash=sha256:c68a2e1afb4f2a5bb4b7bb8f90298d21196ac1c66418523e549430b8c4b7cb1e \ - --hash=sha256:c7ff2b6a79a92b1b169b03bb91b41806843f0cdf6055256554495bffed1d496d \ - --hash=sha256:ccaa2ae03990cedde1f618ff11ec89fefa84622da73091a67b44553ca8be6711 \ - --hash=sha256:cf60c599c40c266a01c458e9c71db7132b11760f98f08233f19b3e0a2153cbf1 \ - --hash=sha256:d29efab3c5d6d978115855a0f2643e0ee8c6450dc536d5b4afec6f52ab99e99e \ - --hash=sha256:d4a19a3332f2ac6d093e60a6f1c589f97eb9f9de7e27ea80d67f188384e31572 \ - --hash=sha256:dc145a241e1f6381efb924bcf3e3462d6020b8a147363f9111eb0a9c89331ad7 \ - --hash=sha256:de85105c568dc5f0f0efe793209ba83e4675d53d00faffc7a7c7a8bea9e0e19a \ - --hash=sha256:e11373d8e4f1f46cf3065bf613f0df9854803dc95aa4a35354ffac19f8c52127 \ - --hash=sha256:e271ad6692d50d70ca75db3bd461bfc26316de78de8fe1f504ef16dcea8f2312 \ - --hash=sha256:e3142c7e51b92855cff300580de949e36a94ab3bfa8f353b27fe26535e9b3542 \ - --hash=sha256:e46b0f4683539965ce849f2c13fc53e323bb08d84d4ba2e4b3d976f364c84210 \ - --hash=sha256:e6ef615d48fa60361e57f998327046bd89679c25d06eee9e78156be5a7a76e03 \ - --hash=sha256:e7bdc94217ae20ad03b375a991e107a31814053bee900ad8c967bf82ef3ff02e \ - --hash=sha256:fc37de7e3a87f5966965fc874d33c9b68d638e6c3718fdf32a5083de563428b0 +psycopg2-binary==2.9.9 \ + --hash=sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9 \ + --hash=sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77 \ + --hash=sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e \ + --hash=sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84 \ + --hash=sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3 \ + --hash=sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2 \ + --hash=sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67 \ + --hash=sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876 \ + --hash=sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152 \ + --hash=sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f \ + --hash=sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a \ + --hash=sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6 \ + --hash=sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503 \ + --hash=sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f \ + --hash=sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493 \ + --hash=sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996 \ + --hash=sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f \ + --hash=sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e \ + --hash=sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59 \ + --hash=sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94 \ + --hash=sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7 \ + --hash=sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682 \ + --hash=sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420 \ + --hash=sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae \ + --hash=sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291 \ + --hash=sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe \ + --hash=sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980 \ + --hash=sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692 \ + --hash=sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119 \ + --hash=sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716 \ + --hash=sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472 \ + --hash=sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b \ + --hash=sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2 \ + --hash=sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc \ + --hash=sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c \ + --hash=sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5 \ + --hash=sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984 \ + --hash=sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9 \ + --hash=sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf \ + --hash=sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0 \ + --hash=sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f \ + --hash=sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212 \ + --hash=sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb \ + --hash=sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be \ + --hash=sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90 \ + --hash=sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041 \ + --hash=sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7 \ + --hash=sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860 \ + --hash=sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245 \ + --hash=sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27 \ + --hash=sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417 \ + --hash=sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359 \ + --hash=sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202 \ + --hash=sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0 \ + --hash=sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7 \ + --hash=sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba \ + --hash=sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1 \ + --hash=sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd \ + --hash=sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07 \ + --hash=sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98 \ + --hash=sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55 \ + --hash=sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d \ + --hash=sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972 \ + --hash=sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f \ + --hash=sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e \ + --hash=sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26 \ + --hash=sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957 \ + --hash=sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53 \ + --hash=sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52 # via -r requirements.in diff --git a/mobile/Gemfile b/mobile/Gemfile deleted file mode 100644 index d1bf7c6fbeb0..000000000000 --- a/mobile/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem "cocoapods" diff --git a/mobile/Gemfile.lock b/mobile/Gemfile.lock deleted file mode 100644 index 31a996d9878e..000000000000 --- a/mobile/Gemfile.lock +++ /dev/null @@ -1,99 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.5) - rexml - activesupport (6.1.7.3) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - claide (1.1.0) - cocoapods (1.11.3) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) - addressable (~> 2.8) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix (~> 4.0) - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.6.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored2 (3.1.2) - concurrent-ruby (1.2.2) - escape (0.0.4) - ethon (0.15.0) - ffi (>= 1.15.0) - ffi (1.15.5) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - json (2.6.1) - minitest (5.18.0) - molinillo (0.8.0) - nanaimo (0.3.0) - nap (1.1.0) - netrc (0.11.0) - public_suffix (4.0.7) - rexml (3.2.5) - ruby-macho (2.5.1) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - xcodeproj (1.21.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - zeitwerk (2.6.7) - -PLATFORMS - arm64-darwin-21 - x86_64-darwin-19 - x86_64-linux - -DEPENDENCIES - cocoapods - -BUNDLED WITH - 2.3.8 diff --git a/mobile/examples/kotlin/hello_world/AsyncDemoFilter.kt b/mobile/examples/kotlin/hello_world/AsyncDemoFilter.kt index a45ec5f7af1d..1436a6b6a8e7 100644 --- a/mobile/examples/kotlin/hello_world/AsyncDemoFilter.kt +++ b/mobile/examples/kotlin/hello_world/AsyncDemoFilter.kt @@ -30,9 +30,7 @@ class AsyncDemoFilter : AsyncResponseFilter { ): FilterHeadersStatus { // If this is the end of the stream, asynchronously resume response processing via callback. if (endStream) { - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } } return FilterHeadersStatus.StopIteration() } @@ -44,9 +42,7 @@ class AsyncDemoFilter : AsyncResponseFilter { ): FilterDataStatus { // If this is the end of the stream, asynchronously resume response processing via callback. if (endStream) { - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } } return FilterDataStatus.StopIterationAndBuffer() } @@ -56,9 +52,7 @@ class AsyncDemoFilter : AsyncResponseFilter { streamIntel: StreamIntel ): FilterTrailersStatus { // Trailers imply end of stream, so asynchronously resume response processing via callbacka - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } return FilterTrailersStatus.StopIteration() } @@ -73,23 +67,14 @@ class AsyncDemoFilter : AsyncResponseFilter { endStream: Boolean, streamIntel: StreamIntel ): FilterResumeStatus { - val builder = headers!!.toResponseHeadersBuilder() - .add("async-filter-demo", "1") + val builder = headers!!.toResponseHeadersBuilder().add("async-filter-demo", "1") return FilterResumeStatus.ResumeIteration(builder.build(), data, trailers) } @Suppress("EmptyFunctionBlock") - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { - } + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onCancel(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onCancel(finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/examples/kotlin/hello_world/BufferDemoFilter.kt b/mobile/examples/kotlin/hello_world/BufferDemoFilter.kt index f158d1b1e65a..f37c330e357e 100644 --- a/mobile/examples/kotlin/hello_world/BufferDemoFilter.kt +++ b/mobile/examples/kotlin/hello_world/BufferDemoFilter.kt @@ -13,8 +13,7 @@ import java.nio.ByteBuffer /** * Example of a more complex HTTP filter that pauses processing on the response filter chain, - * buffers until the response is complete, then resumes filter iteration while setting a new - * header. + * buffers until the response is complete, then resumes filter iteration while setting a new header. */ class BufferDemoFilter : ResponseFilter { private lateinit var headers: ResponseHeaders @@ -39,8 +38,7 @@ class BufferDemoFilter : ResponseFilter { // If this is the end of the stream, resume processing of the (now fully-buffered) response. if (endStream) { - val builder = headers.toResponseHeadersBuilder() - .add("buffer-filter-demo", "1") + val builder = headers.toResponseHeadersBuilder().add("buffer-filter-demo", "1") return FilterDataStatus.ResumeIteration(builder.build(), body) } return FilterDataStatus.StopIterationAndBuffer() @@ -51,23 +49,14 @@ class BufferDemoFilter : ResponseFilter { streamIntel: StreamIntel ): FilterTrailersStatus { // Trailers imply end of stream; resume processing of the (now fully-buffered) response. - val builder = headers.toResponseHeadersBuilder() - .add("buffer-filter-demo", "1") + val builder = headers.toResponseHeadersBuilder().add("buffer-filter-demo", "1") return FilterTrailersStatus.ResumeIteration(builder.build(), this.body, trailers) } @Suppress("EmptyFunctionBlock") - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { - } + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onCancel(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onCancel(finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/examples/kotlin/hello_world/DemoFilter.kt b/mobile/examples/kotlin/hello_world/DemoFilter.kt index 4f8244499e5e..d7ebf70c2193 100644 --- a/mobile/examples/kotlin/hello_world/DemoFilter.kt +++ b/mobile/examples/kotlin/hello_world/DemoFilter.kt @@ -12,9 +12,7 @@ import io.envoyproxy.envoymobile.ResponseTrailers import io.envoyproxy.envoymobile.StreamIntel import java.nio.ByteBuffer -/** - * A filter implemented as a simple example of Envoy response filter. - */ +/** A filter implemented as a simple example of Envoy response filter. */ class DemoFilter : ResponseFilter { override fun onResponseHeaders( headers: ResponseHeaders, @@ -44,10 +42,7 @@ class DemoFilter : ResponseFilter { return FilterTrailersStatus.Continue(trailers) } - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) { Log.d("DemoFilter", "On error!") } @@ -55,7 +50,5 @@ class DemoFilter : ResponseFilter { Log.d("DemoFilter", "On cancel!") } - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/examples/kotlin/hello_world/MainActivity.kt b/mobile/examples/kotlin/hello_world/MainActivity.kt index ee7199fd67c6..c117ddb87304 100644 --- a/mobile/examples/kotlin/hello_world/MainActivity.kt +++ b/mobile/examples/kotlin/hello_world/MainActivity.kt @@ -25,17 +25,16 @@ private const val REQUEST_HANDLER_THREAD_NAME = "hello_envoy_kt" private const val REQUEST_AUTHORITY = "api.lyft.com" private const val REQUEST_PATH = "/ping" private const val REQUEST_SCHEME = "https" -private val FILTERED_HEADERS = setOf( - "server", - "filter-demo", - "buffer-filter-demo", - "async-filter-demo", - "x-envoy-upstream-service-time" -) +private val FILTERED_HEADERS = + setOf( + "server", + "filter-demo", + "buffer-filter-demo", + "async-filter-demo", + "x-envoy-upstream-service-time" + ) -/** - * The main activity of the app. - */ +/** The main activity of the app. */ class MainActivity : Activity() { private val thread = HandlerThread(REQUEST_HANDLER_THREAD_NAME) private lateinit var recyclerView: RecyclerView @@ -47,33 +46,34 @@ class MainActivity : Activity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - engine = AndroidEngineBuilder(application) - .addLogLevel(LogLevel.DEBUG) - .enableProxying(true) - .addPlatformFilter(::DemoFilter) - .addPlatformFilter(::BufferDemoFilter) - .addPlatformFilter(::AsyncDemoFilter) - .addNativeFilter("envoy.filters.http.buffer", "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}") - .addStringAccessor("demo-accessor", { "PlatformString" }) - .setOnEngineRunning { Log.d("MainActivity", "Envoy async internal setup completed") } - .setEventTracker({ - for (entry in it.entries) { - Log.d("MainActivity", "Event emitted: ${entry.key}, ${entry.value}") - } - }) - .setLogger { - Log.d("MainActivity", it) - } - .build() + engine = + AndroidEngineBuilder(application) + .addLogLevel(LogLevel.DEBUG) + .enableProxying(true) + .addPlatformFilter(::DemoFilter) + .addPlatformFilter(::BufferDemoFilter) + .addPlatformFilter(::AsyncDemoFilter) + .addNativeFilter( + "envoy.filters.http.buffer", + "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}" + ) + .addStringAccessor("demo-accessor", { "PlatformString" }) + .setOnEngineRunning { Log.d("MainActivity", "Envoy async internal setup completed") } + .setEventTracker({ + for (entry in it.entries) { + Log.d("MainActivity", "Event emitted: ${entry.key}, ${entry.value}") + } + }) + .setLogger { Log.d("MainActivity", it) } + .build() recyclerView = findViewById(R.id.recycler_view) as RecyclerView recyclerView.layoutManager = LinearLayoutManager(this) viewAdapter = ResponseRecyclerViewAdapter() recyclerView.adapter = viewAdapter - val dividerItemDecoration = DividerItemDecoration( - recyclerView.context, DividerItemDecoration.VERTICAL - ) + val dividerItemDecoration = + DividerItemDecoration(recyclerView.context, DividerItemDecoration.VERTICAL) recyclerView.addItemDecoration(dividerItemDecoration) thread.start() val handler = Handler(thread.looper) @@ -106,10 +106,9 @@ class MainActivity : Activity() { // Note: this request will use an h2 stream for the upstream request due to https scheme. // The Java example uses http so http/1.1. This is done on purpose to test both paths in // end-to-end tests in CI. - val requestHeaders = RequestHeadersBuilder( - RequestMethod.GET, REQUEST_SCHEME, REQUEST_AUTHORITY, REQUEST_PATH - ) - .build() + val requestHeaders = + RequestHeadersBuilder(RequestMethod.GET, REQUEST_SCHEME, REQUEST_AUTHORITY, REQUEST_PATH) + .build() engine .streamClient() .newStreamPrototype() diff --git a/mobile/examples/kotlin/shared/Response.kt b/mobile/examples/kotlin/shared/Response.kt index 8d205c2177ab..5aa9953af3ca 100644 --- a/mobile/examples/kotlin/shared/Response.kt +++ b/mobile/examples/kotlin/shared/Response.kt @@ -2,10 +2,11 @@ package io.envoyproxy.envoymobile.shared // Response is a class to handle HTTP responses. sealed class Response { - fun fold(success: (Success) -> Unit, failure: (Failure) -> Unit) = when (this) { - is Success -> success(this) - is Failure -> failure(this) - } + fun fold(success: (Success) -> Unit, failure: (Failure) -> Unit) = + when (this) { + is Success -> success(this) + is Failure -> failure(this) + } } data class Success(val title: String, val header: String) : Response() diff --git a/mobile/examples/kotlin/shared/ResponseViewHolder.kt b/mobile/examples/kotlin/shared/ResponseViewHolder.kt index 325f843b6bfd..87b6f06aa396 100644 --- a/mobile/examples/kotlin/shared/ResponseViewHolder.kt +++ b/mobile/examples/kotlin/shared/ResponseViewHolder.kt @@ -5,27 +5,26 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView class ResponseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - private val countTextView: TextView = itemView - .findViewById(R.id.response_text_view_count) as TextView - private val responseTextView: TextView = itemView - .findViewById(R.id.response_text_view) as TextView - private val headerTextView: TextView = itemView - .findViewById(R.id.header_text_view) as TextView + private val countTextView: TextView = + itemView.findViewById(R.id.response_text_view_count) as TextView + private val responseTextView: TextView = + itemView.findViewById(R.id.response_text_view) as TextView + private val headerTextView: TextView = itemView.findViewById(R.id.header_text_view) as TextView fun setResult(count: Int, response: Response) { countTextView.text = count.toString() response.fold( { success -> - responseTextView.text = responseTextView.resources - .getString(R.string.title_string, success.title) - headerTextView.text = headerTextView.resources - .getString(R.string.header_string, success.header) + responseTextView.text = + responseTextView.resources.getString(R.string.title_string, success.title) + headerTextView.text = + headerTextView.resources.getString(R.string.header_string, success.header) headerTextView.visibility = View.VISIBLE itemView.setBackgroundResource(R.color.success_color) }, { failure -> - responseTextView.text = responseTextView.resources - .getString(R.string.title_string, failure.message) + responseTextView.text = + responseTextView.resources.getString(R.string.title_string, failure.message) headerTextView.visibility = View.GONE itemView.setBackgroundResource(R.color.failed_color) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt index 58145ec68f5f..9d07422f1729 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt @@ -3,14 +3,11 @@ package io.envoyproxy.envoymobile import android.content.Context import io.envoyproxy.envoymobile.engine.AndroidEngineImpl - -/** - * The engine builder to use to create Envoy engine on Android. - */ -class AndroidEngineBuilder @JvmOverloads constructor( - context: Context, - baseConfiguration: BaseConfiguration = Standard() -) : EngineBuilder(baseConfiguration) { +/** The engine builder to use to create Envoy engine on Android. */ +class AndroidEngineBuilder +@JvmOverloads +constructor(context: Context, baseConfiguration: BaseConfiguration = Standard()) : + EngineBuilder(baseConfiguration) { init { addEngineType { AndroidEngineImpl(context, onEngineRunning, logger, eventTracker, enableProxying) diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/Engine.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/Engine.kt index 18dcab9d5ae2..311f630fd6fa 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/Engine.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/Engine.kt @@ -6,36 +6,29 @@ package io.envoyproxy.envoymobile */ interface Engine { - /** - * @return a {@link StreamClient} for opening and managing HTTP streams. - */ + /** @return a {@link StreamClient} for opening and managing HTTP streams. */ fun streamClient(): StreamClient - /** - * @return a {@link PulseClient} for recording time series metrics. - */ + /** @return a {@link PulseClient} for recording time series metrics. */ fun pulseClient(): PulseClient - /** - * Terminates the running engine. - */ + /** Terminates the running engine. */ fun terminate() /** - * Flush the stats sinks outside of a flushing interval. - * Note: stat flushing is done asynchronously, this function will never block. - * This is a noop if called before the underlying EnvoyEngine has started. + * Flush the stats sinks outside of a flushing interval. Note: stat flushing is done + * asynchronously, this function will never block. This is a noop if called before the underlying + * EnvoyEngine has started. */ fun flushStats() /** * Retrieve the value of all active stats. Note that this function may block for some time. + * * @return The list of active stats and their values, or empty string of the operation failed */ fun dumpStats(): String - /** - * Refresh DNS, and drain connections owned by this Engine. - */ + /** Refresh DNS, and drain connections owned by this Engine. */ fun resetConnectivityState() } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt index d17c2cf7725f..b7cfbe5cc3e4 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -10,14 +10,10 @@ import io.envoyproxy.envoymobile.engine.types.EnvoyKeyValueStore import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor import java.util.UUID -/** - * Envoy engine configuration. - */ +/** Envoy engine configuration. */ sealed class BaseConfiguration -/** - * The standard configuration. - */ +/** The standard configuration. */ class Standard : BaseConfiguration() /** @@ -28,16 +24,13 @@ class Standard : BaseConfiguration() class Custom(val yaml: String) : BaseConfiguration() /** - * Builder for generating the xDS configuration for the Envoy Mobile engine. - * xDS is a protocol for dynamic configuration of Envoy instances, more information can be found in - * https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. - * - * This class is typically used as input to the EngineBuilder's setXds() method. + * Builder for generating the xDS configuration for the Envoy Mobile engine. xDS is a protocol for + * dynamic configuration of Envoy instances, more information can be found in + * https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. + * + * This class is typically used as input to the EngineBuilder's setXds() method. */ -open class XdsBuilder ( - internal val xdsServerAddress: String, - internal val xdsServerPort: Int -) { +open class XdsBuilder(internal val xdsServerAddress: String, internal val xdsServerPort: Int) { companion object { private const val DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS: Int = 60 * 60 * 24 * 90 // 90 days private const val DEFAULT_XDS_TIMEOUT_IN_SECONDS: Int = 5 @@ -56,18 +49,14 @@ open class XdsBuilder ( internal var cdsTimeoutInSeconds: Int = DEFAULT_XDS_TIMEOUT_IN_SECONDS /** - * Sets the authentication HTTP header and token value for authenticating with the xDS - * management server. + * 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 { + fun setAuthenticationToken(header: String, token: String): XdsBuilder { this.authHeader = header this.authToken = token return this @@ -78,9 +67,7 @@ open class XdsBuilder ( * * @param token The JWT token used to authenticate the client to the xDS management server. * @param tokenLifetimeInSeconds The lifetime of the JWT token, in seconds. If none - * (or 0) is specified, then defaultJwtTokenLifetimeSeconds is - * used. - * + * (or 0) is specified, then defaultJwtTokenLifetimeSeconds is used. * @return this builder. */ fun setJwtAuthenticationToken( @@ -88,9 +75,9 @@ open class XdsBuilder ( tokenLifetimeInSeconds: Int = DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS ): XdsBuilder { this.jwtToken = token - this.jwtTokenLifetimeInSeconds = if (tokenLifetimeInSeconds > 0) - tokenLifetimeInSeconds else - DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS + this.jwtTokenLifetimeInSeconds = + if (tokenLifetimeInSeconds > 0) tokenLifetimeInSeconds + else DEFAULT_JWT_TOKEN_LIFETIME_IN_SECONDS return this } @@ -99,7 +86,6 @@ open class XdsBuilder ( * connection. If no root certs are specified, the operating system defaults are used. * * @param rootCerts The PEM-encoded server root certificates. - * * @return this builder. */ fun setSslRootCerts(rootCerts: String): XdsBuilder { @@ -108,12 +94,11 @@ open class XdsBuilder ( } /** - * Sets the SNI (https://datatracker.ietf.org/doc/html/rfc6066#section-3) on the TLS handshake - * and the authority HTTP header. If not set, the SNI is set by default to the xDS server address - * and the authority HTTP header is not set. + * Sets the SNI (https://datatracker.ietf.org/doc/html/rfc6066#section-3) on the TLS handshake and + * the authority HTTP header. If not set, the SNI is set by default to the xDS server address and + * the authority HTTP header is not set. * * @param sni The SNI value. - * * @return this builder. */ fun setSni(sni: String): XdsBuilder { @@ -122,16 +107,15 @@ open class XdsBuilder ( } /** - * Adds Runtime Discovery Service (RTDS) to the Runtime layers of the Bootstrap configuration, - * to retrieve dynamic runtime configuration via the xDS management server. + * Adds Runtime Discovery Service (RTDS) to the Runtime layers of the Bootstrap configuration, to + * retrieve dynamic runtime configuration via the xDS management server. * * @param resourceName The runtime config resource to subscribe to. * @param timeoutInSeconds specifies the `initial_fetch_timeout` field on the - * api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch - * timeout value of 5s, to prevent mobile app initialization from stalling. The default - * parameter value may change through the course of experimentation and no assumptions should - * be made of its exact value. - * + * api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch + * timeout value of 5s, to prevent mobile app initialization from stalling. The default + * parameter value may change through the course of experimentation and no assumptions should be + * made of its exact value. * @return this builder. */ fun addRuntimeDiscoveryService( @@ -144,18 +128,17 @@ open class XdsBuilder ( } /** - * Adds the Cluster Discovery Service (CDS) configuration for retrieving dynamic cluster - * resources via the xDS management server. + * Adds the Cluster Discovery Service (CDS) configuration for retrieving dynamic cluster resources + * via the xDS management server. * * @param cdsResourcesLocator the xdstp:// URI for subscribing to the cluster - * resources. If not using xdstp, then `cds_resources_locator` should be set to the empty - * string. + * resources. If not using xdstp, then `cds_resources_locator` should be set to the empty + * string. * @param timeoutInSeconds specifies the `initial_fetch_timeout` field on the - * api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch - * timeout value of 5s, to prevent mobile app initialization from stalling. The default - * parameter value may change through the course of experimentation and no assumptions should - * be made of its exact value. - * + * api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch + * timeout value of 5s, to prevent mobile app initialization from stalling. The default + * parameter value may change through the course of experimentation and no assumptions should be + * made of its exact value. * @return this builder. */ public fun addClusterDiscoveryService( @@ -173,12 +156,8 @@ open class XdsBuilder ( } } -/** - * Builder used for creating and running a new `Engine` instance. - */ -open class EngineBuilder( - private val configuration: BaseConfiguration = Standard() -) { +/** Builder used for creating and running a new `Engine` instance. */ +open class EngineBuilder(private val configuration: BaseConfiguration = Standard()) { protected var onEngineRunning: (() -> Unit) = {} protected var logger: ((String) -> Unit)? = null protected var eventTracker: ((Map) -> Unit)? = null @@ -232,7 +211,6 @@ open class EngineBuilder( * Add a log level to use with Envoy. * * @param logLevel the log level to use with Envoy. - * * @return this builder. */ fun addLogLevel(logLevel: LogLevel): EngineBuilder { @@ -241,14 +219,12 @@ open class EngineBuilder( } /** - * Specifies the domain (e.g. `example.com`) to use in the default gRPC stat sink to flush - * stats. + * Specifies the domain (e.g. `example.com`) to use in the default gRPC stat sink to flush stats. * * Setting this value enables the gRPC stat sink, which periodically flushes stats via the gRPC * MetricsService API. The flush interval is specified via addStatsFlushSeconds. * * @param grpcStatsDomain The domain to use for the gRPC stats sink. - * * @return this builder. */ fun addGrpcStatsDomain(grpcStatsDomain: String?): EngineBuilder { @@ -257,12 +233,11 @@ open class EngineBuilder( } /** - * Adds additional stats sinks, in the form of the raw YAML/JSON configuration. - * Sinks added in this fashion will be included in addition to the gRPC stats sink - * that may be enabled via addGrpcStatsDomain. + * Adds additional stats sinks, in the form of the raw YAML/JSON configuration. Sinks added in + * this fashion will be included in addition to the gRPC stats sink that may be enabled via + * addGrpcStatsDomain. * * @param statsSinks Configurations of stat sinks to add. - * * @return this builder. */ fun addStatsSinks(statsSinks: List): EngineBuilder { @@ -274,7 +249,6 @@ open class EngineBuilder( * Add a timeout for new network connections to hosts in the cluster. * * @param connectTimeoutSeconds timeout for new network connections to hosts in the cluster. - * * @return this builder. */ fun addConnectTimeoutSeconds(connectTimeoutSeconds: Int): EngineBuilder { @@ -286,7 +260,6 @@ open class EngineBuilder( * Add a default rate at which to refresh DNS. * * @param dnsRefreshSeconds default rate in seconds at which to refresh DNS. - * * @return this builder. */ fun addDNSRefreshSeconds(dnsRefreshSeconds: Int): EngineBuilder { @@ -299,7 +272,6 @@ open class EngineBuilder( * * @param base rate in seconds. * @param max rate in seconds. - * * @return this builder. */ fun addDNSFailureRefreshSeconds(base: Int, max: Int): EngineBuilder { @@ -312,7 +284,6 @@ open class EngineBuilder( * Add a rate at which to timeout DNS queries. * * @param dnsQueryTimeoutSeconds rate in seconds to timeout DNS queries. - * * @return this builder. */ fun addDNSQueryTimeoutSeconds(dnsQueryTimeoutSeconds: Int): EngineBuilder { @@ -325,7 +296,6 @@ open class EngineBuilder( * will be respected, subject to this minimum. Defaults to 60 seconds. * * @param dnsMinRefreshSeconds minimum rate in seconds at which to refresh DNS. - * * @return this builder. */ fun addDNSMinRefreshSeconds(dnsMinRefreshSeconds: Int): EngineBuilder { @@ -337,7 +307,6 @@ open class EngineBuilder( * Add a list of hostnames to preresolve on Engine startup. * * @param dnsPreresolveHostnames hostnames to preresolve. - * * @return this builder. */ fun addDNSPreresolveHostnames(dnsPreresolveHostnames: List): EngineBuilder { @@ -352,7 +321,6 @@ open class EngineBuilder( * establish new connections for any further requests. * * @param enableDrainPostDnsRefresh whether to drain connections after soft DNS refresh. - * * @return This builder. */ fun enableDrainPostDnsRefresh(enableDrainPostDnsRefresh: Boolean): EngineBuilder { @@ -363,12 +331,10 @@ open class EngineBuilder( /** * Specify whether to enable DNS cache. * - * Note that DNS cache requires an addition of a key value store named - * 'reserved.platform_store'. + * Note that DNS cache requires an addition of a key value store named 'reserved.platform_store'. * * @param enableDNSCache whether to enable DNS cache. Disabled by default. - * @param saveInterval the interval at which to save results to the configured key value store. - * + * @param saveInterval the interval at which to save results to the configured key value store. * @return This builder. */ fun enableDNSCache(enableDNSCache: Boolean, saveInterval: Int = 1): EngineBuilder { @@ -378,10 +344,9 @@ open class EngineBuilder( } /** - * Specify whether to do gzip response decompression or not. Defaults to true. + * Specify whether to do gzip response decompression or not. Defaults to true. * * @param enableGzipDecompression whether or not to gunzip responses. - * * @return This builder. */ fun enableGzipDecompression(enableGzipDecompression: Boolean): EngineBuilder { @@ -390,10 +355,9 @@ open class EngineBuilder( } /** - * Specify whether to enable HTTP3. Defaults to true. + * Specify whether to enable HTTP3. Defaults to true. * * @param enableHttp3 whether or not to enable HTTP3. - * * @return This builder. */ fun enableHttp3(enableHttp3: Boolean): EngineBuilder { @@ -402,10 +366,9 @@ open class EngineBuilder( } /** - * Specify whether to do brotli response decompression or not. Defaults to false. + * Specify whether to do brotli response decompression or not. Defaults to false. * * @param enableBrotliDecompression whether or not to brotli decompress responses. - * * @return This builder. */ fun enableBrotliDecompression(enableBrotliDecompression: Boolean): EngineBuilder { @@ -417,7 +380,6 @@ open class EngineBuilder( * Specify whether to support socket tagging or not. Defaults to false. * * @param enableSocketTagging whether or not support socket tagging. - * * @return This builder. */ fun enableSocketTagging(enableSocketTagging: Boolean): EngineBuilder { @@ -430,7 +392,6 @@ open class EngineBuilder( * conditions. * * @param enableInterfaceBinding whether to allow interface binding. - * * @return This builder. */ fun enableInterfaceBinding(enableInterfaceBinding: Boolean): EngineBuilder { @@ -439,16 +400,15 @@ open class EngineBuilder( } /** - * Specify whether system proxy settings should be respected. If yes, Envoy Mobile will - * use Android APIs to query Android Proxy settings configured on a device and will - * respect these settings when establishing connections with remote services. + * Specify whether system proxy settings should be respected. If yes, Envoy Mobile will use + * Android APIs to query Android Proxy settings configured on a device and will respect these + * settings when establishing connections with remote services. * - * The method is introduced for experimentation purposes and as a safety guard against - * critical issues in the implementation of the proxying feature. It's intended to be removed - * after it's confirmed that proxies on Android work as expected. + * The method is introduced for experimentation purposes and as a safety guard against critical + * issues in the implementation of the proxying feature. It's intended to be removed after it's + * confirmed that proxies on Android work as expected. * * @param enableProxying whether to enable Envoy's support for proxies. - * * @return This builder. */ fun enableProxying(enableProxying: Boolean): EngineBuilder { @@ -457,13 +417,12 @@ open class EngineBuilder( } /** - * Add a rate at which to ping h2 connections on new stream creation if the connection has - * sat idle. Defaults to 1 millisecond which effectively enables h2 ping functionality - * and results in a connection ping on every new stream creation. Set it to - * 100000000 milliseconds to effectively disable the ping. + * Add a rate at which to ping h2 connections on new stream creation if the connection has sat + * idle. Defaults to 1 millisecond which effectively enables h2 ping functionality and results in + * a connection ping on every new stream creation. Set it to 100000000 milliseconds to effectively + * disable the ping. * * @param idleIntervalMs rate in milliseconds. - * * @return this builder. */ fun addH2ConnectionKeepaliveIdleIntervalMilliseconds(idleIntervalMs: Int): EngineBuilder { @@ -475,7 +434,6 @@ open class EngineBuilder( * Add a rate at which to timeout h2 pings. * * @param timeoutSeconds rate in seconds to timeout h2 pings. - * * @return this builder. */ fun addH2ConnectionKeepaliveTimeoutSeconds(timeoutSeconds: Int): EngineBuilder { @@ -487,7 +445,6 @@ open class EngineBuilder( * Set the maximum number of connections to open to a single host. Default is 7. * * @param maxConnectionsPerHost the maximum number of connections per host. - * * @return this builder. */ fun setMaxConnectionsPerHost(maxConnectionsPerHost: Int): EngineBuilder { @@ -499,7 +456,6 @@ open class EngineBuilder( * Add an interval at which to flush Envoy stats. * * @param statsFlushSeconds interval at which to flush Envoy stats. - * * @return this builder. */ fun addStatsFlushSeconds(statsFlushSeconds: Int): EngineBuilder { @@ -511,7 +467,6 @@ open class EngineBuilder( * Add a custom idle timeout for HTTP streams. Defaults to 15 seconds. * * @param streamIdleTimeoutSeconds idle timeout for HTTP streams. - * * @return this builder. */ fun addStreamIdleTimeoutSeconds(streamIdleTimeoutSeconds: Int): EngineBuilder { @@ -523,7 +478,6 @@ open class EngineBuilder( * Add a custom per try idle timeout for HTTP streams. Defaults to 15 seconds. * * @param perTryIdleTimeoutSeconds per try idle timeout for HTTP streams. - * * @return this builder. */ fun addPerTryIdleTimeoutSeconds(perTryIdleTimeoutSeconds: Int): EngineBuilder { @@ -534,53 +488,47 @@ open class EngineBuilder( /** * Add an HTTP filter factory used to create platform filters for streams sent by this client. * - * @param name Custom name to use for this filter factory. Useful for having - * more meaningful trace logs, but not required. Should be unique - * per factory registered. + * @param name Custom name to use for this filter factory. Useful for having more meaningful trace + * logs, but not required. Should be unique per factory registered. * @param factory closure returning an instantiated filter. - * * @return this builder. */ - fun addPlatformFilter(name: String, factory: () -> Filter): - EngineBuilder { - this.platformFilterChain.add(FilterFactory(name, factory)) - return this - } + fun addPlatformFilter(name: String, factory: () -> Filter): EngineBuilder { + this.platformFilterChain.add(FilterFactory(name, factory)) + return this + } /** * Add an HTTP filter factory used to create platform filters for streams sent by this client. * * @param factory closure returning an instantiated filter. - * * @return this builder. */ - fun addPlatformFilter(factory: () -> Filter): - EngineBuilder { - this.platformFilterChain.add(FilterFactory(UUID.randomUUID().toString(), factory)) - return this - } + fun addPlatformFilter(factory: () -> Filter): EngineBuilder { + this.platformFilterChain.add(FilterFactory(UUID.randomUUID().toString(), factory)) + return this + } /** * Add an HTTP filter config used to create native filters for streams sent by this client. * - * @param name Custom name to use for this filter factory. Useful for having - * more meaningful trace logs, but not required. Should be unique - * per filter. + * @param name Custom name to use for this filter factory. Useful for having more meaningful trace + * logs, but not required. Should be unique per filter. * @param typedConfig config string for the filter. - * * @return this builder. */ - fun addNativeFilter(name: String = UUID.randomUUID().toString(), typedConfig: String): - EngineBuilder { - this.nativeFilterChain.add(EnvoyNativeFilterConfig(name, typedConfig)) - return this - } + fun addNativeFilter( + name: String = UUID.randomUUID().toString(), + typedConfig: String + ): EngineBuilder { + this.nativeFilterChain.add(EnvoyNativeFilterConfig(name, typedConfig)) + return this + } /** * Set a closure to be called when the engine finishes its async startup and begins running. * * @param closure the closure to be called. - * * @return this builder. */ fun setOnEngineRunning(closure: () -> Unit): EngineBuilder { @@ -590,8 +538,8 @@ open class EngineBuilder( /** * Set a closure to be called when the engine's logger logs. - * @param closure: The closure to be called. * + * @param closure: The closure to be called. * @return This builder. */ fun setLogger(closure: (String) -> Unit): EngineBuilder { @@ -599,9 +547,7 @@ open class EngineBuilder( return this } - /** - * Set event tracker for the engine to call when it emits an event. - */ + /** Set event tracker for the engine to call when it emits an event. */ fun setEventTracker(eventTracker: (Map) -> Unit): EngineBuilder { this.eventTracker = eventTracker return this @@ -612,7 +558,6 @@ open class EngineBuilder( * * @param name the name of the accessor. * @param accessor the string accessor. - * * @return this builder. */ fun addStringAccessor(name: String, accessor: () -> String): EngineBuilder { @@ -625,7 +570,6 @@ open class EngineBuilder( * * @param name the name of the KV store. * @param keyValueStore the KV store implementation. - * * @return this builder. */ fun addKeyValueStore(name: String, keyValueStore: KeyValueStore): EngineBuilder { @@ -637,7 +581,6 @@ open class EngineBuilder( * Add the App Version of the App using this Envoy Client. * * @param appVersion the version. - * * @return this builder. */ fun addAppVersion(appVersion: String): EngineBuilder { @@ -649,7 +592,6 @@ open class EngineBuilder( * Add the App ID of the App using this Envoy Client. * * @param appId the ID. - * * @return this builder. */ fun addAppId(appId: String): EngineBuilder { @@ -661,7 +603,6 @@ open class EngineBuilder( * Set how the TrustChainVerification must be handled. * * @param trustChainVerification whether to mute TLS Cert verification - intended for testing - * * @return this builder. */ fun setTrustChainVerification(trustChainVerification: TrustChainVerification): EngineBuilder { @@ -673,7 +614,6 @@ open class EngineBuilder( * Sets the node.id field in the Bootstrap configuration. * * @param nodeId the node ID. - * * @return this builder. */ fun setNodeId(nodeId: String): EngineBuilder { @@ -687,7 +627,6 @@ open class EngineBuilder( * @param region the region of the node locality. * @param zone the zone of the node locality. * @param subZone the sub-zone of the node locality. - * * @return this builder. */ fun setNodeLocality(region: String, zone: String, subZone: String): EngineBuilder { @@ -701,7 +640,6 @@ open class EngineBuilder( * Sets the xDS configuration for the Envoy Mobile engine. * * @param xdsBuilder The XdsBuilder instance from which to construct the xDS configuration. - * * @return this builder. */ fun setXds(xdsBuilder: XdsBuilder): EngineBuilder { @@ -714,7 +652,6 @@ open class EngineBuilder( * * @param name the name of the runtime guard, e.g. test_feature_false. * @param value the value for the runtime guard. - * * @return This builder. */ fun setRuntimeGuard(name: String, value: Boolean): EngineBuilder { @@ -727,13 +664,12 @@ open class EngineBuilder( * * @param host the host's name. * @param port the port number. - * * @return This builder. */ - fun addQuicHint(host: String, port: Int): EngineBuilder { + fun addQuicHint(host: String, port: Int): EngineBuilder { this.quicHints.put(host, port) return this - } + } /** * Builds and runs a new Engine instance with the provided configuration. @@ -742,77 +678,68 @@ open class EngineBuilder( */ @Suppress("LongMethod") fun build(): Engine { - val engineConfiguration = EnvoyConfiguration( - grpcStatsDomain, - connectTimeoutSeconds, - dnsRefreshSeconds, - dnsFailureRefreshSecondsBase, - dnsFailureRefreshSecondsMax, - dnsQueryTimeoutSeconds, - dnsMinRefreshSeconds, - dnsPreresolveHostnames, - enableDNSCache, - dnsCacheSaveIntervalSeconds, - enableDrainPostDnsRefresh, - enableHttp3, - http3ConnectionOptions, - http3ClientConnectionOptions, - quicHints, - enableGzipDecompression, - enableBrotliDecompression, - enableSocketTagging, - enableInterfaceBinding, - h2ConnectionKeepaliveIdleIntervalMilliseconds, - h2ConnectionKeepaliveTimeoutSeconds, - maxConnectionsPerHost, - statsFlushSeconds, - streamIdleTimeoutSeconds, - perTryIdleTimeoutSeconds, - appVersion, - appId, - trustChainVerification, - nativeFilterChain, - platformFilterChain, - stringAccessors, - keyValueStores, - statsSinks, - runtimeGuards, - enablePlatformCertificatesValidation, - xdsBuilder?.rtdsResourceName, - xdsBuilder?.rtdsTimeoutInSeconds ?: 0, - xdsBuilder?.xdsServerAddress, - xdsBuilder?.xdsServerPort ?: 0, - xdsBuilder?.authHeader, - xdsBuilder?.authToken, - xdsBuilder?.jwtToken, - xdsBuilder?.jwtTokenLifetimeInSeconds ?: 0, - xdsBuilder?.sslRootCerts, - xdsBuilder?.sni, - nodeId, - nodeRegion, - nodeZone, - nodeSubZone, - xdsBuilder?.cdsResourcesLocator, - xdsBuilder?.cdsTimeoutInSeconds ?: 0, - xdsBuilder?.enableCds ?: false, - ) - + val engineConfiguration = + EnvoyConfiguration( + grpcStatsDomain, + connectTimeoutSeconds, + dnsRefreshSeconds, + dnsFailureRefreshSecondsBase, + dnsFailureRefreshSecondsMax, + dnsQueryTimeoutSeconds, + dnsMinRefreshSeconds, + dnsPreresolveHostnames, + enableDNSCache, + dnsCacheSaveIntervalSeconds, + enableDrainPostDnsRefresh, + enableHttp3, + http3ConnectionOptions, + http3ClientConnectionOptions, + quicHints, + enableGzipDecompression, + enableBrotliDecompression, + enableSocketTagging, + enableInterfaceBinding, + h2ConnectionKeepaliveIdleIntervalMilliseconds, + h2ConnectionKeepaliveTimeoutSeconds, + maxConnectionsPerHost, + statsFlushSeconds, + streamIdleTimeoutSeconds, + perTryIdleTimeoutSeconds, + appVersion, + appId, + trustChainVerification, + nativeFilterChain, + platformFilterChain, + stringAccessors, + keyValueStores, + statsSinks, + runtimeGuards, + enablePlatformCertificatesValidation, + xdsBuilder?.rtdsResourceName, + xdsBuilder?.rtdsTimeoutInSeconds ?: 0, + xdsBuilder?.xdsServerAddress, + xdsBuilder?.xdsServerPort ?: 0, + xdsBuilder?.authHeader, + xdsBuilder?.authToken, + xdsBuilder?.jwtToken, + xdsBuilder?.jwtTokenLifetimeInSeconds ?: 0, + xdsBuilder?.sslRootCerts, + xdsBuilder?.sni, + nodeId, + nodeRegion, + nodeZone, + nodeSubZone, + xdsBuilder?.cdsResourcesLocator, + xdsBuilder?.cdsTimeoutInSeconds ?: 0, + xdsBuilder?.enableCds ?: false, + ) return when (configuration) { is Custom -> { - EngineImpl( - engineType(), - engineConfiguration, - configuration.yaml, - logLevel - ) + EngineImpl(engineType(), engineConfiguration, configuration.yaml, logLevel) } is Standard -> { - EngineImpl( - engineType(), - engineConfiguration, - logLevel - ) + EngineImpl(engineType(), engineConfiguration, logLevel) } } } @@ -832,11 +759,11 @@ open class EngineBuilder( * validation logic. Defaults to false. * * @param enablePlatformCertificatesValidation true if using platform APIs is desired. - * * @return This builder. */ - fun enablePlatformCertificatesValidation(enablePlatformCertificatesValidation: Boolean): - EngineBuilder { + fun enablePlatformCertificatesValidation( + enablePlatformCertificatesValidation: Boolean + ): EngineBuilder { this.enablePlatformCertificatesValidation = enablePlatformCertificatesValidation return this } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilderHTTP3Util.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilderHTTP3Util.kt index 564c2f13be81..1d4c29d9b865 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilderHTTP3Util.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilderHTTP3Util.kt @@ -1,20 +1,17 @@ package io.envoyproxy.envoymobile -/** - * Utility to enable HTTP/3. - */ +/** Utility to enable HTTP/3. */ object EngineBuilderHTTP3Util { /** - * Specify whether to enable experimental HTTP/3 (QUIC) support. Note the actual protocol will - * be negotiated with the upstream endpoint and so upstream support is still required for HTTP/3 - * to be utilized. + * Specify whether to enable experimental HTTP/3 (QUIC) support. Note the actual protocol will be + * negotiated with the upstream endpoint and so upstream support is still required for HTTP/3 to + * be utilized. * * @param enableHttp3 whether to enable HTTP/3. - * * @return This builder. */ fun EngineBuilder.enableHttp3(enableHttp3: Boolean): EngineBuilder { - this.enableHttp3 = enableHttp3 - return this + this.enableHttp3 = enableHttp3 + return this } } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineImpl.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineImpl.kt index 066ac6794a65..c26ddb360987 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineImpl.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineImpl.kt @@ -3,10 +3,9 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.EnvoyConfiguration import io.envoyproxy.envoymobile.engine.EnvoyEngine -/** - * An implementation of {@link Engine}. - */ -class EngineImpl constructor( +/** An implementation of {@link Engine}. */ +class EngineImpl +constructor( internal val envoyEngine: EnvoyEngine, internal val envoyConfiguration: EnvoyConfiguration, internal val configurationYAML: String?, diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EnvoyError.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EnvoyError.kt index 6f2163b610b7..9f2363e7fe84 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EnvoyError.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EnvoyError.kt @@ -5,11 +5,12 @@ package io.envoyproxy.envoymobile * * @param errorCode internal error code associated with the exception that occurred. * @param message a description of what exception that occurred. - * @param attemptCount an optional number of times an operation was attempted before firing - * this error. + * @param attemptCount an optional number of times an operation was attempted before firing this + * error. * @param cause an optional cause for the exception. */ -class EnvoyError constructor( +class EnvoyError +constructor( val errorCode: Int, val message: String, val attemptCount: Int? = null, diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/FinalStreamIntel.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/FinalStreamIntel.kt index 5f0a6e9689a9..884bc7476b6d 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/FinalStreamIntel.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/FinalStreamIntel.kt @@ -7,35 +7,37 @@ import io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel * Exposes one time HTTP stream metrics, context, and other details. * * Note: a timestamp field (ends with "Ms") with a value of -1 indicates that it is absent. + * * @param streamId The stream identifier. * @param connectionId The connection identifier. * @param attemptCount The number of attempts used to perform a given request. * @param streamStartMs The time the stream started (a.k.a. request started), in ms since the epoch. * @param dnsStartMs The time the DNS resolution for this request started, in ms since the epoch. * @param dnsEndMs The time the DNS resolution for this request completed, in ms since the epoch. - * @param connectStartMs The time the upstream connection started, in ms since the epoch. - * This may not be set if socketReused is false. - * @param connectEndMs The time the upstream connection completed, in ms since the epoch. - * This may not be set if socketReused is false. - * @param sslStartMs The time the SSL handshake started, in ms since the epoch. - * This may not be set if socketReused is false. - * @param sslEndMs The time the SSL handshake completed, in ms since the epoch. - * This may not be set if socketReused is false. - * @param sendingStartMs The time the first byte of the request was sent upstream, - * in ms since the epoch. + * @param connectStartMs The time the upstream connection started, in ms since the epoch. This may + * not be set if socketReused is false. + * @param connectEndMs The time the upstream connection completed, in ms since the epoch. This may + * not be set if socketReused is false. + * @param sslStartMs The time the SSL handshake started, in ms since the epoch. This may not be set + * if socketReused is false. + * @param sslEndMs The time the SSL handshake completed, in ms since the epoch. This may not be set + * if socketReused is false. + * @param sendingStartMs The time the first byte of the request was sent upstream, in ms since the + * epoch. * @param sendingEndMs The time the last byte of the request was sent upstream, in ms since the - * epoch. + * epoch. * @param responseStartMs The time the first byte of the response was received, in ms since the - * epoch. - * @param streamEndMs The time when the stream reached a final state (Error, Cancel, Success), - * in ms since the epoch. + * epoch. + * @param streamEndMs The time when the stream reached a final state (Error, Cancel, Success), in ms + * since the epoch. * @param socketReused True if the upstream socket had been used previously. * @param sentByteCount The number of bytes sent upstream. * @param receivedByteCount The number of bytes received from upstream. * @param responseFlags The response flags for the stream. */ @Suppress("LongParameterList") -class FinalStreamIntel constructor( +class FinalStreamIntel +constructor( streamId: Long, connectionId: Long, attemptCount: Long, @@ -55,15 +57,27 @@ class FinalStreamIntel constructor( val receivedByteCount: Long, val responseFlags: Long ) : StreamIntel(streamId, connectionId, attemptCount) { - constructor(superBase: EnvoyStreamIntel, base: EnvoyFinalStreamIntel) : this( - superBase.streamId, superBase.connectionId, superBase.attemptCount, - base.streamStartMs, base.dnsStartMs, - base.dnsEndMs, base.connectStartMs, - base.connectEndMs, base.sslStartMs, - base.sslEndMs, base.sendingStartMs, + constructor( + superBase: EnvoyStreamIntel, + base: EnvoyFinalStreamIntel + ) : this( + superBase.streamId, + superBase.connectionId, + superBase.attemptCount, + base.streamStartMs, + base.dnsStartMs, + base.dnsEndMs, + base.connectStartMs, + base.connectEndMs, + base.sslStartMs, + base.sslEndMs, + base.sendingStartMs, base.sendingEndMs, - base.responseStartMs, base.streamEndMs, - base.socketReused, base.sentByteCount, - base.receivedByteCount, base.responseFlags + base.responseStartMs, + base.streamEndMs, + base.socketReused, + base.sentByteCount, + base.receivedByteCount, + base.responseFlags ) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/Headers.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/Headers.kt index 305ae118142c..e32274bca3a1 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/Headers.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/Headers.kt @@ -1,8 +1,8 @@ package io.envoyproxy.envoymobile /** - * Base class that is used to represent header/trailer data structures. - * To instantiate new instances, see `{Request|Response}HeadersBuilder`. + * Base class that is used to represent header/trailer data structures. To instantiate new + * instances, see `{Request|Response}HeadersBuilder`. */ open class Headers { internal val container: HeadersContainer @@ -17,13 +17,11 @@ open class Headers { } /** - * Get the value for the provided header name. It's discouraged - * to use this dictionary for equality key-based lookups as this - * may lead to issues with headers that do not follow expected + * Get the value for the provided header name. It's discouraged to use this dictionary for + * equality key-based lookups as this may lead to issues with headers that do not follow expected * casing i.e., "Content-Length" instead of "content-length". * * @param name: Header name for which to get the current value. - * * @return The current headers specified for the provided name. */ fun value(name: String): List? { diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/HeadersBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/HeadersBuilder.kt index 293f4e8d31e5..e77e13ae03c4 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/HeadersBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/HeadersBuilder.kt @@ -1,8 +1,8 @@ package io.envoyproxy.envoymobile /** - * Base builder class used to construct `Headers` instances. - * See `{Request|Response}HeadersBuilder` for usage. + * Base builder class used to construct `Headers` instances. See `{Request|Response}HeadersBuilder` + * for usage. */ open class HeadersBuilder { protected val container: HeadersContainer @@ -19,9 +19,8 @@ open class HeadersBuilder { /** * Append a value to the header name. * - * @param name: The header name. + * @param name: The header name. * @param value: The value associated to the header name. - * * @return HeadersBuilder, This builder. */ open fun add(name: String, value: String): HeadersBuilder { @@ -37,7 +36,6 @@ open class HeadersBuilder { * * @param name: The header name. * @param value: The value associated to the header name. - * * @return HeadersBuilder, This builder. */ open fun set(name: String, value: MutableList): HeadersBuilder { @@ -52,7 +50,6 @@ open class HeadersBuilder { * Remove all headers with this name. * * @param name: The header name to remove. - * * @return HeadersBuilder, This builder. */ open fun remove(name: String): HeadersBuilder { @@ -68,7 +65,6 @@ open class HeadersBuilder { * * @param name: The header name. * @param value: The value associated to the header name. - * * @return HeadersBuilder, This builder. */ internal open fun internalSet(name: String, value: MutableList): HeadersBuilder { @@ -76,6 +72,8 @@ open class HeadersBuilder { return this } - private fun isRestrictedHeader(name: String) = name.startsWith(":") || - name.startsWith("x-envoy-mobile", ignoreCase = true) || name.equals("host", ignoreCase = true) + private fun isRestrictedHeader(name: String) = + name.startsWith(":") || + name.startsWith("x-envoy-mobile", ignoreCase = true) || + name.equals("host", ignoreCase = true) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/HeadersContainer.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/HeadersContainer.kt index 90dabac23ce9..dffac831201a 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/HeadersContainer.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/HeadersContainer.kt @@ -1,19 +1,18 @@ package io.envoyproxy.envoymobile /** - * The container that manages the underlying headers map. - * It maintains the original casing of passed header names. - * It treats headers names as case-insensitive for the purpose - * of header lookups and header name conflict resolutions. + * The container that manages the underlying headers map. It maintains the original casing of passed + * header names. It treats headers names as case-insensitive for the purpose of header lookups and + * header name conflict resolutions. */ open class HeadersContainer { protected val headers: MutableMap /** - * Represents a header name together with all of its values. - * It preserves the original casing of the header name. + * Represents a header name together with all of its values. It preserves the original casing of + * the header name. * - * @param name The name of the header. Its casing is preserved. + * @param name The name of the header. Its casing is preserved. * @param value The value associated with a given header. */ data class Header(val name: String, var value: MutableList) { @@ -46,15 +45,15 @@ open class HeadersContainer { internal constructor(headers: Map>) { var underlyingHeaders = mutableMapOf() /** - * Dictionaries are unordered collections. Process headers with names - * that are the same when lowercased in an alphabetical order to avoid a situation - * in which the result of the initialization is non-derministic i.e., we want - * mapOf("A" to listOf("1"), "a" to listOf("2")) headers to be always converted to - * mapOf("A" to listOf("1", "2")) and never to mapOf("a" to listOf("2", "1")). + * Dictionaries are unordered collections. Process headers with names that are the same when + * lowercased in an alphabetical order to avoid a situation in which the result of the + * initialization is non-derministic i.e., we want mapOf("A" to listOf("1"), "a" to listOf("2")) + * headers to be always converted to mapOf("A" to listOf("1", "2")) and never to mapOf("a" to + * listOf("2", "1")). * - * If a given header name already exists in the processed headers map, check - * if the currently processed header name is before the existing header name as - * determined by an alphabetical order. + * If a given header name already exists in the processed headers map, check if the currently + * processed header name is before the existing header name as determined by an alphabetical + * order. */ headers.forEach { val lowercased = it.key.lowercase() @@ -74,13 +73,12 @@ open class HeadersContainer { companion object { /** - * Create a new instance of the receiver using a provider headers map. - * Not implemented as a constructor due to conflicting JVM signatures with - * other constructors. + * Create a new instance of the receiver using a provider headers map. Not implemented as a + * constructor due to conflicting JVM signatures with other constructors. * * @param headers The headers to create the container with. */ - fun create(headers: Map>) : HeadersContainer { + fun create(headers: Map>): HeadersContainer { return HeadersContainer(headers.mapValues { it.value.toMutableList() }) } } @@ -88,23 +86,20 @@ open class HeadersContainer { /** * Add a value to a header with a given name. * - * @param name The name of the header. For the purpose of headers lookup - * and header name conflict resolution, the name of the header - * is considered to be case-insensitive. + * @param name The name of the header. For the purpose of headers lookup and header name conflict + * resolution, the name of the header is considered to be case-insensitive. * @param value The value to add. */ fun add(name: String, value: String) { val lowercased = name.lowercase() - headers[lowercased]?.let { it.add(value) } ?: run { - headers.put(lowercased, Header(name, mutableListOf(value))) - } + headers[lowercased]?.let { it.add(value) } + ?: run { headers.put(lowercased, Header(name, mutableListOf(value))) } } - /** * Set the value of a given header. * - * @param name The name of the header. + * @param name The name of the header. * @param value The value to set the header value to. */ fun set(name: String, value: List) { @@ -123,8 +118,7 @@ open class HeadersContainer { /** * Get the value for the provided header name. * - * @param name The case-insensitive header name for which to - * get the current value. + * @param name The case-insensitive header name for which to get the current value. * @return The value associated with a given header. */ fun value(name: String): List? { @@ -132,16 +126,14 @@ open class HeadersContainer { } /** - * Accessor for all underlying case-sensitive headers. When possible, - * use case-insensitive accessors instead. + * Accessor for all underlying case-sensitive headers. When possible, use case-insensitive + * accessors instead. * * @return The underlying headers. */ fun caseSensitiveHeaders(): Map> { var caseSensitiveHeaders = mutableMapOf>() - headers.forEach { - caseSensitiveHeaders.put(it.value.name, it.value.value) - } + headers.forEach { caseSensitiveHeaders.put(it.value.name, it.value.value) } return caseSensitiveHeaders } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/LogLevel.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/LogLevel.kt index 763ffc77277a..63887409a580 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/LogLevel.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/LogLevel.kt @@ -13,5 +13,5 @@ enum class LogLevel(internal val level: String, val levelInt: Int) { WARN("warn", 3), ERROR("error", 4), CRITICAL("critical", 5), - OFF("off", -1); + OFF("off", -1) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/PulseClient.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/PulseClient.kt index 463b5275fdab..20957778e318 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/PulseClient.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/PulseClient.kt @@ -8,13 +8,9 @@ package io.envoyproxy.envoymobile */ interface PulseClient { - /** - * @return A counter based on the joined elements. - */ + /** @return A counter based on the joined elements. */ fun counter(vararg elements: Element): Counter - /** - * @return A counter based on the joined elements with tags. - */ + /** @return A counter based on the joined elements with tags. */ fun counter(vararg elements: Element, tags: Tags): Counter } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/PulseClientImpl.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/PulseClientImpl.kt index 43a739ffe8c9..66c7bd7909c6 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/PulseClientImpl.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/PulseClientImpl.kt @@ -2,12 +2,8 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.EnvoyEngine -/** - * Envoy implementation of `PulseClient`. - */ -internal class PulseClientImpl constructor( - internal val engine: EnvoyEngine -) : PulseClient { +/** Envoy implementation of `PulseClient`. */ +internal class PulseClientImpl constructor(internal val engine: EnvoyEngine) : PulseClient { override fun counter(vararg elements: Element): Counter { return CounterImpl(engine, elements.asList()) diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeaders.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeaders.kt index 4feeac437aac..185041c02994 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeaders.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeaders.kt @@ -1,8 +1,6 @@ package io.envoyproxy.envoymobile -/** - * Headers representing an outbound request. - */ +/** Headers representing an outbound request. */ open class RequestHeaders : Headers { /** * Internal constructor used by builders. @@ -11,31 +9,21 @@ open class RequestHeaders : Headers { */ internal constructor(headers: Map>) : super(HeadersContainer.create(headers)) - internal constructor(container: HeadersContainer): super(container) + internal constructor(container: HeadersContainer) : super(container) - /** - * Method for the request. - */ + /** Method for the request. */ val method: RequestMethod by lazy { RequestMethod.enumValue(value(":method")?.first()!!) } - /** - * The URL scheme for the request (i.e., "https"). - */ + /** The URL scheme for the request (i.e., "https"). */ val scheme: String by lazy { value(":scheme")?.first()!! } - /** - * The URL authority for the request (i.e., "api.foo.com"). - */ + /** The URL authority for the request (i.e., "api.foo.com"). */ val authority: String by lazy { value(":authority")?.first()!! } - /** - * The URL path for the request (i.e., "/foo"). - */ + /** The URL path for the request (i.e., "/foo"). */ val path: String by lazy { value(":path")?.first()!! } - /** - * Retry policy to use for this request. - */ + /** Retry policy to use for this request. */ val retryPolicy: RetryPolicy? by lazy { RetryPolicy.from(this) } /** diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilder.kt index be9dd7cc3b15..89bf4ed4b91a 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilder.kt @@ -1,24 +1,22 @@ package io.envoyproxy.envoymobile -/** - * Builder used for constructing instances of RequestHeaders`. - */ +/** Builder used for constructing instances of RequestHeaders`. */ class RequestHeadersBuilder : HeadersBuilder { /** * Initialize a new instance of the builder. * - * @param method: Method for the request. - * @param scheme: The URL scheme for the request (i.e., "https"). + * @param method: Method for the request. + * @param scheme: The URL scheme for the request (i.e., "https"). * @param authority: The URL authority for the request (i.e., "api.foo.com"). - * @param path: The URL path for the request (i.e., "/foo"). + * @param path: The URL path for the request (i.e., "/foo"). */ constructor( method: RequestMethod, scheme: String = "https", authority: String, path: String - ) : - super(HeadersContainer( + ) : super( + HeadersContainer( mapOf( ":authority" to mutableListOf(authority), ":method" to mutableListOf(method.stringValue), @@ -26,7 +24,7 @@ class RequestHeadersBuilder : HeadersBuilder { ":scheme" to mutableListOf(scheme) ) ) - ) + ) /** * Instantiate a new builder. Used only by RequestHeaders to convert back to @@ -67,7 +65,6 @@ class RequestHeadersBuilder : HeadersBuilder { * Add a retry policy to be used with this request. * * @param retryPolicy: The retry policy to use. - * * @return RequestHeadersBuilder, This builder. */ fun addRetryPolicy(retryPolicy: RetryPolicy): RequestHeadersBuilder { @@ -84,21 +81,17 @@ class RequestHeadersBuilder : HeadersBuilder { * @param uid: Traffic stats UID to be applied. * @param tag: Traffic stats tag to be applied. * - * See: https://source.android.com/devices/tech/datausage/tags-explained - * See: https://developer.android.com/reference/android/net/TrafficStats#setThreadStatsTag(int) - * See: https://developer.android.com/reference/android/net/TrafficStats#setThreadStatsUid(int) - * See: https://developer.android.com/reference/android/net/TrafficStats#tagSocket(java.net.Socket) + * See: https://source.android.com/devices/tech/datausage/tags-explained See: + * https://developer.android.com/reference/android/net/TrafficStats#setThreadStatsTag(int) See: + * https://developer.android.com/reference/android/net/TrafficStats#setThreadStatsUid(int) See: + * https://developer.android.com/reference/android/net/TrafficStats#tagSocket(java.net.Socket) * * @return RequestHeadersBuilder, This builder. */ - fun addSocketTag(uid: Int, tag: Int): - RequestHeadersBuilder { - internalSet( - "x-envoy-mobile-socket-tag", - mutableListOf(uid.toString() + "," + tag.toString()) - ) - return this - } + fun addSocketTag(uid: Int, tag: Int): RequestHeadersBuilder { + internalSet("x-envoy-mobile-socket-tag", mutableListOf(uid.toString() + "," + tag.toString())) + return this + } /** * Build the request headers using the current builder. diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderCompressionUtil.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderCompressionUtil.kt index db7d14dbdc74..be671c7b0082 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderCompressionUtil.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderCompressionUtil.kt @@ -1,23 +1,18 @@ package io.envoyproxy.envoymobile -/** - * Utility to enable request compression. - */ +/** Utility to enable request compression. */ object RequestHeadersBuilderCompressionUtil { /** - * Compress this request's body using the specified algorithm. - * Will only apply if the content length exceeds 30 bytes. + * Compress this request's body using the specified algorithm. Will only apply if the content + * length exceeds 30 bytes. * * @param algorithm: The compression algorithm to use to compress this request. - * * @return RequestHeadersBuilder, This builder. */ - fun RequestHeadersBuilder.enableRequestCompression(algorithm: CompressionAlgorithm): - RequestHeadersBuilder { - internalSet( - "x-envoy-mobile-compression", - mutableListOf(algorithm.stringValue) - ) - return this - } + fun RequestHeadersBuilder.enableRequestCompression( + algorithm: CompressionAlgorithm + ): RequestHeadersBuilder { + internalSet("x-envoy-mobile-compression", mutableListOf(algorithm.stringValue)) + return this + } } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestMethod.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestMethod.kt index 200931048b87..6ce182849778 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestMethod.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestMethod.kt @@ -2,9 +2,7 @@ package io.envoyproxy.envoymobile import java.lang.IllegalArgumentException -/** - * Represents an HTTP request method. - */ +/** Represents an HTTP request method. */ enum class RequestMethod(internal val stringValue: String) { DELETE("DELETE"), GET("GET"), diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestTrailers.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestTrailers.kt index 6c2157622bbd..bb7a0efe233b 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestTrailers.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestTrailers.kt @@ -1,8 +1,6 @@ package io.envoyproxy.envoymobile -/** - * Trailers representing an outbound request. - */ +/** Trailers representing an outbound request. */ @Suppress("EmptyClassBlock") class RequestTrailers : Trailers { /** diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestTrailersBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestTrailersBuilder.kt index 085a1bc77578..665aa07ee0c4 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestTrailersBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/RequestTrailersBuilder.kt @@ -1,8 +1,6 @@ package io.envoyproxy.envoymobile -/** - * Builder used for constructing instances of `RequestTrailers`. - */ +/** Builder used for constructing instances of `RequestTrailers`. */ class RequestTrailersBuilder : HeadersBuilder { /* * Instantiate a new builder. @@ -22,8 +20,9 @@ class RequestTrailersBuilder : HeadersBuilder { * * @param trailers: The trailers to start with. */ - internal constructor(trailers: MutableMap>) - : super(HeadersContainer(trailers)) + internal constructor( + trailers: MutableMap> + ) : super(HeadersContainer(trailers)) override fun add(name: String, value: String): RequestTrailersBuilder { super.add(name, value) diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseHeaders.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseHeaders.kt index 9710b08a4426..3ff33add6646 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseHeaders.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseHeaders.kt @@ -1,8 +1,6 @@ package io.envoyproxy.envoymobile -/** - * Headers representing an inbound response. - */ +/** Headers representing an inbound response. */ class ResponseHeaders : Headers { /** * Internal constructor used by builders. @@ -13,12 +11,8 @@ class ResponseHeaders : Headers { internal constructor(container: HeadersContainer) : super(container) - /** - * HTTP status code received with the response. - */ - val httpStatus: Int? by lazy { - value(":status")?.first()?.toIntOrNull()?.takeIf { it >= 0 } - } + /** HTTP status code received with the response. */ + val httpStatus: Int? by lazy { value(":status")?.first()?.toIntOrNull()?.takeIf { it >= 0 } } /** * Convert the headers back to a builder for mutation. diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseHeadersBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseHeadersBuilder.kt index 93254aaac8f7..50cc7b860d3b 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseHeadersBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseHeadersBuilder.kt @@ -1,8 +1,6 @@ package io.envoyproxy.envoymobile -/** - * Builder used for constructing instances of `ResponseHeaders`. - */ +/** Builder used for constructing instances of `ResponseHeaders`. */ class ResponseHeadersBuilder : HeadersBuilder { /* @@ -16,8 +14,9 @@ class ResponseHeadersBuilder : HeadersBuilder { * * @param headers: The headers to start with. */ - internal constructor(headers: MutableMap>) - : super(HeadersContainer(headers)) + internal constructor( + headers: MutableMap> + ) : super(HeadersContainer(headers)) /* * Instantiate a new builder. @@ -50,7 +49,6 @@ class ResponseHeadersBuilder : HeadersBuilder { * Add an HTTP status to the response headers. Must be a positive integer. * * @param status: The HTTP status to add. - * * @return ResponseHeadersBuilder, This builder. */ fun addHttpStatus(status: Int): ResponseHeadersBuilder { diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseTrailers.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseTrailers.kt index 579059535b93..0b95723e4955 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseTrailers.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseTrailers.kt @@ -1,8 +1,6 @@ package io.envoyproxy.envoymobile -/** - * Trailers representing an inbound response. - */ +/** Trailers representing an inbound response. */ @Suppress("EmptyClassBlock") class ResponseTrailers : Trailers { /** diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseTrailersBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseTrailersBuilder.kt index f67635a28a16..ef05f9f12763 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseTrailersBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/ResponseTrailersBuilder.kt @@ -1,12 +1,8 @@ package io.envoyproxy.envoymobile -/** - * Builder used for constructing instances of `ResponseTrailers`. - */ +/** Builder used for constructing instances of `ResponseTrailers`. */ class ResponseTrailersBuilder : HeadersBuilder { - /** - * Initialize a new instance of the builder. - */ + /** Initialize a new instance of the builder. */ constructor() : super(HeadersContainer(mapOf())) /** @@ -15,8 +11,9 @@ class ResponseTrailersBuilder : HeadersBuilder { * * @param trailers: The trailers to start with. */ - internal constructor(trailers: MutableMap>) - : super(HeadersContainer(trailers)) + internal constructor( + trailers: MutableMap> + ) : super(HeadersContainer(trailers)) internal constructor(container: HeadersContainer) : super(container) diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/RetryPolicy.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/RetryPolicy.kt index ef3dfacc4a61..1610022dfa49 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/RetryPolicy.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/RetryPolicy.kt @@ -8,11 +8,11 @@ import java.lang.IllegalArgumentException * @param maxRetryCount Maximum number of retries that a request may be performed. * @param retryOn Rules checked for retrying. * @param retryStatusCodes Additional list of status codes that should be retried. - * @param perRetryTimeoutMS Timeout (in milliseconds) to apply to each retry. - * Must be <= `totalUpstreamTimeoutMS` if it's a positive number. - * @param totalUpstreamTimeoutMS Total timeout (in milliseconds) that includes all retries. - * Spans the point at which the entire downstream request has been processed and when the - * upstream response has been completely processed. Null or 0 may be specified to disable it. + * @param perRetryTimeoutMS Timeout (in milliseconds) to apply to each retry. Must be <= + * `totalUpstreamTimeoutMS` if it's a positive number. + * @param totalUpstreamTimeoutMS Total timeout (in milliseconds) that includes all retries. Spans + * the point at which the entire downstream request has been processed and when the upstream + * response has been completely processed. Null or 0 may be specified to disable it. */ data class RetryPolicy( val maxRetryCount: Int, @@ -22,8 +22,11 @@ data class RetryPolicy( val totalUpstreamTimeoutMS: Long? = 15000 ) { init { - if (perRetryTimeoutMS != null && totalUpstreamTimeoutMS != null && - perRetryTimeoutMS > totalUpstreamTimeoutMS && totalUpstreamTimeoutMS != 0L + if ( + perRetryTimeoutMS != null && + totalUpstreamTimeoutMS != null && + perRetryTimeoutMS > totalUpstreamTimeoutMS && + totalUpstreamTimeoutMS != 0L ) { throw IllegalArgumentException("Per-retry timeout cannot be less than total timeout") } @@ -43,11 +46,15 @@ data class RetryPolicy( // Envoy internally coalesces multiple x-envoy header values into one comma-delimited value. // These flatMap transformations split those values up to correctly map back to // Kotlin enums. - headers.value("x-envoy-retry-on") - ?.flatMap { it.split(",") }?.map { retryOn -> RetryRule.enumValue(retryOn) } + headers + .value("x-envoy-retry-on") + ?.flatMap { it.split(",") } + ?.map { retryOn -> RetryRule.enumValue(retryOn) } ?.filterNotNull() ?: emptyList(), - headers.value("x-envoy-retriable-status-codes") - ?.flatMap { it.split(",") }?.map { statusCode -> statusCode.toIntOrNull() } + headers + .value("x-envoy-retriable-status-codes") + ?.flatMap { it.split(",") } + ?.map { statusCode -> statusCode.toIntOrNull() } ?.filterNotNull() ?: emptyList(), headers.value("x-envoy-upstream-rq-per-try-timeout-ms")?.firstOrNull()?.toLongOrNull(), headers.value("x-envoy-upstream-rq-timeout-ms")?.firstOrNull()?.toLongOrNull() @@ -57,8 +64,8 @@ data class RetryPolicy( } /** - * Rules that may be used with `RetryPolicy`. - * See the `x-envoy-retry-on` Envoy header for documentation. + * Rules that may be used with `RetryPolicy`. See the `x-envoy-retry-on` Envoy header for + * documentation. */ enum class RetryRule(internal val stringValue: String) { STATUS_5XX("5xx"), diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/RetryPolicyMapper.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/RetryPolicyMapper.kt index ba07bb904c65..d72e85009526 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/RetryPolicyMapper.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/RetryPolicyMapper.kt @@ -7,10 +7,11 @@ package io.envoyproxy.envoymobile */ internal fun RetryPolicy.outboundHeaders(): Map> { val upstreamTimeoutMS = totalUpstreamTimeoutMS ?: 0L - val headers = mutableMapOf( - "x-envoy-max-retries" to listOf("$maxRetryCount"), - "x-envoy-upstream-rq-timeout-ms" to listOf("$upstreamTimeoutMS") - ) + val headers = + mutableMapOf( + "x-envoy-max-retries" to listOf("$maxRetryCount"), + "x-envoy-upstream-rq-timeout-ms" to listOf("$upstreamTimeoutMS") + ) if (perRetryTimeoutMS != null) { headers["x-envoy-upstream-rq-per-try-timeout-ms"] = listOf("$perRetryTimeoutMS") diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/Stream.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/Stream.kt index 5c86dc26580b..056df5a52d9a 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/Stream.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/Stream.kt @@ -37,11 +37,11 @@ open class Stream( /** * For sending data to an associated stream. By default, the length sent is the - * **[ByteBuffer.capacity]**. However, the length will rather be **[ByteBuffer.position]** - * if the Stream was configured to do so - see **[StreamPrototype.useByteBufferPosition]**. + * **[ByteBuffer.capacity]**. However, the length will rather be **[ByteBuffer.position]** if the + * Stream was configured to do so - see **[StreamPrototype.useByteBufferPosition]**. * - * Note: the provided ByteBuffer won't be mutated in any case. On the other hand, until the - * stream is closed, any further mutations may lead to an unpredictable outcome. + * Note: the provided ByteBuffer won't be mutated in any case. On the other hand, until the stream + * is closed, any further mutations may lead to an unpredictable outcome. * * @param data Data to send over the stream. * @return This stream, for chaining syntax. @@ -63,11 +63,11 @@ open class Stream( /** * Close the stream with a data frame. By default, the length sent is the - * **[ByteBuffer.capacity]**. However, the length will rather be **[ByteBuffer.position]** - * if the Stream was configured to do so - see **[StreamPrototype.useByteBufferPosition]**. + * **[ByteBuffer.capacity]**. However, the length will rather be **[ByteBuffer.position]** if the + * Stream was configured to do so - see **[StreamPrototype.useByteBufferPosition]**. * - * Note: the provided ByteBuffer won't be mutated in any case. On the other hand, until the - * stream is closed, any further mutations may lead to an unpredictable outcome. + * Note: the provided ByteBuffer won't be mutated in any case. On the other hand, until the stream + * is closed, any further mutations may lead to an unpredictable outcome. * * @param data Data with which to close the stream. */ @@ -76,9 +76,7 @@ open class Stream( underlyingStream.sendData(data, length, true) } - /** - * Cancel the stream. - */ + /** Cancel the stream. */ open fun cancel() { underlyingStream.cancel() } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamCallbacks.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamCallbacks.kt index 9bd34c3579e3..e5668eb28b49 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamCallbacks.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamCallbacks.kt @@ -7,28 +7,26 @@ import java.nio.ByteBuffer import java.util.concurrent.Executor /** - * A collection of platform-level callbacks that are specified by consumers - * who wish to interact with streams. + * A collection of platform-level callbacks that are specified by consumers who wish to interact + * with streams. * * `StreamCallbacks` are bridged through to `EnvoyHTTPCallbacks` to communicate with the engine. */ internal class StreamCallbacks { - var onHeaders: ( - (headers: ResponseHeaders, endStream: Boolean, streamIntel: StreamIntel) -> Unit - )? = null + var onHeaders: + ((headers: ResponseHeaders, endStream: Boolean, streamIntel: StreamIntel) -> Unit)? = + null var onData: ((data: ByteBuffer, endStream: Boolean, streamIntel: StreamIntel) -> Unit)? = null var onTrailers: ((trailers: ResponseTrailers, streamIntel: StreamIntel) -> Unit)? = null var onCancel: ((finalStreamIntel: FinalStreamIntel) -> Unit)? = null - var onError: ( - (error: EnvoyError, finalStreamIntel: FinalStreamIntel) -> Unit - )? = null + var onError: ((error: EnvoyError, finalStreamIntel: FinalStreamIntel) -> Unit)? = null var onSendWindowAvailable: ((streamIntel: StreamIntel) -> Unit)? = null var onComplete: ((finalStreamIntel: FinalStreamIntel) -> Unit)? = null } /** - * Class responsible for bridging between the platform-level `StreamCallbacks` and the - * engine's `EnvoyHTTPCallbacks`. + * Class responsible for bridging between the platform-level `StreamCallbacks` and the engine's + * `EnvoyHTTPCallbacks`. */ internal class EnvoyHTTPCallbacksAdapter( private val executor: Executor, diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamClient.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamClient.kt index 497c4a8f91d8..bf4026d74901 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamClient.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamClient.kt @@ -1,8 +1,6 @@ package io.envoyproxy.envoymobile -/** - * Client used to create HTTP streams. - */ +/** Client used to create HTTP streams. */ interface StreamClient { /** * Create a new stream prototype which can be used to start streams. diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamClientImpl.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamClientImpl.kt index ebc582b47c79..ad83cfad32ca 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamClientImpl.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamClientImpl.kt @@ -2,12 +2,8 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.EnvoyEngine -/** - * Envoy implementation of `StreamClient`. - */ -internal class StreamClientImpl constructor( - internal val engine: EnvoyEngine -) : StreamClient { +/** Envoy implementation of `StreamClient`. */ +internal class StreamClientImpl constructor(internal val engine: EnvoyEngine) : StreamClient { override fun newStreamPrototype() = StreamPrototype(engine) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamIntel.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamIntel.kt index 29238d361bc0..eb8266ce3e51 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamIntel.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamIntel.kt @@ -4,15 +4,13 @@ import io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel /** * Exposes internal HTTP stream metrics, context, and other details. + * * @param streamId An internal identifier for the stream. -1 if not set. * @param connectionId An internal identifier for the connection carrying the stream. -1 if not set. - * @param attemptCount The number of internal attempts to carry out a request/operation. 0 if - * not set. + * @param attemptCount The number of internal attempts to carry out a request/operation. 0 if not + * set. */ -open class StreamIntel constructor( - val streamId: Long, - val connectionId: Long, - val attemptCount: Long -) { +open class StreamIntel +constructor(val streamId: Long, val connectionId: Long, val attemptCount: Long) { constructor(base: EnvoyStreamIntel) : this(base.streamId, base.connectionId, base.attemptCount) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt index 15f4f68b225f..ffa0ca366d64 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt @@ -8,8 +8,8 @@ import java.util.concurrent.Executors /** * A type representing a stream that has not yet been started. * - * Constructed via `StreamClient`, and used to assign response callbacks - * prior to starting an `Stream` by calling `start()`. + * Constructed via `StreamClient`, and used to assign response callbacks prior to starting an + * `Stream` by calling `start()`. * * @param engine Engine to use for starting streams. */ @@ -26,17 +26,14 @@ open class StreamPrototype(private val engine: EnvoyEngine) { * @return The new stream. */ open fun start(executor: Executor = Executors.newSingleThreadExecutor()): Stream { - val engineStream = engine.startStream( - createCallbacks(executor), - explicitFlowControl, - minDeliverySize - ) + val engineStream = + engine.startStream(createCallbacks(executor), explicitFlowControl, minDeliverySize) return Stream(engineStream, useByteBufferPosition) } /** - * Sets min delivery: data will be buffered in the C++ layer until the min - * delivery length or end stream is read. + * Sets min delivery: data will be buffered in the C++ layer until the min delivery length or end + * stream is read. * * @param value set the minimum delivery size fo for this stream * @return This stream, for chaining syntax. @@ -46,7 +43,6 @@ open class StreamPrototype(private val engine: EnvoyEngine) { return this } - /** * Allows explicit flow control to be enabled. When flow control is enabled, the owner of a stream * is responsible for providing a buffer to receive response body data. If the buffer is smaller @@ -75,11 +71,11 @@ open class StreamPrototype(private val engine: EnvoyEngine) { } /** - * Specify a callback for when response headers are received by the stream. - * If `endStream` is `true`, the stream is complete, pending an onComplete callback. + * Specify a callback for when response headers are received by the stream. If `endStream` is + * `true`, the stream is complete, pending an onComplete callback. * - * @param closure Closure which will receive the headers and flag indicating if the stream - * is headers-only. + * @param closure Closure which will receive the headers and flag indicating if the stream is + * headers-only. * @return This stream, for chaining syntax. */ fun setOnResponseHeaders( @@ -90,11 +86,11 @@ open class StreamPrototype(private val engine: EnvoyEngine) { } /** - * Specify a callback for when a data frame is received by the stream. - * If `endStream` is `true`, the stream is complete, pending an onComplete callback. + * Specify a callback for when a data frame is received by the stream. If `endStream` is `true`, + * the stream is complete, pending an onComplete callback. * - * @param closure Closure which will receive the data and flag indicating whether this - * is the last data frame. + * @param closure Closure which will receive the data and flag indicating whether this is the last + * data frame. * @return This stream, for chaining syntax. */ fun setOnResponseData( @@ -105,8 +101,8 @@ open class StreamPrototype(private val engine: EnvoyEngine) { } /** - * Specify a callback for when trailers are received by the stream. - * If the closure is called, the stream is complete, pending an onComplete callback. + * Specify a callback for when trailers are received by the stream. If the closure is called, the + * stream is complete, pending an onComplete callback. * * @param closure Closure which will receive the trailers. * @return This stream, for chaining syntax. @@ -119,62 +115,52 @@ open class StreamPrototype(private val engine: EnvoyEngine) { } /** - * Specify a callback for when an internal Envoy exception occurs with the stream. - * If the closure is called, the stream is complete. + * Specify a callback for when an internal Envoy exception occurs with the stream. If the closure + * is called, the stream is complete. * * @param closure Closure which will be called when an error occurs. * @return This stream, for chaining syntax. */ fun setOnError( - closure: ( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) -> Unit + closure: (error: EnvoyError, finalStreamIntel: FinalStreamIntel) -> Unit ): StreamPrototype { callbacks.onError = closure return this } -/** - * Specify a callback for when a stream is complete. - * If the closure is called, the stream is complete. + /** + * Specify a callback for when a stream is complete. If the closure is called, the stream is + * complete. * * @param closure Closure which will be called when an error occurs. * @return This stream, for chaining syntax. */ - fun setOnComplete( - closure: (finalStreamIntel: FinalStreamIntel) -> Unit - ): StreamPrototype { + fun setOnComplete(closure: (finalStreamIntel: FinalStreamIntel) -> Unit): StreamPrototype { callbacks.onComplete = closure return this } /** - * Specify a callback for when the stream is canceled. - * If the closure is called, the stream is complete. + * Specify a callback for when the stream is canceled. If the closure is called, the stream is + * complete. * * @param closure Closure which will be called when the stream is canceled. * @return This stream, for chaining syntax. */ - fun setOnCancel( - closure: (finalStreamIntel: FinalStreamIntel) -> Unit - ): StreamPrototype { + fun setOnCancel(closure: (finalStreamIntel: FinalStreamIntel) -> Unit): StreamPrototype { callbacks.onCancel = closure return this } /** - * Specify a callback for when additional send window becomes available. - * This is only ever called when the library is in explicit flow control mode. When enabled, - * the issuer should wait for this callback after calling sendData, before making another call - * to sendData. + * Specify a callback for when additional send window becomes available. This is only ever called + * when the library is in explicit flow control mode. When enabled, the issuer should wait for + * this callback after calling sendData, before making another call to sendData. * * @param closure Closure which will be called when additional send window becomes available. * @return This stream, for chaining syntax. */ - fun setOnSendWindowAvailable( - closure: (streamIntel: StreamIntel) -> Unit - ): StreamPrototype { + fun setOnSendWindowAvailable(closure: (streamIntel: StreamIntel) -> Unit): StreamPrototype { callbacks.onSendWindowAvailable = closure return this } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/StringAccessor.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/StringAccessor.kt index 0336681bd38a..8c24e50a8776 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/StringAccessor.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/StringAccessor.kt @@ -2,23 +2,19 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor -/** - * `StringAccessor` is bridged through to `EnvoyStringAccessor` to communicate with the engine. - */ -class StringAccessor constructor ( - /** - * Accessor for a string exposed by a platform. - */ +/** `StringAccessor` is bridged through to `EnvoyStringAccessor` to communicate with the engine. */ +class StringAccessor +constructor( + /** Accessor for a string exposed by a platform. */ val getEnvoyString: (() -> String) ) /** - * Class responsible for bridging between the platform-level `StringAccessor` and the - * engine's `EnvoyStringAccessor`. + * Class responsible for bridging between the platform-level `StringAccessor` and the engine's + * `EnvoyStringAccessor`. */ -internal class EnvoyStringAccessorAdapter( - private val callbacks: StringAccessor -) : EnvoyStringAccessor { +internal class EnvoyStringAccessorAdapter(private val callbacks: StringAccessor) : + EnvoyStringAccessor { override fun getEnvoyString(): String { return callbacks.getEnvoyString() } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/Trailers.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/Trailers.kt index 6742fdaebb06..b056c8aadbef 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/Trailers.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/Trailers.kt @@ -1,8 +1,8 @@ package io.envoyproxy.envoymobile /** - * Base class representing trailers data structures. - * To instantiate new instances see `{Request|Response}TrailersBuilder`. + * Base class representing trailers data structures. To instantiate new instances see + * `{Request|Response}TrailersBuilder`. */ open class Trailers : Headers { /** @@ -10,8 +10,9 @@ open class Trailers : Headers { * * @param trailers: Trailers to set. */ - protected constructor(trailers: Map>) - : super(HeadersContainer.create(trailers)) + protected constructor( + trailers: Map> + ) : super(HeadersContainer.create(trailers)) protected constructor(container: HeadersContainer) : super(container) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/android/SharedPreferencesStore.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/android/SharedPreferencesStore.kt index 783f12f1de0a..7873a204e387 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/android/SharedPreferencesStore.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/android/SharedPreferencesStore.kt @@ -1,12 +1,9 @@ package io.envoyproxy.envoymobile.android import android.content.SharedPreferences - import io.envoyproxy.envoymobile.KeyValueStore -/** - * Simple implementation of a `KeyValueStore` leveraging `SharedPreferences` for persistence. - */ +/** Simple implementation of a `KeyValueStore` leveraging `SharedPreferences` for persistence. */ class SharedPreferencesStore(sharedPreferences: SharedPreferences) : KeyValueStore { private val preferences = sharedPreferences private val editor = sharedPreferences.edit() diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/AsyncRequestFilter.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/AsyncRequestFilter.kt index 70f52b029602..abfc8d237a71 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/AsyncRequestFilter.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/AsyncRequestFilter.kt @@ -16,11 +16,11 @@ interface AsyncRequestFilter : RequestFilter { /** * Invoked explicitly in response to an asynchronous `resumeRequest()` callback when filter - * iteration has been stopped. The parameters passed to this invocation will be a snapshot - * of any stream state that has not yet been forwarded along the filter chain. + * iteration has been stopped. The parameters passed to this invocation will be a snapshot of any + * stream state that has not yet been forwarded along the filter chain. * - * As with other filter invocations, this will be called on Envoy's main thread, and thus - * no additional synchronization is required between this and other invocations. + * As with other filter invocations, this will be called on Envoy's main thread, and thus no + * additional synchronization is required between this and other invocations. * * @param headers: Headers, if `StopIteration` was returned from `onRequestHeaders`. * @param data: Any data that has been buffered where `StopIterationAndBuffer` was returned. diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/AsyncResponseFilter.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/AsyncResponseFilter.kt index b135ce7739a3..2765511449bb 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/AsyncResponseFilter.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/AsyncResponseFilter.kt @@ -16,11 +16,11 @@ interface AsyncResponseFilter : ResponseFilter { /** * Invoked explicitly in response to an asynchronous `resumeResponse()` callback when filter - * iteration has been stopped. The parameters passed to this invocation will be a snapshot - * of any stream state that has not yet been forwarded along the filter chain. + * iteration has been stopped. The parameters passed to this invocation will be a snapshot of any + * stream state that has not yet been forwarded along the filter chain. * - * As with other filter invocations, this will be called on Envoy's main thread, and thus - * no additional synchronization is required between this and other invocations. + * As with other filter invocations, this will be called on Envoy's main thread, and thus no + * additional synchronization is required between this and other invocations. * * @param headers: Headers, if `StopIteration` was returned from `onResponseHeaders`. * @param data: Any data that has been buffered where `StopIterationAndBuffer` was returned. diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/Filter.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/Filter.kt index ba9d419e2928..fe76f098a41b 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/Filter.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/Filter.kt @@ -10,99 +10,161 @@ import java.nio.ByteBuffer /* * Interface representing a filter. See `RequestFilter` and `ResponseFilter` for more details. */ -@Suppress("EmptyClassBlock") -interface Filter +@Suppress("EmptyClassBlock") interface Filter -internal class FilterFactory( - private val filterName: String, - private val factory: () -> Filter -) : EnvoyHTTPFilterFactory { +internal class FilterFactory(private val filterName: String, private val factory: () -> Filter) : + EnvoyHTTPFilterFactory { override fun getFilterName(): String { return filterName } - override fun create(): EnvoyHTTPFilter { return EnvoyHTTPFilterAdapter(factory()) } + override fun create(): EnvoyHTTPFilter { + return EnvoyHTTPFilterAdapter(factory()) + } } -internal class EnvoyHTTPFilterAdapter( - private val filter: Filter -) : EnvoyHTTPFilter { +internal class EnvoyHTTPFilterAdapter(private val filter: Filter) : EnvoyHTTPFilter { - override fun onRequestHeaders(headers: Map>, endStream: Boolean, streamIntel: EnvoyStreamIntel): Array { + override fun onRequestHeaders( + headers: Map>, + endStream: Boolean, + streamIntel: EnvoyStreamIntel + ): Array { (filter as? RequestFilter)?.let { requestFilter -> - val result = requestFilter.onRequestHeaders(RequestHeaders(headers), endStream, StreamIntel(streamIntel)) + val result = + requestFilter.onRequestHeaders(RequestHeaders(headers), endStream, StreamIntel(streamIntel)) return when (result) { - is FilterHeadersStatus.Continue -> arrayOf(result.status, result.headers.caseSensitiveHeaders()) - is FilterHeadersStatus.StopIteration -> arrayOf(result.status, emptyMap>()) + is FilterHeadersStatus.Continue -> + arrayOf(result.status, result.headers.caseSensitiveHeaders()) + is FilterHeadersStatus.StopIteration -> + arrayOf(result.status, emptyMap>()) } } return arrayOf(0, headers) } - override fun onResponseHeaders(headers: Map>, endStream: Boolean, streamIntel: EnvoyStreamIntel): Array { + override fun onResponseHeaders( + headers: Map>, + endStream: Boolean, + streamIntel: EnvoyStreamIntel + ): Array { (filter as? ResponseFilter)?.let { responseFilter -> - val result = responseFilter.onResponseHeaders(ResponseHeaders(headers), endStream, StreamIntel(streamIntel)) + val result = + responseFilter.onResponseHeaders( + ResponseHeaders(headers), + endStream, + StreamIntel(streamIntel) + ) return when (result) { - is FilterHeadersStatus.Continue -> arrayOf(result.status, result.headers.caseSensitiveHeaders()) - is FilterHeadersStatus.StopIteration -> arrayOf(result.status, emptyMap>()) + is FilterHeadersStatus.Continue -> + arrayOf(result.status, result.headers.caseSensitiveHeaders()) + is FilterHeadersStatus.StopIteration -> + arrayOf(result.status, emptyMap>()) } } return arrayOf(0, headers) } - override fun onRequestData(data: ByteBuffer, endStream: Boolean, streamIntel: EnvoyStreamIntel): Array { + override fun onRequestData( + data: ByteBuffer, + endStream: Boolean, + streamIntel: EnvoyStreamIntel + ): Array { (filter as? RequestFilter)?.let { requestFilter -> val result = requestFilter.onRequestData(data, endStream, StreamIntel(streamIntel)) return when (result) { is FilterDataStatus.Continue<*> -> arrayOf(result.status, result.data) - is FilterDataStatus.StopIterationAndBuffer<*> -> arrayOf(result.status, ByteBuffer.allocate(0)) - is FilterDataStatus.StopIterationNoBuffer<*> -> arrayOf(result.status, ByteBuffer.allocate(0)) - is FilterDataStatus.ResumeIteration<*> -> arrayOf(result.status, result.data, result.headers?.caseSensitiveHeaders()) + is FilterDataStatus.StopIterationAndBuffer<*> -> + arrayOf(result.status, ByteBuffer.allocate(0)) + is FilterDataStatus.StopIterationNoBuffer<*> -> + arrayOf(result.status, ByteBuffer.allocate(0)) + is FilterDataStatus.ResumeIteration<*> -> + arrayOf(result.status, result.data, result.headers?.caseSensitiveHeaders()) } } return arrayOf(0, data) } - override fun onResponseData(data: ByteBuffer, endStream: Boolean, streamIntel: EnvoyStreamIntel): Array { + override fun onResponseData( + data: ByteBuffer, + endStream: Boolean, + streamIntel: EnvoyStreamIntel + ): Array { (filter as? ResponseFilter)?.let { responseFilter -> val result = responseFilter.onResponseData(data, endStream, StreamIntel(streamIntel)) return when (result) { is FilterDataStatus.Continue<*> -> arrayOf(result.status, result.data) - is FilterDataStatus.StopIterationAndBuffer<*> -> arrayOf(result.status, ByteBuffer.allocate(0)) - is FilterDataStatus.StopIterationNoBuffer<*> -> arrayOf(result.status, ByteBuffer.allocate(0)) - is FilterDataStatus.ResumeIteration<*> -> arrayOf(result.status, result.data, result.headers?.caseSensitiveHeaders()) + is FilterDataStatus.StopIterationAndBuffer<*> -> + arrayOf(result.status, ByteBuffer.allocate(0)) + is FilterDataStatus.StopIterationNoBuffer<*> -> + arrayOf(result.status, ByteBuffer.allocate(0)) + is FilterDataStatus.ResumeIteration<*> -> + arrayOf(result.status, result.data, result.headers?.caseSensitiveHeaders()) } } return arrayOf(0, data) } - override fun onRequestTrailers(trailers: Map>, streamIntel: EnvoyStreamIntel): Array { + override fun onRequestTrailers( + trailers: Map>, + streamIntel: EnvoyStreamIntel + ): Array { (filter as? RequestFilter)?.let { requestFilter -> - val result = requestFilter.onRequestTrailers(RequestTrailers(trailers), StreamIntel(streamIntel)) + val result = + requestFilter.onRequestTrailers(RequestTrailers(trailers), StreamIntel(streamIntel)) return when (result) { - is FilterTrailersStatus.Continue<*, *> -> arrayOf(result.status, result.trailers.caseSensitiveHeaders()) - is FilterTrailersStatus.StopIteration<*, *> -> arrayOf(result.status, emptyMap>()) - is FilterTrailersStatus.ResumeIteration<*, *> -> arrayOf(result.status, result.trailers.caseSensitiveHeaders(), result.headers?.caseSensitiveHeaders(), result.data) + is FilterTrailersStatus.Continue<*, *> -> + arrayOf(result.status, result.trailers.caseSensitiveHeaders()) + is FilterTrailersStatus.StopIteration<*, *> -> + arrayOf(result.status, emptyMap>()) + is FilterTrailersStatus.ResumeIteration<*, *> -> + arrayOf( + result.status, + result.trailers.caseSensitiveHeaders(), + result.headers?.caseSensitiveHeaders(), + result.data + ) } } return arrayOf(0, trailers) } - override fun onResponseTrailers(trailers: Map>, streamIntel: EnvoyStreamIntel): Array { + override fun onResponseTrailers( + trailers: Map>, + streamIntel: EnvoyStreamIntel + ): Array { (filter as? ResponseFilter)?.let { responseFilter -> - val result = responseFilter.onResponseTrailers(ResponseTrailers(trailers), StreamIntel(streamIntel)) + val result = + responseFilter.onResponseTrailers(ResponseTrailers(trailers), StreamIntel(streamIntel)) return when (result) { - is FilterTrailersStatus.Continue<*, *> -> arrayOf(result.status, result.trailers.caseSensitiveHeaders()) - is FilterTrailersStatus.StopIteration<*, *> -> arrayOf(result.status, emptyMap>()) - is FilterTrailersStatus.ResumeIteration<*, *> -> arrayOf(result.status, result.trailers.caseSensitiveHeaders(), result.headers?.caseSensitiveHeaders(), result.data) + is FilterTrailersStatus.Continue<*, *> -> + arrayOf(result.status, result.trailers.caseSensitiveHeaders()) + is FilterTrailersStatus.StopIteration<*, *> -> + arrayOf(result.status, emptyMap>()) + is FilterTrailersStatus.ResumeIteration<*, *> -> + arrayOf( + result.status, + result.trailers.caseSensitiveHeaders(), + result.headers?.caseSensitiveHeaders(), + result.data + ) } } return arrayOf(0, trailers) } - override fun onError(errorCode: Int, message: String, attemptCount: Int, streamIntel: EnvoyStreamIntel, finalStreamIntel: EnvoyFinalStreamIntel) { + override fun onError( + errorCode: Int, + message: String, + attemptCount: Int, + streamIntel: EnvoyStreamIntel, + finalStreamIntel: EnvoyFinalStreamIntel + ) { (filter as? ResponseFilter)?.let { responseFilter -> - responseFilter.onError(EnvoyError(errorCode, message, attemptCount), FinalStreamIntel(streamIntel, finalStreamIntel)) + responseFilter.onError( + EnvoyError(errorCode, message, attemptCount), + FinalStreamIntel(streamIntel, finalStreamIntel) + ) } } @@ -124,17 +186,30 @@ internal class EnvoyHTTPFilterAdapter( } } - override fun onResumeRequest(headers: Map>?, data: ByteBuffer?, trailers: Map>?, endStream: Boolean, streamIntel: EnvoyStreamIntel): Array { + override fun onResumeRequest( + headers: Map>?, + data: ByteBuffer?, + trailers: Map>?, + endStream: Boolean, + streamIntel: EnvoyStreamIntel + ): Array { (filter as? AsyncRequestFilter)?.let { asyncRequestFilter -> - val result = asyncRequestFilter.onResumeRequest( - headers?.let(::RequestHeaders), - data, - trailers?.let(::RequestTrailers), - endStream, - StreamIntel(streamIntel) - ) + val result = + asyncRequestFilter.onResumeRequest( + headers?.let(::RequestHeaders), + data, + trailers?.let(::RequestTrailers), + endStream, + StreamIntel(streamIntel) + ) return when (result) { - is FilterResumeStatus.ResumeIteration<*, *> -> arrayOf(result.status, result.headers?.caseSensitiveHeaders(), result.data, result.trailers?.caseSensitiveHeaders()) + is FilterResumeStatus.ResumeIteration<*, *> -> + arrayOf( + result.status, + result.headers?.caseSensitiveHeaders(), + result.data, + result.trailers?.caseSensitiveHeaders() + ) } } return arrayOf(-1, headers, data, trailers) @@ -146,17 +221,30 @@ internal class EnvoyHTTPFilterAdapter( } } - override fun onResumeResponse(headers: Map>?, data: ByteBuffer?, trailers: Map>?, endStream: Boolean, streamIntel: EnvoyStreamIntel): Array { + override fun onResumeResponse( + headers: Map>?, + data: ByteBuffer?, + trailers: Map>?, + endStream: Boolean, + streamIntel: EnvoyStreamIntel + ): Array { (filter as? AsyncResponseFilter)?.let { asyncResponseFilter -> - val result = asyncResponseFilter.onResumeResponse( - headers?.let(::ResponseHeaders), - data, - trailers?.let(::ResponseTrailers), - endStream, - StreamIntel(streamIntel) - ) + val result = + asyncResponseFilter.onResumeResponse( + headers?.let(::ResponseHeaders), + data, + trailers?.let(::ResponseTrailers), + endStream, + StreamIntel(streamIntel) + ) return when (result) { - is FilterResumeStatus.ResumeIteration<*, *> -> arrayOf(result.status, result.headers?.caseSensitiveHeaders(), result.data, result.trailers?.caseSensitiveHeaders()) + is FilterResumeStatus.ResumeIteration<*, *> -> + arrayOf( + result.status, + result.headers?.caseSensitiveHeaders(), + result.data, + result.trailers?.caseSensitiveHeaders() + ) } } return arrayOf(-1, headers, data, trailers) diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterDataStatus.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterDataStatus.kt index 737671837fb7..fe864381e4d0 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterDataStatus.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterDataStatus.kt @@ -5,9 +5,7 @@ import java.nio.ByteBuffer /* * Status to be returned by filters when transmitting or receiving data. */ -sealed class FilterDataStatus( - val status: Int -) { +sealed class FilterDataStatus(val status: Int) { /** * Continue filter chain iteration. If headers have not yet been sent to the next filter, they * will be sent first via `onRequestHeaders()`/`onResponseHeaders()`. @@ -24,8 +22,7 @@ sealed class FilterDataStatus( * been buffered so far. * * Returning `ResumeIteration` from another filter invocation or calling - * `resumeRequest()`/`resumeResponse()` MUST be called when continued filter iteration is - * desired. + * `resumeRequest()`/`resumeResponse()` MUST be called when continued filter iteration is desired. * * This should be called by filters which must parse a larger block of the incoming data before * continuing processing. @@ -33,14 +30,12 @@ sealed class FilterDataStatus( class StopIterationAndBuffer : FilterDataStatus(1) /** - * Do not iterate to any of the remaining filters in the chain, and do not internally buffer - * data. + * Do not iterate to any of the remaining filters in the chain, and do not internally buffer data. * * `onData` will continue to be called with new chunks of data. * * Returning `ResumeIteration` from another filter invocation or calling - * `resumeRequest()`/`resumeResponse()` MUST be called when continued filter iteration is - * desired. + * `resumeRequest()`/`resumeResponse()` MUST be called when continued filter iteration is desired. * * This may be called by filters which must parse a larger block of the incoming data before * continuing processing, and will handle their own buffering. @@ -51,12 +46,13 @@ sealed class FilterDataStatus( * Resume previously-stopped iteration, possibly forwarding headers if iteration was stopped * during an on*Headers invocation. * - * It is an error to return `ResumeIteration` if iteration is not currently stopped, and it is - * an error to include headers if headers have already been forwarded to the next filter - * (i.e. iteration was stopped during an on*Data invocation instead of on*Headers). + * It is an error to return `ResumeIteration` if iteration is not currently stopped, and it is an + * error to include headers if headers have already been forwarded to the next filter (i.e. + * iteration was stopped during an on*Data invocation instead of on*Headers). * * @param headers: Headers to be forwarded (if needed). * @param data: Data to be forwarded. */ - class ResumeIteration(val headers: T?, val data: ByteBuffer) : FilterDataStatus(-1) + class ResumeIteration(val headers: T?, val data: ByteBuffer) : + FilterDataStatus(-1) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterHeadersStatus.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterHeadersStatus.kt index c50bca57e2e2..5b33e477da4e 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterHeadersStatus.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterHeadersStatus.kt @@ -3,9 +3,7 @@ package io.envoyproxy.envoymobile /* * Status to be returned by filters when transmitting or receiving headers. */ -sealed class FilterHeadersStatus( - val status: Int -) { +sealed class FilterHeadersStatus(val status: Int) { /** * Continue filter chain iteration, passing the provided headers through. * @@ -17,8 +15,7 @@ sealed class FilterHeadersStatus( * Do not iterate to any of the remaining filters in the chain with headers. * * Returning `ResumeIteration` from another filter invocation or calling - * `resumeRequest()`/`resumeResponse()` MUST occur when continued filter iteration is - * desired. + * `resumeRequest()`/`resumeResponse()` MUST occur when continued filter iteration is desired. */ class StopIteration : FilterHeadersStatus(1) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterResumeStatus.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterResumeStatus.kt index 933eed5ff8aa..2544a7a6fe56 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterResumeStatus.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterResumeStatus.kt @@ -5,18 +5,16 @@ import java.nio.ByteBuffer /* * Status to be returned by filters after resuming iteration asynchronously. */ -sealed class FilterResumeStatus( - val status: Int -) { +sealed class FilterResumeStatus(val status: Int) { /** - * Resume previously-stopped iteration, potentially forwarding headers, data, and/or trailers - * that have not yet been passed along the filter chain. + * Resume previously-stopped iteration, potentially forwarding headers, data, and/or trailers that + * have not yet been passed along the filter chain. * - * It is an error to return ResumeIteration if iteration is not currently stopped, and it is - * an error to include headers if headers have already been forwarded to the next filter - * (i.e. iteration was stopped during an on*Data invocation instead of on*Headers). It is also - * an error to include data or trailers if `endStream` was previously set or if trailers have - * already been forwarded. + * It is an error to return ResumeIteration if iteration is not currently stopped, and it is an + * error to include headers if headers have already been forwarded to the next filter (i.e. + * iteration was stopped during an on*Data invocation instead of on*Headers). It is also an error + * to include data or trailers if `endStream` was previously set or if trailers have already been + * forwarded. * * @param headers: Headers to be forwarded (if needed). * @param data: Data to be forwarded (if needed). diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterTrailersStatus.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterTrailersStatus.kt index e4a1fe93d793..011ae22d2ff5 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterTrailersStatus.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/FilterTrailersStatus.kt @@ -5,9 +5,7 @@ import java.nio.ByteBuffer /* * Status to be returned by filters when transmitting or receiving trailers. */ -sealed class FilterTrailersStatus( - val status: Int -) { +sealed class FilterTrailersStatus(val status: Int) { /** * Continue filter chain iteration, passing the provided trailers through. * @@ -20,8 +18,8 @@ sealed class FilterTrailersStatus( * * Because trailers are by definition the last HTTP entity of a request or response, only * asynchronous filters support resumption after returning `StopIteration` from on*Trailers. - * Calling `resumeRequest()`/`resumeResponse()` MUST occur if continued filter iteration - * is desired. + * Calling `resumeRequest()`/`resumeResponse()` MUST occur if continued filter iteration is + * desired. */ class StopIteration : FilterTrailersStatus(1) @@ -29,9 +27,9 @@ sealed class FilterTrailersStatus( * Resume previously-stopped iteration, possibly forwarding headers and data if iteration was * stopped during an on*Headers or on*Data invocation. * - * It is an error to return `ResumeIteration` if iteration is not currently stopped, and it is - * an error to include headers if headers have already been forwarded to the next filter - * (i.e. iteration was stopped during an on*Data invocation instead of on*Headers). + * It is an error to return `ResumeIteration` if iteration is not currently stopped, and it is an + * error to include headers if headers have already been forwarded to the next filter (i.e. + * iteration was stopped during an on*Data invocation instead of on*Headers). * * @param headers: Headers to be forwarded (if needed). * @param data: Data to be forwarded (if needed). diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilter.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilter.kt index ef02dc065fc2..ccbcfbea6aec 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilter.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilter.kt @@ -11,39 +11,47 @@ interface RequestFilter : Filter { * * Filters may mutate or delay the request headers. * - * @param headers: The current request headers. - * @param endStream: Whether this is a headers-only request. + * @param headers: The current request headers. + * @param endStream: Whether this is a headers-only request. * @param streamIntel: Internal HTTP stream metrics, context, and other details. * * @return: The header status containing headers with which to continue or buffer. */ - fun onRequestHeaders(headers: RequestHeaders, endStream: Boolean, streamIntel: StreamIntel): - FilterHeadersStatus + fun onRequestHeaders( + headers: RequestHeaders, + endStream: Boolean, + streamIntel: StreamIntel + ): FilterHeadersStatus /** * Called any number of times whenever body data is sent. * * Filters may mutate or buffer (defer and concatenate) the data. * - * @param body: The outbound body data chunk. - * @param endStream: Whether this is the last data frame. + * @param body: The outbound body data chunk. + * @param endStream: Whether this is the last data frame. * @param streamIntel: Internal HTTP stream metrics, context, and other details. * * @return: The data status containing body with which to continue or buffer. */ - fun onRequestData(body: ByteBuffer, endStream: Boolean, streamIntel: StreamIntel): - FilterDataStatus + fun onRequestData( + body: ByteBuffer, + endStream: Boolean, + streamIntel: StreamIntel + ): FilterDataStatus /** * Called at most once when the request is closed from the client with trailers. * * Filters may mutate or delay the trailers. Note trailers imply the stream has ended. * - * @param trailers: The outbound trailers. + * @param trailers: The outbound trailers. * @param streamIntel: Internal HTTP stream metrics, context, and other details. * * @return: The trailer status containing body with which to continue or buffer. */ - fun onRequestTrailers(trailers: RequestTrailers, streamIntel: StreamIntel): - FilterTrailersStatus + fun onRequestTrailers( + trailers: RequestTrailers, + streamIntel: StreamIntel + ): FilterTrailersStatus } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilterCallbacks.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilterCallbacks.kt index 47ef5c8e36fa..013ad19976e2 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilterCallbacks.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilterCallbacks.kt @@ -9,8 +9,7 @@ interface RequestFilterCallbacks { * * This will result in an `onResumeRequest()` callback on the RequestFilter. * - * If the request is not complete, the filter may receive further `onData()`/`onTrailers()` - * calls. + * If the request is not complete, the filter may receive further `onData()`/`onTrailers()` calls. */ fun resumeRequest() diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilterCallbacksImpl.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilterCallbacksImpl.kt index 823c8166cd82..f49f36736a84 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilterCallbacksImpl.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/RequestFilterCallbacksImpl.kt @@ -2,12 +2,9 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPFilterCallbacks -/** - * Envoy implementation of `RequestFilterCallbacks`. - */ -internal class RequestFilterCallbacksImpl constructor( - internal val callbacks: EnvoyHTTPFilterCallbacks -) : RequestFilterCallbacks { +/** Envoy implementation of `RequestFilterCallbacks`. */ +internal class RequestFilterCallbacksImpl +constructor(internal val callbacks: EnvoyHTTPFilterCallbacks) : RequestFilterCallbacks { override fun resumeRequest() { callbacks.resumeIteration() diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/ResponseFilter.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/ResponseFilter.kt index fa7ce9a5eeeb..e924f016cb8e 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/ResponseFilter.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/ResponseFilter.kt @@ -11,50 +11,57 @@ interface ResponseFilter : Filter { * * Filters may mutate or delay the response headers. * - * @param headers: The current response headers. - * @param endStream: Whether this is a headers-only response. + * @param headers: The current response headers. + * @param endStream: Whether this is a headers-only response. * @param streamIntel: Internal HTTP stream metrics, context, and other details. * * @return: The header status containing headers with which to continue or buffer. */ - fun onResponseHeaders(headers: ResponseHeaders, endStream: Boolean, streamIntel: StreamIntel): - FilterHeadersStatus + fun onResponseHeaders( + headers: ResponseHeaders, + endStream: Boolean, + streamIntel: StreamIntel + ): FilterHeadersStatus /** * Called any number of times whenever body data is received. * * Filters may mutate or buffer (defer and concatenate) the data. * - * @param body: The inbound body data chunk. - * @param endStream: Whether this is the last data frame. + * @param body: The inbound body data chunk. + * @param endStream: Whether this is the last data frame. * @param streamIntel: Internal HTTP stream metrics, context, and other details. * * @return: The data status containing body with which to continue or buffer. */ - fun onResponseData(body: ByteBuffer, endStream: Boolean, streamIntel: StreamIntel): - FilterDataStatus + fun onResponseData( + body: ByteBuffer, + endStream: Boolean, + streamIntel: StreamIntel + ): FilterDataStatus /** * Called at most once when the response is closed from the server with trailers. * * Filters may mutate or delay the trailers. Note trailers imply the stream has ended. * - * @param trailers: The inbound trailers. + * @param trailers: The inbound trailers. * @param streamIntel: Internal HTTP stream metrics, context, and other details. * * @return: The trailer status containing body with which to continue or buffer. */ - fun onResponseTrailers(trailers: ResponseTrailers, streamIntel: StreamIntel): - FilterTrailersStatus + fun onResponseTrailers( + trailers: ResponseTrailers, + streamIntel: StreamIntel + ): FilterTrailersStatus /** * Called at most once when an error within Envoy occurs. * - * Only one of onError, onCancel, or onComplete will be called per stream. - * This should be considered a terminal state, and invalidates any previous attempts to - * `stopIteration{...}`. + * Only one of onError, onCancel, or onComplete will be called per stream. This should be + * considered a terminal state, and invalidates any previous attempts to `stopIteration{...}`. * - * @param error: The error that occurred within Envoy. + * @param error: The error that occurred within Envoy. * @param finalStreamIntel: Final internal HTTP stream metrics, context, and other details. */ fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) @@ -62,20 +69,18 @@ interface ResponseFilter : Filter { /** * Called at most once when the client cancels the stream. * - * Only one of onError, onCancel, or onComplete will be called per stream. - * This should be considered a terminal state, and invalidates any previous attempts to - * `stopIteration{...}`. + * Only one of onError, onCancel, or onComplete will be called per stream. This should be + * considered a terminal state, and invalidates any previous attempts to `stopIteration{...}`. * * @param finalStreamIntel: Final internal HTTP stream metrics, context, and other details. */ fun onCancel(finalStreamIntel: FinalStreamIntel) -/** + /** * Called at most once when the stream completes gracefully. * - * Only one of onError, onCancel, or onComplete will be called per stream. - * This should be considered a terminal state, and invalidates any previous attempts to - * `stopIteration{...}`. + * Only one of onError, onCancel, or onComplete will be called per stream. This should be + * considered a terminal state, and invalidates any previous attempts to `stopIteration{...}`. * * @param finalStreamIntel: Final internal HTTP stream metrics, context, and other details. */ diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/ResponseFilterCallbacksImpl.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/ResponseFilterCallbacksImpl.kt index 95a52310daef..ea74711b1f9d 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/ResponseFilterCallbacksImpl.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/filters/ResponseFilterCallbacksImpl.kt @@ -2,12 +2,9 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPFilterCallbacks -/** - * Envoy implementation of `ResponseFilterCallbacks`. - */ -internal class ResponseFilterCallbacksImpl constructor( - internal val callbacks: EnvoyHTTPFilterCallbacks -) : ResponseFilterCallbacks { +/** Envoy implementation of `ResponseFilterCallbacks`. */ +internal class ResponseFilterCallbacksImpl +constructor(internal val callbacks: EnvoyHTTPFilterCallbacks) : ResponseFilterCallbacks { override fun resumeResponse() { callbacks.resumeIteration() diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCClient.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCClient.kt index 7f4dc6b3f9d0..77f2dcd95523 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCClient.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCClient.kt @@ -8,15 +8,11 @@ internal const val GRPC_PREFIX_LENGTH = 5 * * @param streamClient The stream client to use for gRPC streams. */ -class GRPCClient( - private val streamClient: StreamClient -) { +class GRPCClient(private val streamClient: StreamClient) { /** * Create a new gRPC stream prototype which can be used to start streams. * * @return The new gRPC stream prototype. */ - fun newGRPCStreamPrototype() = GRPCStreamPrototype( - streamClient.newStreamPrototype() - ) + fun newGRPCStreamPrototype() = GRPCStreamPrototype(streamClient.newStreamPrototype()) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCRequestHeadersBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCRequestHeadersBuilder.kt index 51b53dee2272..8b658802ce4a 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCRequestHeadersBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCRequestHeadersBuilder.kt @@ -45,16 +45,21 @@ class GRPCRequestHeadersBuilder : HeadersBuilder { * @param authority The URL authority for the request (i.e., "api.foo.com"). * @param path Path for the RPC (i.e., `/pb.api.v1.Foo/GetBar`). */ - constructor(scheme: String, authority: String, path: String) : super(HeadersContainer( - mapOf>( - ":authority" to mutableListOf(authority), - ":method" to mutableListOf("POST"), - ":path" to mutableListOf(path), - ":scheme" to mutableListOf(scheme), - "content-type" to mutableListOf("application/grpc"), + constructor( + scheme: String, + authority: String, + path: String + ) : super( + HeadersContainer( + mapOf>( + ":authority" to mutableListOf(authority), + ":method" to mutableListOf("POST"), + ":path" to mutableListOf(path), + ":scheme" to mutableListOf(scheme), + "content-type" to mutableListOf("application/grpc"), + ) ) ) - ) /** * Add a specific timeout for the gRPC request. This will be sent in the `grpc-timeout` header. diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStream.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStream.kt index cfa212989f0a..6872c854866d 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStream.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStream.kt @@ -8,9 +8,7 @@ import java.nio.ByteOrder * * Constructed using `GRPCStreamPrototype`, and used to write to the network. */ -class GRPCStream( - private val underlyingStream: Stream -) { +class GRPCStream(private val underlyingStream: Stream) { /** * Send headers over the gRPC stream. * @@ -50,9 +48,7 @@ class GRPCStream( return this } - /** - * Cancel the stream forcefully regardless of whether the peer has more data to send. - */ + /** Cancel the stream forcefully regardless of whether the peer has more data to send. */ fun cancel() { underlyingStream.cancel() } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStreamPrototype.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStreamPrototype.kt index 7b35796c5229..420936572c55 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStreamPrototype.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStreamPrototype.kt @@ -9,14 +9,13 @@ import java.util.concurrent.Executors /** * A type representing a gRPC stream that has not yet been started. * - * Constructed via `GRPCClient`, and used to assign response callbacks - * prior to starting a `GRPCStream` by calling `start()`. + * Constructed via `GRPCClient`, and used to assign response callbacks prior to starting a + * `GRPCStream` by calling `start()`. */ -class GRPCStreamPrototype( - private val underlyingStream: StreamPrototype -) { +class GRPCStreamPrototype(private val underlyingStream: StreamPrototype) { /** * Start a new gRPC stream. + * * @param executor Executor on which to receive callback events. * @return The new gRPC stream. */ @@ -28,7 +27,8 @@ class GRPCStreamPrototype( /** * Specify a callback for when response headers are received by the stream. * - * @param closure Closure which will receive the headers and flag indicating if the stream is headers-only. + * @param closure Closure which will receive the headers and flag indicating if the stream is + * headers-only. * @return This stream, for chaining syntax. */ fun setOnResponseHeaders( @@ -39,8 +39,8 @@ class GRPCStreamPrototype( } /** - * Specify a callback for when a new message has been received by the stream. - * If `endStream` is `true`, the stream is complete. + * Specify a callback for when a new message has been received by the stream. If `endStream` is + * `true`, the stream is complete. * * @param closure Closure which will receive messages on the stream. * @return This stream, for chaining syntax. @@ -50,26 +50,29 @@ class GRPCStreamPrototype( ): GRPCStreamPrototype { val byteBufferedOutputStream = ByteArrayOutputStream() val processor = GRPCMessageProcessor() - var processState: GRPCMessageProcessor.ProcessState = GRPCMessageProcessor.ProcessState.CompressionFlag + var processState: GRPCMessageProcessor.ProcessState = + GRPCMessageProcessor.ProcessState.CompressionFlag underlyingStream.setOnResponseData { byteBuffer, _, streamIntel -> - val byteBufferArray = if (byteBuffer.hasArray()) { - byteBuffer.array() - } else { - val array = ByteArray(byteBuffer.remaining()) - byteBuffer.get(array) - array - } + val byteBufferArray = + if (byteBuffer.hasArray()) { + byteBuffer.array() + } else { + val array = ByteArray(byteBuffer.remaining()) + byteBuffer.get(array) + array + } byteBufferedOutputStream.write(byteBufferArray) - processState = processor.processData(byteBufferedOutputStream, processState, streamIntel, closure) + processState = + processor.processData(byteBufferedOutputStream, processState, streamIntel, closure) } return this } /** - * Specify a callback for when trailers are received by the stream. - * If the closure is called, the stream is complete. + * Specify a callback for when trailers are received by the stream. If the closure is called, the + * stream is complete. * * @param closure Closure which will receive the trailers. * @return This stream, for chaining syntax. @@ -82,8 +85,8 @@ class GRPCStreamPrototype( } /** - * Specify a callback for when an internal Envoy exception occurs with the stream. - * If the closure is called, the stream is complete. + * Specify a callback for when an internal Envoy exception occurs with the stream. If the closure + * is called, the stream is complete. * * @param closure Closure which will be called when an error occurs. * @return This stream, for chaining syntax. @@ -96,24 +99,20 @@ class GRPCStreamPrototype( } /** - * Specify a callback for when the stream is canceled. - * If the closure is called, the stream is complete. + * Specify a callback for when the stream is canceled. If the closure is called, the stream is + * complete. * * @param closure Closure which will be called when the stream is canceled. * @return This stream, for chaining syntax. */ - fun setOnCancel( - closure: (finalStreamIntel: FinalStreamIntel) -> Unit - ): GRPCStreamPrototype { + fun setOnCancel(closure: (finalStreamIntel: FinalStreamIntel) -> Unit): GRPCStreamPrototype { underlyingStream.setOnCancel(closure) return this } } private class GRPCMessageProcessor { - /** - * Represents the process state of the response stream's body data. - */ + /** Represents the process state of the response stream's body data. */ sealed class ProcessState { // Awaiting a gRPC compression flag. object CompressionFlag : ProcessState() @@ -126,8 +125,8 @@ private class GRPCMessageProcessor { } /** - * Recursively processes a buffer of data, buffering it into messages based on state. - * When a message has been fully buffered, `onMessage` will be called with the message. + * Recursively processes a buffer of data, buffering it into messages based on state. When a + * message has been fully buffered, `onMessage` will be called with the message. * * @param bufferedStream The buffer of data from which to determine state and messages. * @param processState The current process state of the buffering. @@ -187,9 +186,7 @@ private class GRPCMessageProcessor { ) bufferedStream.reset() bufferedStream.write( - byteArray.sliceArray( - GRPC_PREFIX_LENGTH + processState.messageLength until byteArray.size - ) + byteArray.sliceArray(GRPC_PREFIX_LENGTH + processState.messageLength until byteArray.size) ) val remainingLength = GRPC_PREFIX_LENGTH + processState.messageLength until byteArray.size diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockEnvoyEngine.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockEnvoyEngine.kt index 554e0a81d882..4f8700e643b9 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockEnvoyEngine.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockEnvoyEngine.kt @@ -5,21 +5,22 @@ import io.envoyproxy.envoymobile.engine.EnvoyEngine import io.envoyproxy.envoymobile.engine.EnvoyHTTPStream import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPCallbacks import io.envoyproxy.envoymobile.engine.types.EnvoyNetworkType -import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor import io.envoyproxy.envoymobile.engine.types.EnvoyStatus +import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor /** * Mock implementation of `EnvoyEngine`. Used internally for testing the bridging layer & mocking. */ internal class MockEnvoyEngine : EnvoyEngine { - override fun runWithConfig(envoyConfiguration: EnvoyConfiguration?, logLevel: String?): EnvoyStatus = EnvoyStatus.ENVOY_SUCCESS + override fun runWithConfig( + envoyConfiguration: EnvoyConfiguration?, + logLevel: String? + ): EnvoyStatus = EnvoyStatus.ENVOY_SUCCESS override fun performRegistration(envoyConfiguration: EnvoyConfiguration) = Unit - override fun runWithYaml( - configurationYAML: String, - logLevel: String - ): EnvoyStatus = EnvoyStatus.ENVOY_SUCCESS + override fun runWithYaml(configurationYAML: String, logLevel: String): EnvoyStatus = + EnvoyStatus.ENVOY_SUCCESS override fun startStream( callbacks: EnvoyHTTPCallbacks?, @@ -31,7 +32,11 @@ internal class MockEnvoyEngine : EnvoyEngine { override fun terminate() = Unit - override fun recordCounterInc(elements: String, tags: MutableMap, count: Int): Int = 0 + override fun recordCounterInc( + elements: String, + tags: MutableMap, + count: Int + ): Int = 0 override fun registerStringAccessor(accessorName: String, accessor: EnvoyStringAccessor): Int = 0 diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStream.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStream.kt index 4f1f54ddf24f..c5ad9839b6ab 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStream.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStream.kt @@ -5,52 +5,105 @@ import io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel import java.nio.ByteBuffer /** - * Mock implementation of `Stream` that also provides an interface for sending - * mocked responses through to the stream's callbacks. Created via `MockStreamPrototype`. + * Mock implementation of `Stream` that also provides an interface for sending mocked responses + * through to the stream's callbacks. Created via `MockStreamPrototype`. */ -class MockStream internal constructor(underlyingStream: MockEnvoyHTTPStream) : Stream(underlyingStream, useByteBufferPosition = false) { +class MockStream internal constructor(underlyingStream: MockEnvoyHTTPStream) : + Stream(underlyingStream, useByteBufferPosition = false) { private val mockStream: MockEnvoyHTTPStream = underlyingStream - private val mockStreamIntel = object : EnvoyStreamIntel { - override fun getStreamId(): Long { return 0 } - override fun getConnectionId(): Long { return 0 } - override fun getAttemptCount(): Long { return 0 } - override fun getConsumedBytesFromResponse(): Long { return 0 } - } + private val mockStreamIntel = + object : EnvoyStreamIntel { + override fun getStreamId(): Long { + return 0 + } - private val mockFinalStreamIntel = object : EnvoyFinalStreamIntel { - override fun getStreamStartMs(): Long { return 0 } - override fun getDnsStartMs(): Long { return 0 } - override fun getDnsEndMs(): Long { return 0 } - override fun getConnectStartMs(): Long { return 0 } - override fun getConnectEndMs(): Long { return 0 } - override fun getSslStartMs(): Long { return 0 } - override fun getSslEndMs(): Long { return 0 } - override fun getSendingStartMs(): Long { return 0 } - override fun getSendingEndMs(): Long { return 0 } - override fun getResponseStartMs(): Long { return 0 } - override fun getStreamEndMs(): Long { return 0 } - override fun getSocketReused(): Boolean { return false } - override fun getSentByteCount(): Long { return 0 } - override fun getReceivedByteCount(): Long { return 0 } - override fun getResponseFlags(): Long { return 0 } - override fun getUpstreamProtocol(): Long { return 0 } - } - /** - * Closure that will be called when request headers are sent. - */ + override fun getConnectionId(): Long { + return 0 + } + + override fun getAttemptCount(): Long { + return 0 + } + + override fun getConsumedBytesFromResponse(): Long { + return 0 + } + } + + private val mockFinalStreamIntel = + object : EnvoyFinalStreamIntel { + override fun getStreamStartMs(): Long { + return 0 + } + + override fun getDnsStartMs(): Long { + return 0 + } + + override fun getDnsEndMs(): Long { + return 0 + } + + override fun getConnectStartMs(): Long { + return 0 + } + + override fun getConnectEndMs(): Long { + return 0 + } + + override fun getSslStartMs(): Long { + return 0 + } + + override fun getSslEndMs(): Long { + return 0 + } + + override fun getSendingStartMs(): Long { + return 0 + } + + override fun getSendingEndMs(): Long { + return 0 + } + + override fun getResponseStartMs(): Long { + return 0 + } + + override fun getStreamEndMs(): Long { + return 0 + } + + override fun getSocketReused(): Boolean { + return false + } + + override fun getSentByteCount(): Long { + return 0 + } + + override fun getReceivedByteCount(): Long { + return 0 + } + + override fun getResponseFlags(): Long { + return 0 + } + + override fun getUpstreamProtocol(): Long { + return 0 + } + } + /** Closure that will be called when request headers are sent. */ var onRequestHeaders: ((headers: RequestHeaders, endStream: Boolean) -> Unit)? = null - /** - * Closure that will be called when request data is sent. - */ + /** Closure that will be called when request data is sent. */ var onRequestData: ((data: ByteBuffer, endStream: Boolean) -> Unit)? = null - /** - * Closure that will be called when request trailers are sent. - */ + /** Closure that will be called when request trailers are sent. */ var onRequestTrailers: ((trailers: RequestTrailers) -> Unit)? = null - /** - * Closure that will be called when the stream is canceled by the client. - */ + /** Closure that will be called when the stream is canceled by the client. */ var onCancel: (() -> Unit)? = null override fun sendHeaders(headers: RequestHeaders, endStream: Boolean): Stream { @@ -104,9 +157,7 @@ class MockStream internal constructor(underlyingStream: MockEnvoyHTTPStream) : S mockStream.callbacks.onTrailers(trailers.caseSensitiveHeaders(), mockStreamIntel) } - /** - * Simulate the stream receiving a cancellation signal from Envoy. - */ + /** Simulate the stream receiving a cancellation signal from Envoy. */ fun receiveCancel() { mockStream.callbacks.onCancel(mockStreamIntel, mockFinalStreamIntel) } @@ -117,6 +168,12 @@ class MockStream internal constructor(underlyingStream: MockEnvoyHTTPStream) : S * @param error The error to receive. */ fun receiveError(error: EnvoyError) { - mockStream.callbacks.onError(error.errorCode, error.message, error.attemptCount ?: 0, mockStreamIntel, mockFinalStreamIntel) + mockStream.callbacks.onError( + error.errorCode, + error.message, + error.attemptCount ?: 0, + mockStreamIntel, + mockFinalStreamIntel + ) } } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamClient.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamClient.kt index 9df0c98b7b5d..a346770c30b3 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamClient.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamClient.kt @@ -3,9 +3,9 @@ package io.envoyproxy.envoymobile /** * Mock implementation of `StreamClient` which produces `MockStreamPrototype` values. * - * @param onStartStream Closure that may be set to observe the creation of new streams. - * It will be called each time `newStreamPrototype()` is executed. - * Typically, this is used to capture streams on creation before sending values through them. + * @param onStartStream Closure that may be set to observe the creation of new streams. It will be + * called each time `newStreamPrototype()` is executed. Typically, this is used to capture streams + * on creation before sending values through them. */ class MockStreamClient(var onStartStream: ((MockStream) -> Unit)?) : StreamClient { override fun newStreamPrototype(): StreamPrototype { diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamPrototype.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamPrototype.kt index 22188905f54a..ffd0623a05ff 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamPrototype.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamPrototype.kt @@ -7,7 +7,9 @@ import java.util.concurrent.Executor * * @param onStart Closure that will be called each time a new stream is started from the prototype. */ -class MockStreamPrototype internal constructor(private val onStart: ((stream: MockStream) -> Unit)?) : StreamPrototype(MockEnvoyEngine()) { +class MockStreamPrototype +internal constructor(private val onStart: ((stream: MockStream) -> Unit)?) : + StreamPrototype(MockEnvoyEngine()) { override fun start(executor: Executor): Stream { val callbacks = createCallbacks(executor) val stream = MockStream(MockEnvoyHTTPStream(callbacks, false, 0)) diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Counter.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Counter.kt index 45731c0ffe81..829c520b07e5 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Counter.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Counter.kt @@ -1,17 +1,11 @@ package io.envoyproxy.envoymobile -/** - * A time series counter. - */ +/** A time series counter. */ interface Counter { - /** - * Increments the counter by the given count. - */ + /** Increments the counter by the given count. */ fun increment(count: Int = 1) - /** - * Increments the counter by the given count and tags. - */ + /** Increments the counter by the given count and tags. */ fun increment(tags: Tags = TagsBuilder().build(), count: Int = 1) } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/CounterImpl.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/CounterImpl.kt index 959c31487010..b6a77380ab97 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/CounterImpl.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/CounterImpl.kt @@ -3,15 +3,17 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.EnvoyEngine import java.lang.ref.WeakReference -/** - * Envoy implementation of a `Counter`. - */ +/** Envoy implementation of a `Counter`. */ internal class CounterImpl : Counter { var envoyEngine: WeakReference var series: String var tags: Tags - internal constructor(engine: EnvoyEngine, elements: List, tags: Tags = TagsBuilder().build()) { + internal constructor( + engine: EnvoyEngine, + elements: List, + tags: Tags = TagsBuilder().build() + ) { this.envoyEngine = WeakReference(engine) this.series = elements.joinToString(separator = ".") { it.value } this.tags = tags diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Element.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Element.kt index 30c47406c775..8b91da91b066 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Element.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Element.kt @@ -7,7 +7,6 @@ import java.util.regex.Pattern * * Element values must conform to the [Element.ELEMENT_REGEX]. */ - class Element(internal val value: String) { init { require(ELEMENT_PATTERN.matcher(value).matches()) { diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Tags.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Tags.kt index de385a90a8e9..975f1b7839c3 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Tags.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/Tags.kt @@ -5,8 +5,7 @@ package io.envoyproxy.envoymobile * To instantiate new instances, see `TagsBuilder`. */ class Tags { - @Suppress("MemberNameEqualsClassName") - val tags: Map + @Suppress("MemberNameEqualsClassName") val tags: Map /** * Internal constructor used by builders. @@ -21,7 +20,6 @@ class Tags { * Get the value for the provided tag name. * * @param name: Tag name for which to get the current value. - * * @return String?, The current tags specified for the provided name. */ fun value(name: String): String? { diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/TagsBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/TagsBuilder.kt index 202b8215c425..3480a0905a8e 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/TagsBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/stats/TagsBuilder.kt @@ -22,9 +22,8 @@ public class TagsBuilder { /** * Append a value to the Tag name. * - * @param name: The Tag name. + * @param name: The Tag name. * @param value: The value associated to the Tag name. - * * @return TagsBuilder, This builder. */ public fun add(name: String, value: String): TagsBuilder { @@ -37,7 +36,6 @@ public class TagsBuilder { * * @param name: The Tag name. * @param value: The value associated to the Tag name. - * * @return TagsBuilder, This builder. */ public fun set(name: String, value: String): TagsBuilder { @@ -49,7 +47,6 @@ public class TagsBuilder { * Remove all Tags with this name. * * @param name: The Tag name to remove. - * * @return TagsBuilder, This builder. */ public fun remove(name: String): TagsBuilder { @@ -61,7 +58,6 @@ public class TagsBuilder { * Adds all tags from map to this builder. * * @param tags: A map of tags. - * * @return TagsBuilder, This builder. */ public fun putAll(tags: Map): TagsBuilder { diff --git a/mobile/test/kotlin/apps/baseline/AsyncDemoFilter.kt b/mobile/test/kotlin/apps/baseline/AsyncDemoFilter.kt index 1c0ae69a1a7a..dda47a7b3460 100644 --- a/mobile/test/kotlin/apps/baseline/AsyncDemoFilter.kt +++ b/mobile/test/kotlin/apps/baseline/AsyncDemoFilter.kt @@ -30,9 +30,7 @@ class AsyncDemoFilter : AsyncResponseFilter { ): FilterHeadersStatus { // If this is the end of the stream, asynchronously resume response processing via callback. if (endStream) { - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } } return FilterHeadersStatus.StopIteration() } @@ -44,9 +42,7 @@ class AsyncDemoFilter : AsyncResponseFilter { ): FilterDataStatus { // If this is the end of the stream, asynchronously resume response processing via callback. if (endStream) { - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } } return FilterDataStatus.StopIterationAndBuffer() } @@ -56,9 +52,7 @@ class AsyncDemoFilter : AsyncResponseFilter { streamIntel: StreamIntel ): FilterTrailersStatus { // Trailers imply end of stream, so asynchronously resume response processing via callbacka - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } return FilterTrailersStatus.StopIteration() } @@ -73,23 +67,14 @@ class AsyncDemoFilter : AsyncResponseFilter { endStream: Boolean, streamIntel: StreamIntel ): FilterResumeStatus { - val builder = headers!!.toResponseHeadersBuilder() - .add("async-filter-demo", "1") + val builder = headers!!.toResponseHeadersBuilder().add("async-filter-demo", "1") return FilterResumeStatus.ResumeIteration(builder.build(), data, trailers) } @Suppress("EmptyFunctionBlock") - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { - } + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onCancel(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onCancel(finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/test/kotlin/apps/baseline/BufferDemoFilter.kt b/mobile/test/kotlin/apps/baseline/BufferDemoFilter.kt index 478a283f33b0..069a9c8c157f 100644 --- a/mobile/test/kotlin/apps/baseline/BufferDemoFilter.kt +++ b/mobile/test/kotlin/apps/baseline/BufferDemoFilter.kt @@ -13,8 +13,7 @@ import java.nio.ByteBuffer /** * Example of a more complex HTTP filter that pauses processing on the response filter chain, - * buffers until the response is complete, then resumes filter iteration while setting a new - * header. + * buffers until the response is complete, then resumes filter iteration while setting a new header. */ class BufferDemoFilter : ResponseFilter { private lateinit var headers: ResponseHeaders @@ -39,8 +38,7 @@ class BufferDemoFilter : ResponseFilter { // If this is the end of the stream, resume processing of the (now fully-buffered) response. if (endStream) { - val builder = headers.toResponseHeadersBuilder() - .add("buffer-filter-demo", "1") + val builder = headers.toResponseHeadersBuilder().add("buffer-filter-demo", "1") return FilterDataStatus.ResumeIteration(builder.build(), body) } return FilterDataStatus.StopIterationAndBuffer() @@ -51,23 +49,14 @@ class BufferDemoFilter : ResponseFilter { streamIntel: StreamIntel ): FilterTrailersStatus { // Trailers imply end of stream; resume processing of the (now fully-buffered) response. - val builder = headers.toResponseHeadersBuilder() - .add("buffer-filter-demo", "1") + val builder = headers.toResponseHeadersBuilder().add("buffer-filter-demo", "1") return FilterTrailersStatus.ResumeIteration(builder.build(), this.body, trailers) } @Suppress("EmptyFunctionBlock") - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { - } + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onCancel(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onCancel(finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/test/kotlin/apps/baseline/DemoFilter.kt b/mobile/test/kotlin/apps/baseline/DemoFilter.kt index ab027dafe636..706b584b89ad 100644 --- a/mobile/test/kotlin/apps/baseline/DemoFilter.kt +++ b/mobile/test/kotlin/apps/baseline/DemoFilter.kt @@ -41,10 +41,7 @@ class DemoFilter : ResponseFilter { return FilterTrailersStatus.Continue(trailers) } - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) { Log.d("DemoFilter", "On error!") } @@ -52,7 +49,5 @@ class DemoFilter : ResponseFilter { Log.d("DemoFilter", "On cancel!") } - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/test/kotlin/apps/baseline/MainActivity.kt b/mobile/test/kotlin/apps/baseline/MainActivity.kt index 25bc30a4cf9b..ebadd21327c0 100644 --- a/mobile/test/kotlin/apps/baseline/MainActivity.kt +++ b/mobile/test/kotlin/apps/baseline/MainActivity.kt @@ -25,13 +25,14 @@ private const val REQUEST_HANDLER_THREAD_NAME = "hello_envoy_kt" private const val REQUEST_AUTHORITY = "api.lyft.com" private const val REQUEST_PATH = "/ping" private const val REQUEST_SCHEME = "http" -private val FILTERED_HEADERS = setOf( - "server", - "filter-demo", - "buffer-filter-demo", - "async-filter-demo", - "x-envoy-upstream-service-time" -) +private val FILTERED_HEADERS = + setOf( + "server", + "filter-demo", + "buffer-filter-demo", + "async-filter-demo", + "x-envoy-upstream-service-time" + ) class MainActivity : Activity() { private val thread = HandlerThread(REQUEST_HANDLER_THREAD_NAME) @@ -44,30 +45,31 @@ class MainActivity : Activity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - engine = AndroidEngineBuilder(application) - .addLogLevel(LogLevel.DEBUG) - .addPlatformFilter(::DemoFilter) - .addNativeFilter("envoy.filters.http.buffer", "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}") - .addStringAccessor("demo-accessor", { "PlatformString" }) - .setOnEngineRunning { Log.d("MainActivity", "Envoy async internal setup completed") } - .setEventTracker({ - for (entry in it.entries) { - Log.d("MainActivity", "Event emitted: ${entry.key}, ${entry.value}") - } - }) - .setLogger { - Log.d("MainActivity", it) - } - .build() + engine = + AndroidEngineBuilder(application) + .addLogLevel(LogLevel.DEBUG) + .addPlatformFilter(::DemoFilter) + .addNativeFilter( + "envoy.filters.http.buffer", + "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}" + ) + .addStringAccessor("demo-accessor", { "PlatformString" }) + .setOnEngineRunning { Log.d("MainActivity", "Envoy async internal setup completed") } + .setEventTracker({ + for (entry in it.entries) { + Log.d("MainActivity", "Event emitted: ${entry.key}, ${entry.value}") + } + }) + .setLogger { Log.d("MainActivity", it) } + .build() recyclerView = findViewById(R.id.recycler_view) as RecyclerView recyclerView.layoutManager = LinearLayoutManager(this) viewAdapter = ResponseRecyclerViewAdapter() recyclerView.adapter = viewAdapter - val dividerItemDecoration = DividerItemDecoration( - recyclerView.context, DividerItemDecoration.VERTICAL - ) + val dividerItemDecoration = + DividerItemDecoration(recyclerView.context, DividerItemDecoration.VERTICAL) recyclerView.addItemDecoration(dividerItemDecoration) thread.start() val handler = Handler(thread.looper) @@ -100,10 +102,9 @@ class MainActivity : Activity() { // Note: this request will use an h2 stream for the upstream request. // The Java example uses http/1.1. This is done on purpose to test both paths in end-to-end // tests in CI. - val requestHeaders = RequestHeadersBuilder( - RequestMethod.GET, REQUEST_SCHEME, REQUEST_AUTHORITY, REQUEST_PATH - ) - .build() + val requestHeaders = + RequestHeadersBuilder(RequestMethod.GET, REQUEST_SCHEME, REQUEST_AUTHORITY, REQUEST_PATH) + .build() engine .streamClient() .newStreamPrototype() diff --git a/mobile/test/kotlin/apps/experimental/AsyncDemoFilter.kt b/mobile/test/kotlin/apps/experimental/AsyncDemoFilter.kt index 90c5c5991706..5bc6126bdde5 100644 --- a/mobile/test/kotlin/apps/experimental/AsyncDemoFilter.kt +++ b/mobile/test/kotlin/apps/experimental/AsyncDemoFilter.kt @@ -30,9 +30,7 @@ class AsyncDemoFilter : AsyncResponseFilter { ): FilterHeadersStatus { // If this is the end of the stream, asynchronously resume response processing via callback. if (endStream) { - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } } return FilterHeadersStatus.StopIteration() } @@ -44,9 +42,7 @@ class AsyncDemoFilter : AsyncResponseFilter { ): FilterDataStatus { // If this is the end of the stream, asynchronously resume response processing via callback. if (endStream) { - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } } return FilterDataStatus.StopIterationAndBuffer() } @@ -56,9 +52,7 @@ class AsyncDemoFilter : AsyncResponseFilter { streamIntel: StreamIntel ): FilterTrailersStatus { // Trailers imply end of stream, so asynchronously resume response processing via callbacka - Timer("AsyncResume", false).schedule(100) { - callbacks.resumeResponse() - } + Timer("AsyncResume", false).schedule(100) { callbacks.resumeResponse() } return FilterTrailersStatus.StopIteration() } @@ -73,23 +67,14 @@ class AsyncDemoFilter : AsyncResponseFilter { endStream: Boolean, streamIntel: StreamIntel ): FilterResumeStatus { - val builder = headers!!.toResponseHeadersBuilder() - .add("async-filter-demo", "1") + val builder = headers!!.toResponseHeadersBuilder().add("async-filter-demo", "1") return FilterResumeStatus.ResumeIteration(builder.build(), data, trailers) } @Suppress("EmptyFunctionBlock") - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { - } + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onCancel(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onCancel(finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/test/kotlin/apps/experimental/BufferDemoFilter.kt b/mobile/test/kotlin/apps/experimental/BufferDemoFilter.kt index 2c22504becbe..29f8f84b6fe0 100644 --- a/mobile/test/kotlin/apps/experimental/BufferDemoFilter.kt +++ b/mobile/test/kotlin/apps/experimental/BufferDemoFilter.kt @@ -13,8 +13,7 @@ import java.nio.ByteBuffer /** * Example of a more complex HTTP filter that pauses processing on the response filter chain, - * buffers until the response is complete, then resumes filter iteration while setting a new - * header. + * buffers until the response is complete, then resumes filter iteration while setting a new header. */ class BufferDemoFilter : ResponseFilter { private lateinit var headers: ResponseHeaders @@ -39,8 +38,7 @@ class BufferDemoFilter : ResponseFilter { // If this is the end of the stream, resume processing of the (now fully-buffered) response. if (endStream) { - val builder = headers.toResponseHeadersBuilder() - .add("buffer-filter-demo", "1") + val builder = headers.toResponseHeadersBuilder().add("buffer-filter-demo", "1") return FilterDataStatus.ResumeIteration(builder.build(), body) } return FilterDataStatus.StopIterationAndBuffer() @@ -51,23 +49,14 @@ class BufferDemoFilter : ResponseFilter { streamIntel: StreamIntel ): FilterTrailersStatus { // Trailers imply end of stream; resume processing of the (now fully-buffered) response. - val builder = headers.toResponseHeadersBuilder() - .add("buffer-filter-demo", "1") + val builder = headers.toResponseHeadersBuilder().add("buffer-filter-demo", "1") return FilterTrailersStatus.ResumeIteration(builder.build(), this.body, trailers) } @Suppress("EmptyFunctionBlock") - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { - } + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onCancel(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onCancel(finalStreamIntel: FinalStreamIntel) {} - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/test/kotlin/apps/experimental/DemoFilter.kt b/mobile/test/kotlin/apps/experimental/DemoFilter.kt index 6790e22d3f55..3621722c95dd 100644 --- a/mobile/test/kotlin/apps/experimental/DemoFilter.kt +++ b/mobile/test/kotlin/apps/experimental/DemoFilter.kt @@ -41,10 +41,7 @@ class DemoFilter : ResponseFilter { return FilterTrailersStatus.Continue(trailers) } - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) { + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) { Log.d("DemoFilter", "On error!") } @@ -52,7 +49,5 @@ class DemoFilter : ResponseFilter { Log.d("DemoFilter", "On cancel!") } - @Suppress("EmptyFunctionBlock") - override fun onComplete(finalStreamIntel: FinalStreamIntel) { - } + @Suppress("EmptyFunctionBlock") override fun onComplete(finalStreamIntel: FinalStreamIntel) {} } diff --git a/mobile/test/kotlin/apps/experimental/MainActivity.kt b/mobile/test/kotlin/apps/experimental/MainActivity.kt index 6a76e94ae47a..be8b07c78dd1 100644 --- a/mobile/test/kotlin/apps/experimental/MainActivity.kt +++ b/mobile/test/kotlin/apps/experimental/MainActivity.kt @@ -9,7 +9,6 @@ import android.util.Log import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import io.envoyproxy.envoymobile.android.SharedPreferencesStore import io.envoyproxy.envoymobile.AndroidEngineBuilder import io.envoyproxy.envoymobile.CompressionAlgorithm import io.envoyproxy.envoymobile.Element @@ -18,6 +17,7 @@ import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestHeadersBuilderCompressionUtil.enableRequestCompression import io.envoyproxy.envoymobile.RequestMethod +import io.envoyproxy.envoymobile.android.SharedPreferencesStore import io.envoyproxy.envoymobile.shared.Failure import io.envoyproxy.envoymobile.shared.ResponseRecyclerViewAdapter import io.envoyproxy.envoymobile.shared.Success @@ -30,13 +30,14 @@ private const val REQUEST_AUTHORITY = "api.lyft.com" private const val REQUEST_PATH = "/ping" private const val REQUEST_SCHEME = "https" private const val PERSISTENCE_KEY = "EnvoyMobilePersistenceKey" -private val FILTERED_HEADERS = setOf( - "server", - "filter-demo", - "buffer-filter-demo", - "async-filter-demo", - "x-envoy-upstream-service-time" -) +private val FILTERED_HEADERS = + setOf( + "server", + "filter-demo", + "buffer-filter-demo", + "async-filter-demo", + "x-envoy-upstream-service-time" + ) class MainActivity : Activity() { private val thread = HandlerThread(REQUEST_HANDLER_THREAD_NAME) @@ -51,40 +52,41 @@ class MainActivity : Activity() { val preferences = getSharedPreferences(PERSISTENCE_KEY, Context.MODE_PRIVATE) - engine = AndroidEngineBuilder(application) - .addLogLevel(LogLevel.DEBUG) - .addPlatformFilter(::DemoFilter) - .addPlatformFilter(::BufferDemoFilter) - .addPlatformFilter(::AsyncDemoFilter) - .enableDNSCache(true) - // required by DNS cache - .addKeyValueStore("reserved.platform_store", SharedPreferencesStore(preferences)) - .enableInterfaceBinding(true) - .enableSocketTagging(true) - .enableProxying(true) - // TODO: uncomment once platform cert validation is fixed. - // .enablePlatformCertificatesValidation(true) - .addNativeFilter("envoy.filters.http.buffer", "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}") - .addStringAccessor("demo-accessor", { "PlatformString" }) - .setOnEngineRunning { Log.d("MainActivity", "Envoy async internal setup completed") } - .setEventTracker({ - for (entry in it.entries) { - Log.d("MainActivity", "Event emitted: ${entry.key}, ${entry.value}") - } - }) - .setLogger { - Log.d("MainActivity", it) - } - .build() + engine = + AndroidEngineBuilder(application) + .addLogLevel(LogLevel.DEBUG) + .addPlatformFilter(::DemoFilter) + .addPlatformFilter(::BufferDemoFilter) + .addPlatformFilter(::AsyncDemoFilter) + .enableDNSCache(true) + // required by DNS cache + .addKeyValueStore("reserved.platform_store", SharedPreferencesStore(preferences)) + .enableInterfaceBinding(true) + .enableSocketTagging(true) + .enableProxying(true) + // TODO: uncomment once platform cert validation is fixed. + // .enablePlatformCertificatesValidation(true) + .addNativeFilter( + "envoy.filters.http.buffer", + "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}" + ) + .addStringAccessor("demo-accessor", { "PlatformString" }) + .setOnEngineRunning { Log.d("MainActivity", "Envoy async internal setup completed") } + .setEventTracker({ + for (entry in it.entries) { + Log.d("MainActivity", "Event emitted: ${entry.key}, ${entry.value}") + } + }) + .setLogger { Log.d("MainActivity", it) } + .build() recyclerView = findViewById(R.id.recycler_view) as RecyclerView recyclerView.layoutManager = LinearLayoutManager(this) viewAdapter = ResponseRecyclerViewAdapter() recyclerView.adapter = viewAdapter - val dividerItemDecoration = DividerItemDecoration( - recyclerView.context, DividerItemDecoration.VERTICAL - ) + val dividerItemDecoration = + DividerItemDecoration(recyclerView.context, DividerItemDecoration.VERTICAL) recyclerView.addItemDecoration(dividerItemDecoration) thread.start() val handler = Handler(thread.looper) @@ -117,12 +119,11 @@ class MainActivity : Activity() { // Note: this request will use an h2 stream for the upstream request. // The Java example uses http/1.1. This is done on purpose to test both paths in end-to-end // tests in CI. - val requestHeaders = RequestHeadersBuilder( - RequestMethod.GET, REQUEST_SCHEME, REQUEST_AUTHORITY, REQUEST_PATH - ) - .enableRequestCompression(CompressionAlgorithm.GZIP) - .addSocketTag(1,2) - .build() + val requestHeaders = + RequestHeadersBuilder(RequestMethod.GET, REQUEST_SCHEME, REQUEST_AUTHORITY, REQUEST_PATH) + .enableRequestCompression(CompressionAlgorithm.GZIP) + .addSocketTag(1, 2) + .build() engine .streamClient() .newStreamPrototype() diff --git a/mobile/test/kotlin/integration/CancelGRPCStreamTest.kt b/mobile/test/kotlin/integration/CancelGRPCStreamTest.kt index 6539fe4522a1..c9bba2c7495a 100644 --- a/mobile/test/kotlin/integration/CancelGRPCStreamTest.kt +++ b/mobile/test/kotlin/integration/CancelGRPCStreamTest.kt @@ -1,6 +1,5 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.EnvoyError import io.envoyproxy.envoymobile.FilterDataStatus @@ -12,6 +11,7 @@ import io.envoyproxy.envoymobile.GRPCRequestHeadersBuilder import io.envoyproxy.envoymobile.ResponseFilter import io.envoyproxy.envoymobile.ResponseHeaders import io.envoyproxy.envoymobile.ResponseTrailers +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer @@ -22,8 +22,10 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Test private const val FILTER_NAME = "cancel_validation_filter" -private const val PBF_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge" -private const val LOCAL_ERROR_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.local_error.LocalError" +private const val PBF_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge" +private const val LOCAL_ERROR_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.local_error.LocalError" class CancelGRPCStreamTest { @@ -34,9 +36,7 @@ class CancelGRPCStreamTest { private val filterExpectation = CountDownLatch(1) private val onCancelCallbackExpectation = CountDownLatch(1) - class CancelValidationFilter( - private val latch: CountDownLatch - ) : ResponseFilter { + class CancelValidationFilter(private val latch: CountDownLatch) : ResponseFilter { override fun onResponseHeaders( headers: ResponseHeaders, endStream: Boolean, @@ -61,6 +61,7 @@ class CancelGRPCStreamTest { } override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} + override fun onComplete(finalStreamIntel: FinalStreamIntel) {} override fun onCancel(finalStreamIntel: FinalStreamIntel) { @@ -70,28 +71,27 @@ class CancelGRPCStreamTest { @Test fun `cancel grpc stream calls onCancel callback`() { - val engine = EngineBuilder(Standard()) - .addPlatformFilter( - name = FILTER_NAME, - factory = { CancelValidationFilter(filterExpectation) } - ) - .addNativeFilter("envoy.filters.http.platform_bridge", "{'@type': $PBF_TYPE, platform_filter_name: $FILTER_NAME}") - .addNativeFilter("envoy.filters.http.local_error", "{'@type': $LOCAL_ERROR_FILTER_TYPE}") - .build() + val engine = + EngineBuilder(Standard()) + .addPlatformFilter( + name = FILTER_NAME, + factory = { CancelValidationFilter(filterExpectation) } + ) + .addNativeFilter( + "envoy.filters.http.platform_bridge", + "{'@type': $PBF_TYPE, platform_filter_name: $FILTER_NAME}" + ) + .addNativeFilter("envoy.filters.http.local_error", "{'@type': $LOCAL_ERROR_FILTER_TYPE}") + .build() val client = GRPCClient(engine.streamClient()) - val requestHeaders = GRPCRequestHeadersBuilder( - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() - - client.newGRPCStreamPrototype() - .setOnCancel { _ -> - onCancelCallbackExpectation.countDown() - } + val requestHeaders = + GRPCRequestHeadersBuilder(scheme = "https", authority = "example.com", path = "/test").build() + + client + .newGRPCStreamPrototype() + .setOnCancel { _ -> onCancelCallbackExpectation.countDown() } .start(Executors.newSingleThreadExecutor()) .sendHeaders(requestHeaders, false) .cancel() diff --git a/mobile/test/kotlin/integration/CancelStreamTest.kt b/mobile/test/kotlin/integration/CancelStreamTest.kt index df4a971ecbc7..6993fe62f26e 100644 --- a/mobile/test/kotlin/integration/CancelStreamTest.kt +++ b/mobile/test/kotlin/integration/CancelStreamTest.kt @@ -1,6 +1,5 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.EnvoyError import io.envoyproxy.envoymobile.FilterDataStatus @@ -12,6 +11,7 @@ import io.envoyproxy.envoymobile.RequestMethod import io.envoyproxy.envoymobile.ResponseFilter import io.envoyproxy.envoymobile.ResponseHeaders import io.envoyproxy.envoymobile.ResponseTrailers +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer @@ -21,7 +21,8 @@ import java.util.concurrent.TimeUnit import org.assertj.core.api.Assertions.assertThat import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" class CancelStreamTest { @@ -32,9 +33,7 @@ class CancelStreamTest { private val filterExpectation = CountDownLatch(1) private val runExpectation = CountDownLatch(1) - class CancelValidationFilter( - private val latch: CountDownLatch - ) : ResponseFilter { + class CancelValidationFilter(private val latch: CountDownLatch) : ResponseFilter { override fun onResponseHeaders( headers: ResponseHeaders, endStream: Boolean, @@ -59,6 +58,7 @@ class CancelStreamTest { } override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} + override fun onComplete(finalStreamIntel: FinalStreamIntel) {} override fun onCancel(finalStreamIntel: FinalStreamIntel) { @@ -68,28 +68,29 @@ class CancelStreamTest { @Test fun `cancel stream calls onCancel callback`() { - val engine = EngineBuilder(Standard()) - .addPlatformFilter( - name = "cancel_validation_filter", - factory = { CancelValidationFilter(filterExpectation) } - ) - .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") - .build() + val engine = + EngineBuilder(Standard()) + .addPlatformFilter( + name = "cancel_validation_filter", + factory = { CancelValidationFilter(filterExpectation) } + ) + .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() - - client.newStreamPrototype() - .setOnCancel { _ -> - runExpectation.countDown() - } + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() + + client + .newStreamPrototype() + .setOnCancel { _ -> runExpectation.countDown() } .start(Executors.newSingleThreadExecutor()) .sendHeaders(requestHeaders, false) .cancel() diff --git a/mobile/test/kotlin/integration/EngineApiTest.kt b/mobile/test/kotlin/integration/EngineApiTest.kt index 6b6859cc827a..e62b9bd4dc0b 100644 --- a/mobile/test/kotlin/integration/EngineApiTest.kt +++ b/mobile/test/kotlin/integration/EngineApiTest.kt @@ -17,11 +17,12 @@ class EngineApiTest { @Test fun `verify engine APIs`() { val countDownLatch = CountDownLatch(1) - val engine = EngineBuilder() - .addLogLevel(LogLevel.INFO) - .addStatsFlushSeconds(1) - .setOnEngineRunning { countDownLatch.countDown() } - .build() + val engine = + EngineBuilder() + .addLogLevel(LogLevel.INFO) + .addStatsFlushSeconds(1) + .setOnEngineRunning { countDownLatch.countDown() } + .build() assertThat(countDownLatch.await(30, TimeUnit.SECONDS)).isTrue() diff --git a/mobile/test/kotlin/integration/FilterThrowingExceptionTest.kt b/mobile/test/kotlin/integration/FilterThrowingExceptionTest.kt index df556fcb38ab..6b2ebb069fa0 100644 --- a/mobile/test/kotlin/integration/FilterThrowingExceptionTest.kt +++ b/mobile/test/kotlin/integration/FilterThrowingExceptionTest.kt @@ -2,10 +2,8 @@ package test.kotlin.integration import android.content.Context import androidx.test.core.app.ApplicationProvider - import io.envoyproxy.envoymobile.AndroidEngineBuilder import io.envoyproxy.envoymobile.EnvoyError -import io.envoyproxy.envoymobile.engine.JniLibrary import io.envoyproxy.envoymobile.FilterDataStatus import io.envoyproxy.envoymobile.FilterHeadersStatus import io.envoyproxy.envoymobile.FilterTrailersStatus @@ -20,18 +18,17 @@ import io.envoyproxy.envoymobile.ResponseFilter import io.envoyproxy.envoymobile.ResponseHeaders import io.envoyproxy.envoymobile.ResponseTrailers import io.envoyproxy.envoymobile.StreamIntel - +import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit - import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -class ThrowingFilter: RequestFilter, ResponseFilter { +class ThrowingFilter : RequestFilter, ResponseFilter { override fun onRequestHeaders( headers: RequestHeaders, endStream: Boolean, @@ -40,13 +37,18 @@ class ThrowingFilter: RequestFilter, ResponseFilter { throw Exception("Simulated onRequestHeaders exception") } - override fun onRequestData(body: ByteBuffer, endStream: Boolean, streamIntel: StreamIntel): - FilterDataStatus { - return FilterDataStatus.Continue(body) + override fun onRequestData( + body: ByteBuffer, + endStream: Boolean, + streamIntel: StreamIntel + ): FilterDataStatus { + return FilterDataStatus.Continue(body) } - override fun onRequestTrailers(trailers: RequestTrailers, streamIntel: StreamIntel): - FilterTrailersStatus { + override fun onRequestTrailers( + trailers: RequestTrailers, + streamIntel: StreamIntel + ): FilterTrailersStatus { return FilterTrailersStatus.Continue(trailers) } @@ -73,10 +75,7 @@ class ThrowingFilter: RequestFilter, ResponseFilter { return FilterTrailersStatus.Continue(trailers) } - override fun onError( - error: EnvoyError, - finalStreamIntel: FinalStreamIntel - ) {} + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} override fun onCancel(finalStreamIntel: FinalStreamIntel) {} @@ -95,31 +94,37 @@ class FilterThrowingExceptionTest { val onRespondeHeadersLatch = CountDownLatch(1) val onJNIExceptionEventLatch = CountDownLatch(2) - var expectedMessages = mutableListOf( - "Simulated onRequestHeaders exception||onRequestHeaders||", - "Simulated onResponseHeaders exception||onResponseHeaders||") + var expectedMessages = + mutableListOf( + "Simulated onRequestHeaders exception||onRequestHeaders||", + "Simulated onResponseHeaders exception||onResponseHeaders||" + ) val context = ApplicationProvider.getApplicationContext() val builder = AndroidEngineBuilder(context) - val engine = builder - .addLogLevel(LogLevel.DEBUG) - .setEventTracker { event -> - if (event["name"] == "event_log" && event["log_name"] == "jni_cleared_pending_exception") { - assertThat(event["message"]).contains(expectedMessages.removeFirst()) - onJNIExceptionEventLatch.countDown() + val engine = + builder + .addLogLevel(LogLevel.DEBUG) + .setEventTracker { event -> + if ( + event["name"] == "event_log" && event["log_name"] == "jni_cleared_pending_exception" + ) { + assertThat(event["message"]).contains(expectedMessages.removeFirst()) + onJNIExceptionEventLatch.countDown() + } } - } - .addPlatformFilter(::ThrowingFilter) - .setOnEngineRunning { onEngineRunningLatch.countDown() } - .build() - - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "api.lyft.com", - path = "/ping" - ) - .build() + .addPlatformFilter(::ThrowingFilter) + .setOnEngineRunning { onEngineRunningLatch.countDown() } + .build() + + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "api.lyft.com", + path = "/ping" + ) + .build() engine .streamClient() diff --git a/mobile/test/kotlin/integration/GRPCReceiveErrorTest.kt b/mobile/test/kotlin/integration/GRPCReceiveErrorTest.kt index 96720dc1119b..19efe62602e9 100644 --- a/mobile/test/kotlin/integration/GRPCReceiveErrorTest.kt +++ b/mobile/test/kotlin/integration/GRPCReceiveErrorTest.kt @@ -1,6 +1,5 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.EnvoyError import io.envoyproxy.envoymobile.FilterDataStatus @@ -12,6 +11,7 @@ import io.envoyproxy.envoymobile.GRPCRequestHeadersBuilder import io.envoyproxy.envoymobile.ResponseFilter import io.envoyproxy.envoymobile.ResponseHeaders import io.envoyproxy.envoymobile.ResponseTrailers +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer @@ -21,7 +21,8 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val PBF_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge" +private const val PBF_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge" private const val FILTER_NAME = "error_validation_filter" class GRPCReceiveErrorTest { @@ -63,6 +64,7 @@ class GRPCReceiveErrorTest { override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) { receivedError.countDown() } + override fun onComplete(finalStreamIntel: FinalStreamIntel) {} override fun onCancel(finalStreamIntel: FinalStreamIntel) { @@ -72,28 +74,30 @@ class GRPCReceiveErrorTest { @Test fun `errors on stream call onError callback`() { - val requestHeader = GRPCRequestHeadersBuilder( - scheme = "https", - authority = "example.com", - path = "/pb.api.v1.Foo/GetBar" - ).build() - - val engine = EngineBuilder(Standard()) - .addPlatformFilter( - name = FILTER_NAME, - factory = { ErrorValidationFilter(filterReceivedError, filterNotCancelled) } - ) - .addNativeFilter("envoy.filters.http.platform_bridge", "{'@type': $PBF_TYPE, platform_filter_name: $FILTER_NAME}") - .build() + val requestHeader = + GRPCRequestHeadersBuilder( + scheme = "https", + authority = "example.com", + path = "/pb.api.v1.Foo/GetBar" + ) + .build() + + val engine = + EngineBuilder(Standard()) + .addPlatformFilter( + name = FILTER_NAME, + factory = { ErrorValidationFilter(filterReceivedError, filterNotCancelled) } + ) + .addNativeFilter( + "envoy.filters.http.platform_bridge", + "{'@type': $PBF_TYPE, platform_filter_name: $FILTER_NAME}" + ) + .build() GRPCClient(engine.streamClient()) .newGRPCStreamPrototype() - .setOnError { _, _ -> - callbackReceivedError.countDown() - } - .setOnCancel { _ -> - fail("Unexpected call to onCancel response callback") - } + .setOnError { _, _ -> callbackReceivedError.countDown() } + .setOnCancel { _ -> fail("Unexpected call to onCancel response callback") } .start() .sendHeaders(requestHeader, false) .sendMessage(ByteBuffer.wrap(ByteArray(5))) diff --git a/mobile/test/kotlin/integration/KeyValueStoreTest.kt b/mobile/test/kotlin/integration/KeyValueStoreTest.kt index f1862818ecbb..fdc46031951e 100644 --- a/mobile/test/kotlin/integration/KeyValueStoreTest.kt +++ b/mobile/test/kotlin/integration/KeyValueStoreTest.kt @@ -1,10 +1,10 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.KeyValueStore import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.engine.JniLibrary import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -12,7 +12,8 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" private const val TEST_KEY = "foo" private const val TEST_VALUE = "bar" @@ -27,28 +28,42 @@ class KeyValueStoreTest { val readExpectation = CountDownLatch(3) val saveExpectation = CountDownLatch(1) - val testKeyValueStore = object : KeyValueStore { - override fun read(key: String): String? { readExpectation.countDown(); return null } - override fun remove(key: String) {} - override fun save(key: String, value: String) { saveExpectation.countDown() } - } + val testKeyValueStore = + object : KeyValueStore { + override fun read(key: String): String? { + readExpectation.countDown() + return null + } + + override fun remove(key: String) {} - val engine = EngineBuilder(Standard()) + override fun save(key: String, value: String) { + saveExpectation.countDown() + } + } + + val engine = + EngineBuilder(Standard()) .addKeyValueStore("envoy.key_value.platform_test", testKeyValueStore) - .addNativeFilter("envoy.filters.http.test_kv_store", "{'@type': type.googleapis.com/envoymobile.extensions.filters.http.test_kv_store.TestKeyValueStore, kv_store_name: envoy.key_value.platform_test, test_key: $TEST_KEY, test_value: $TEST_VALUE}") + .addNativeFilter( + "envoy.filters.http.test_kv_store", + "{'@type': type.googleapis.com/envoymobile.extensions.filters.http.test_kv_store.TestKeyValueStore, kv_store_name: envoy.key_value.platform_test, test_key: $TEST_KEY, test_value: $TEST_VALUE}" + ) .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() - client.newStreamPrototype() + client + .newStreamPrototype() .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeaders, true) diff --git a/mobile/test/kotlin/integration/ReceiveDataTest.kt b/mobile/test/kotlin/integration/ReceiveDataTest.kt index dd230e5f9c80..a20bf37e37f4 100644 --- a/mobile/test/kotlin/integration/ReceiveDataTest.kt +++ b/mobile/test/kotlin/integration/ReceiveDataTest.kt @@ -1,9 +1,9 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer import java.util.concurrent.CountDownLatch @@ -12,7 +12,8 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" class ReceiveDataTest { @@ -23,25 +24,28 @@ class ReceiveDataTest { @Test fun `response headers and response data call onResponseHeaders and onResponseData`() { - val engine = EngineBuilder(Standard()) - .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") - .build() + val engine = + EngineBuilder(Standard()) + .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() val headersExpectation = CountDownLatch(1) val dataExpectation = CountDownLatch(1) var status: Int? = null var body: ByteBuffer? = null - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { responseHeaders, _, _ -> status = responseHeaders.httpStatus headersExpectation.countDown() diff --git a/mobile/test/kotlin/integration/ReceiveDataWithDeliveryLimitsTest.kt b/mobile/test/kotlin/integration/ReceiveDataWithDeliveryLimitsTest.kt index 0b65324ee1da..053b7ec66fb1 100644 --- a/mobile/test/kotlin/integration/ReceiveDataWithDeliveryLimitsTest.kt +++ b/mobile/test/kotlin/integration/ReceiveDataWithDeliveryLimitsTest.kt @@ -1,9 +1,9 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer import java.util.concurrent.CountDownLatch @@ -12,7 +12,8 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" class ReceiveDataWithDeliveryLimitsTest { @@ -23,25 +24,28 @@ class ReceiveDataWithDeliveryLimitsTest { @Test fun `data is received with min delivery size set`() { - val engine = EngineBuilder(Standard()) - .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") - .build() + val engine = + EngineBuilder(Standard()) + .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() val headersExpectation = CountDownLatch(1) val dataExpectation = CountDownLatch(1) var status: Int? = null var body: ByteBuffer? = null - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { responseHeaders, _, _ -> status = responseHeaders.httpStatus headersExpectation.countDown() diff --git a/mobile/test/kotlin/integration/ReceiveErrorTest.kt b/mobile/test/kotlin/integration/ReceiveErrorTest.kt index d6daa9675a4a..c1ae21de4310 100644 --- a/mobile/test/kotlin/integration/ReceiveErrorTest.kt +++ b/mobile/test/kotlin/integration/ReceiveErrorTest.kt @@ -1,6 +1,5 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.EnvoyError import io.envoyproxy.envoymobile.FilterDataStatus @@ -11,6 +10,7 @@ import io.envoyproxy.envoymobile.GRPCRequestHeadersBuilder import io.envoyproxy.envoymobile.ResponseFilter import io.envoyproxy.envoymobile.ResponseHeaders import io.envoyproxy.envoymobile.ResponseTrailers +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer @@ -20,8 +20,10 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val PBF_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge" -private const val LOCAL_ERROR_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.local_error.LocalError" +private const val PBF_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge" +private const val LOCAL_ERROR_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.local_error.LocalError" private const val FILTER_NAME = "error_validation_filter" class ReceiveErrorTest { @@ -63,6 +65,7 @@ class ReceiveErrorTest { override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) { receivedError.countDown() } + override fun onComplete(finalStreamIntel: FinalStreamIntel) {} override fun onCancel(finalStreamIntel: FinalStreamIntel) { @@ -72,24 +75,31 @@ class ReceiveErrorTest { @Test fun `errors on stream call onError callback`() { - val requestHeader = GRPCRequestHeadersBuilder( - scheme = "https", - authority = "doesnotexist.example.com", - path = "/test" - ).build() - - val engine = EngineBuilder(Standard()) - .addPlatformFilter( - name = FILTER_NAME, - factory = { ErrorValidationFilter(filterReceivedError, filterNotCancelled) } - ) - .addNativeFilter("envoy.filters.http.platform_bridge", "{'@type': $PBF_TYPE, platform_filter_name: $FILTER_NAME}") - .addNativeFilter("envoy.filters.http.local_error", "{'@type': $LOCAL_ERROR_FILTER_TYPE}") - .build() + val requestHeader = + GRPCRequestHeadersBuilder( + scheme = "https", + authority = "doesnotexist.example.com", + path = "/test" + ) + .build() + + val engine = + EngineBuilder(Standard()) + .addPlatformFilter( + name = FILTER_NAME, + factory = { ErrorValidationFilter(filterReceivedError, filterNotCancelled) } + ) + .addNativeFilter( + "envoy.filters.http.platform_bridge", + "{'@type': $PBF_TYPE, platform_filter_name: $FILTER_NAME}" + ) + .addNativeFilter("envoy.filters.http.local_error", "{'@type': $LOCAL_ERROR_FILTER_TYPE}") + .build() var errorCode: Int? = null - engine.streamClient() + engine + .streamClient() .newStreamPrototype() .setOnResponseHeaders { _, _, _ -> fail("Headers received instead of expected error") } .setOnResponseData { _, _, _ -> fail("Data received instead of expected error") } @@ -99,9 +109,7 @@ class ReceiveErrorTest { errorCode = error.errorCode callbackReceivedError.countDown() } - .setOnCancel { _ -> - fail("Unexpected call to onCancel response callback") - } + .setOnCancel { _ -> fail("Unexpected call to onCancel response callback") } .start() .sendHeaders(requestHeader, true) diff --git a/mobile/test/kotlin/integration/ReceiveTrailersTest.kt b/mobile/test/kotlin/integration/ReceiveTrailersTest.kt index 9ca0ac2c00d1..9dd3515efe06 100644 --- a/mobile/test/kotlin/integration/ReceiveTrailersTest.kt +++ b/mobile/test/kotlin/integration/ReceiveTrailersTest.kt @@ -1,19 +1,19 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.EnvoyError -import io.envoyproxy.envoymobile.ResponseFilter -import io.envoyproxy.envoymobile.RequestHeadersBuilder -import io.envoyproxy.envoymobile.RequestMethod -import io.envoyproxy.envoymobile.StreamIntel -import io.envoyproxy.envoymobile.FinalStreamIntel -import io.envoyproxy.envoymobile.ResponseHeaders import io.envoyproxy.envoymobile.FilterDataStatus import io.envoyproxy.envoymobile.FilterHeadersStatus import io.envoyproxy.envoymobile.FilterTrailersStatus -import io.envoyproxy.envoymobile.ResponseTrailers +import io.envoyproxy.envoymobile.FinalStreamIntel +import io.envoyproxy.envoymobile.RequestHeadersBuilder +import io.envoyproxy.envoymobile.RequestMethod import io.envoyproxy.envoymobile.RequestTrailersBuilder +import io.envoyproxy.envoymobile.ResponseFilter +import io.envoyproxy.envoymobile.ResponseHeaders +import io.envoyproxy.envoymobile.ResponseTrailers +import io.envoyproxy.envoymobile.Standard +import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer import java.util.concurrent.CountDownLatch @@ -22,7 +22,8 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" private const val MATCHER_TRAILER_NAME = "test-trailer" private const val MATCHER_TRAILER_VALUE = "test.code" @@ -32,10 +33,7 @@ class ReceiveTrailersTest { JniLibrary.loadTestLibrary() } - - class ErrorValidationFilter( - private val onTrailers: CountDownLatch - ) : ResponseFilter { + class ErrorValidationFilter(private val onTrailers: CountDownLatch) : ResponseFilter { override fun onResponseHeaders( headers: ResponseHeaders, endStream: Boolean, @@ -60,51 +58,50 @@ class ReceiveTrailersTest { return FilterTrailersStatus.Continue(trailers) } - override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) { - } + override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {} + override fun onComplete(finalStreamIntel: FinalStreamIntel) {} - override fun onCancel(finalStreamIntel: FinalStreamIntel) { - } + override fun onCancel(finalStreamIntel: FinalStreamIntel) {} } @Test fun `successful sending of trailers`() { val trailersReceived = CountDownLatch(2) val expectation = CountDownLatch(2) - val engine = EngineBuilder(Standard()) - .addPlatformFilter( - name = "test_platform_filter", - factory = { ErrorValidationFilter(trailersReceived) } - ) - .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") - .build() + val engine = + EngineBuilder(Standard()) + .addPlatformFilter( + name = "test_platform_filter", + factory = { ErrorValidationFilter(trailersReceived) } + ) + .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") + .build() val client = engine.streamClient() - val builder = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) + val builder = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) builder.add("send-trailers", "true") val requestHeadersDefault = builder.build() val body = ByteBuffer.wrap("match_me".toByteArray(Charsets.UTF_8)) - val requestTrailers = RequestTrailersBuilder() - .add(MATCHER_TRAILER_NAME, MATCHER_TRAILER_VALUE) - .build() + val requestTrailers = + RequestTrailersBuilder().add(MATCHER_TRAILER_NAME, MATCHER_TRAILER_VALUE).build() var responseStatus: Int? = null - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { headers, _, _ -> responseStatus = headers.httpStatus expectation.countDown() } - .setOnError { _, _ -> - fail("Unexpected error") - } + .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeadersDefault, false) .sendData(body) @@ -115,14 +112,13 @@ class ReceiveTrailersTest { builder.remove("send-trailers") builder.add("send-trailers", "empty") val requestHeadersEmpty = builder.build() - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { headers, _, _ -> responseStatus = headers.httpStatus expectation.countDown() } - .setOnError { _, _ -> - fail("Unexpected error") - } + .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeadersEmpty, false) .sendData(body) @@ -132,14 +128,13 @@ class ReceiveTrailersTest { builder.remove("send-trailers") builder.add("send-trailers", "empty-value") val requestHeadersEmptyValue = builder.build() - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { headers, _, _ -> responseStatus = headers.httpStatus expectation.countDown() } - .setOnError { _, _ -> - fail("Unexpected error") - } + .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeadersEmptyValue, false) .sendData(body) diff --git a/mobile/test/kotlin/integration/ResetConnectivityStateTest.kt b/mobile/test/kotlin/integration/ResetConnectivityStateTest.kt index 40702aab67f8..9e1e9971b477 100644 --- a/mobile/test/kotlin/integration/ResetConnectivityStateTest.kt +++ b/mobile/test/kotlin/integration/ResetConnectivityStateTest.kt @@ -1,10 +1,10 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod import io.envoyproxy.envoymobile.ResponseHeaders +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.engine.JniLibrary import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -12,7 +12,8 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" // This test doesn't do what it advertises (https://github.com/envoyproxy/envoy/issues/25180) class ResetConnectivityStateTest { @@ -25,30 +26,31 @@ class ResetConnectivityStateTest { fun `successful request after connection drain`() { val headersExpectation = CountDownLatch(2) - val engine = EngineBuilder(Standard()) - .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") - .build() + val engine = + EngineBuilder(Standard()) + .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() var resultHeaders1: ResponseHeaders? = null var resultEndStream1: Boolean? = null - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { responseHeaders, endStream, _ -> resultHeaders1 = responseHeaders resultEndStream1 = endStream headersExpectation.countDown() } - .setOnResponseData { _, endStream, _ -> - resultEndStream1 = endStream - } + .setOnResponseData { _, endStream, _ -> resultEndStream1 = endStream } .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeaders, true) @@ -59,15 +61,14 @@ class ResetConnectivityStateTest { var resultHeaders2: ResponseHeaders? = null var resultEndStream2: Boolean? = null - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { responseHeaders, endStream, _ -> resultHeaders2 = responseHeaders resultEndStream2 = endStream headersExpectation.countDown() } - .setOnResponseData { _, endStream, _ -> - resultEndStream2 = endStream - } + .setOnResponseData { _, endStream, _ -> resultEndStream2 = endStream } .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeaders, true) diff --git a/mobile/test/kotlin/integration/SendDataTest.kt b/mobile/test/kotlin/integration/SendDataTest.kt index 994a5981b083..fb63b82aae15 100644 --- a/mobile/test/kotlin/integration/SendDataTest.kt +++ b/mobile/test/kotlin/integration/SendDataTest.kt @@ -1,13 +1,13 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod -import io.envoyproxy.envoymobile.engine.testing.TestJni -import io.envoyproxy.envoymobile.engine.JniLibrary -import io.envoyproxy.envoymobile.engine.AndroidJniLibrary; +import io.envoyproxy.envoymobile.Standard +import io.envoyproxy.envoymobile.engine.AndroidJniLibrary import io.envoyproxy.envoymobile.engine.EnvoyConfiguration.TrustChainVerification +import io.envoyproxy.envoymobile.engine.JniLibrary +import io.envoyproxy.envoymobile.engine.testing.TestJni import java.nio.ByteBuffer import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -15,7 +15,8 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val ASSERTION_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion" +private const val ASSERTION_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion" private const val REQUEST_STRING_MATCH = "match_me" class SendDataTest { @@ -30,37 +31,39 @@ class SendDataTest { val port = TestJni.getServerPort() val expectation = CountDownLatch(1) - val engine = EngineBuilder(Standard()) - .addNativeFilter("envoy.filters.http.assertion", "{'@type': $ASSERTION_FILTER_TYPE, match_config: {http_request_generic_body_match: {patterns: [{string_match: $REQUEST_STRING_MATCH}]}}}") - .setTrustChainVerification(TrustChainVerification.ACCEPT_UNTRUSTED) - .build() + val engine = + EngineBuilder(Standard()) + .addNativeFilter( + "envoy.filters.http.assertion", + "{'@type': $ASSERTION_FILTER_TYPE, match_config: {http_request_generic_body_match: {patterns: [{string_match: $REQUEST_STRING_MATCH}]}}}" + ) + .setTrustChainVerification(TrustChainVerification.ACCEPT_UNTRUSTED) + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "localhost:$port", - path = "/simple.txt" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "localhost:$port", + path = "/simple.txt" + ) + .build() val body = ByteBuffer.wrap(REQUEST_STRING_MATCH.toByteArray(Charsets.UTF_8)) var responseStatus: Int? = null var responseEndStream = false - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { headers, endStream, _ -> responseStatus = headers.httpStatus responseEndStream = endStream expectation.countDown() } - .setOnResponseData { _, endStream, _ -> - responseEndStream = endStream - } - .setOnError { _, _ -> - fail("Unexpected error") - } + .setOnResponseData { _, endStream, _ -> responseEndStream = endStream } + .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeaders, false) .close(body) @@ -68,7 +71,7 @@ class SendDataTest { expectation.await(10, TimeUnit.SECONDS) engine.terminate() - TestJni.shutdownTestServer(); + TestJni.shutdownTestServer() assertThat(expectation.count).isEqualTo(0) assertThat(responseStatus).isEqualTo(200) diff --git a/mobile/test/kotlin/integration/SendHeadersTest.kt b/mobile/test/kotlin/integration/SendHeadersTest.kt index 496bf982cbb6..1304a20bf38e 100644 --- a/mobile/test/kotlin/integration/SendHeadersTest.kt +++ b/mobile/test/kotlin/integration/SendHeadersTest.kt @@ -1,10 +1,10 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod import io.envoyproxy.envoymobile.ResponseHeaders +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.engine.JniLibrary import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -12,7 +12,8 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" class SendHeadersTest { @@ -24,30 +25,31 @@ class SendHeadersTest { fun `successful sending of request headers`() { val headersExpectation = CountDownLatch(1) - val engine = EngineBuilder(Standard()) - .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") - .build() + val engine = + EngineBuilder(Standard()) + .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() var resultHeaders: ResponseHeaders? = null var resultEndStream: Boolean? = null - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { responseHeaders, endStream, _ -> resultHeaders = responseHeaders resultEndStream = endStream headersExpectation.countDown() } - .setOnResponseData { _, endStream, _ -> - resultEndStream = endStream - } + .setOnResponseData { _, endStream, _ -> resultEndStream = endStream } .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeaders, true) diff --git a/mobile/test/kotlin/integration/SendTrailersTest.kt b/mobile/test/kotlin/integration/SendTrailersTest.kt index 22c7e0d6b78e..ff5e906b5917 100644 --- a/mobile/test/kotlin/integration/SendTrailersTest.kt +++ b/mobile/test/kotlin/integration/SendTrailersTest.kt @@ -1,10 +1,10 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod import io.envoyproxy.envoymobile.RequestTrailersBuilder +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer import java.util.concurrent.CountDownLatch @@ -13,8 +13,10 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" -private const val ASSERTION_FILTER_TYPE = "type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val ASSERTION_FILTER_TYPE = + "type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion" private const val MATCHER_TRAILER_NAME = "test-trailer" private const val MATCHER_TRAILER_VALUE = "test.code" @@ -28,36 +30,42 @@ class SendTrailersTest { fun `successful sending of trailers`() { val expectation = CountDownLatch(1) - val engine = EngineBuilder(Standard()) - .addNativeFilter("envoy.filters.http.assertion", "{'@type': $ASSERTION_FILTER_TYPE, match_config: {http_request_trailers_match: {headers: [{name: 'test-trailer', exact_match: 'test.code'}]}}}") - .addNativeFilter("envoy.filters.http.buffer", "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":65000}") - .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") - .build() + val engine = + EngineBuilder(Standard()) + .addNativeFilter( + "envoy.filters.http.assertion", + "{'@type': $ASSERTION_FILTER_TYPE, match_config: {http_request_trailers_match: {headers: [{name: 'test-trailer', exact_match: 'test.code'}]}}}" + ) + .addNativeFilter( + "envoy.filters.http.buffer", + "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":65000}" + ) + .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() val body = ByteBuffer.wrap("match_me".toByteArray(Charsets.UTF_8)) - val requestTrailers = RequestTrailersBuilder() - .add(MATCHER_TRAILER_NAME, MATCHER_TRAILER_VALUE) - .build() + val requestTrailers = + RequestTrailersBuilder().add(MATCHER_TRAILER_NAME, MATCHER_TRAILER_VALUE).build() var responseStatus: Int? = null - client.newStreamPrototype() + client + .newStreamPrototype() .setOnResponseHeaders { headers, _, _ -> responseStatus = headers.httpStatus expectation.countDown() } - .setOnError { _, _ -> - fail("Unexpected error") - } + .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeaders, false) .sendData(body) diff --git a/mobile/test/kotlin/integration/SetEventTrackerTest.kt b/mobile/test/kotlin/integration/SetEventTrackerTest.kt index 46dcf388e467..a6630eedb03f 100644 --- a/mobile/test/kotlin/integration/SetEventTrackerTest.kt +++ b/mobile/test/kotlin/integration/SetEventTrackerTest.kt @@ -19,34 +19,33 @@ class SetEventTrackerTest { @Test fun `set eventTracker`() { val countDownLatch = CountDownLatch(1) - val engine = EngineBuilder() - .setEventTracker { events -> - for (entry in events) { - assertThat(entry.key).isEqualTo("foo") - assertThat(entry.value).isEqualTo("bar") + val engine = + EngineBuilder() + .setEventTracker { events -> + for (entry in events) { + assertThat(entry.key).isEqualTo("foo") + assertThat(entry.value).isEqualTo("bar") + } + countDownLatch.countDown() } - countDownLatch.countDown() - } - .addNativeFilter( - "envoy.filters.http.test_event_tracker", - "{\"@type\":\"type.googleapis.com/envoymobile.extensions.filters.http.test_event_tracker.TestEventTracker\",\"attributes\":{\"foo\":\"bar\"}}" - ) - .build() + .addNativeFilter( + "envoy.filters.http.test_event_tracker", + "{\"@type\":\"type.googleapis.com/envoymobile.extensions.filters.http.test_event_tracker.TestEventTracker\",\"attributes\":{\"foo\":\"bar\"}}" + ) + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() - client - .newStreamPrototype() - .start() - .sendHeaders(requestHeaders, true) + client.newStreamPrototype().start().sendHeaders(requestHeaders, true) countDownLatch.await(30, TimeUnit.SECONDS) engine.terminate() @@ -56,20 +55,19 @@ class SetEventTrackerTest { @Test fun `engine should continue to run if no eventTracker is set and event is emitted`() { val countDownLatch = CountDownLatch(1) - val engine = EngineBuilder() - .addNativeFilter( - "envoy.filters.http.test_event_tracker", - "{\"@type\":\"type.googleapis.com/envoymobile.extensions.filters.http.test_event_tracker.TestEventTracker\",\"attributes\":{\"foo\":\"bar\"}}" - ) - .build() + val engine = + EngineBuilder() + .addNativeFilter( + "envoy.filters.http.test_event_tracker", + "{\"@type\":\"type.googleapis.com/envoymobile.extensions.filters.http.test_event_tracker.TestEventTracker\",\"attributes\":{\"foo\":\"bar\"}}" + ) + .build() val client = engine.streamClient() client .newStreamPrototype() - .setOnResponseData { _, _, _ -> - countDownLatch.countDown() - } + .setOnResponseData { _, _, _ -> countDownLatch.countDown() } .start() .close(ByteBuffer.allocate(1)) diff --git a/mobile/test/kotlin/integration/SetLoggerTest.kt b/mobile/test/kotlin/integration/SetLoggerTest.kt index b7d1f3fd398c..0e3b9f980488 100644 --- a/mobile/test/kotlin/integration/SetLoggerTest.kt +++ b/mobile/test/kotlin/integration/SetLoggerTest.kt @@ -1,11 +1,11 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.Engine import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.engine.JniLibrary import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -22,20 +22,24 @@ class SetLoggerTest { fun `set logger`() { val countDownLatch = CountDownLatch(1) val logEventLatch = CountDownLatch(1) - val engine = EngineBuilder(Standard()) - .addLogLevel(LogLevel.DEBUG) - .addNativeFilter("test_logger", "{\"@type\":\"type.googleapis.com/envoymobile.extensions.filters.http.test_logger.TestLogger\"}") - .setLogger { msg -> - if (msg.contains("starting main dispatch loop")) { - countDownLatch.countDown() + val engine = + EngineBuilder(Standard()) + .addLogLevel(LogLevel.DEBUG) + .addNativeFilter( + "test_logger", + "{\"@type\":\"type.googleapis.com/envoymobile.extensions.filters.http.test_logger.TestLogger\"}" + ) + .setLogger { msg -> + if (msg.contains("starting main dispatch loop")) { + countDownLatch.countDown() + } } - } - .setEventTracker { event -> - if (event["log_name"] == "event_name") { - logEventLatch.countDown() + .setEventTracker { event -> + if (event["log_name"] == "event_name") { + logEventLatch.countDown() + } } - } - .build() + .build() countDownLatch.await(30, TimeUnit.SECONDS) @@ -52,18 +56,20 @@ class SetLoggerTest { fun `engine should continue to run if no logger is set`() { val countDownLatch = CountDownLatch(1) val logEventLatch = CountDownLatch(1) - val engine = EngineBuilder(Standard()) - .setEventTracker { event -> - if (event["log_name"] == "event_name") { - logEventLatch.countDown() + val engine = + EngineBuilder(Standard()) + .setEventTracker { event -> + if (event["log_name"] == "event_name") { + logEventLatch.countDown() + } } - } - .addLogLevel(LogLevel.DEBUG) - .addNativeFilter("test_logger", "{\"@type\":\"type.googleapis.com/envoymobile.extensions.filters.http.test_logger.TestLogger\"}") - .setOnEngineRunning { - countDownLatch.countDown() - } - .build() + .addLogLevel(LogLevel.DEBUG) + .addNativeFilter( + "test_logger", + "{\"@type\":\"type.googleapis.com/envoymobile.extensions.filters.http.test_logger.TestLogger\"}" + ) + .setOnEngineRunning { countDownLatch.countDown() } + .build() countDownLatch.await(30, TimeUnit.SECONDS) @@ -78,16 +84,15 @@ class SetLoggerTest { fun sendRequest(engine: Engine) { val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() - client.newStreamPrototype() - .start() - .sendHeaders(requestHeaders, true) + client.newStreamPrototype().start().sendHeaders(requestHeaders, true) } } diff --git a/mobile/test/kotlin/integration/StatFlushIntegrationTest.kt b/mobile/test/kotlin/integration/StatFlushIntegrationTest.kt index 94950f35d968..1a0a746a3106 100644 --- a/mobile/test/kotlin/integration/StatFlushIntegrationTest.kt +++ b/mobile/test/kotlin/integration/StatFlushIntegrationTest.kt @@ -27,14 +27,15 @@ class StatFlushIntegrationTest { @Test fun `multiple stat sinks configured`() { val countDownLatch = CountDownLatch(1) - engine = EngineBuilder() - .addLogLevel(LogLevel.DEBUG) - // Really high flush interval so it won't trigger during test execution. - .addStatsFlushSeconds(100) - .addStatsSinks(listOf(statsdSinkConfig(8125), statsdSinkConfig(5000))) - .addGrpcStatsDomain("example.com") - .setOnEngineRunning { countDownLatch.countDown() } - .build() + engine = + EngineBuilder() + .addLogLevel(LogLevel.DEBUG) + // Really high flush interval so it won't trigger during test execution. + .addStatsFlushSeconds(100) + .addStatsSinks(listOf(statsdSinkConfig(8125), statsdSinkConfig(5000))) + .addGrpcStatsDomain("example.com") + .setOnEngineRunning { countDownLatch.countDown() } + .build() assertThat(countDownLatch.await(30, TimeUnit.SECONDS)).isTrue() } @@ -42,13 +43,14 @@ class StatFlushIntegrationTest { @Test fun `flush flushes to stats sink`() { val countDownLatch = CountDownLatch(1) - engine = EngineBuilder() - .addLogLevel(LogLevel.DEBUG) - // Really high flush interval so it won't trigger during test execution. - .addStatsFlushSeconds(100) - .addStatsSinks(listOf(statsdSinkConfig(8125), statsdSinkConfig(5000))) - .setOnEngineRunning { countDownLatch.countDown() } - .build() + engine = + EngineBuilder() + .addLogLevel(LogLevel.DEBUG) + // Really high flush interval so it won't trigger during test execution. + .addStatsFlushSeconds(100) + .addStatsSinks(listOf(statsdSinkConfig(8125), statsdSinkConfig(5000))) + .setOnEngineRunning { countDownLatch.countDown() } + .build() assertThat(countDownLatch.await(30, TimeUnit.SECONDS)).isTrue() @@ -69,7 +71,7 @@ class StatFlushIntegrationTest { } private fun statsdSinkConfig(port: Int): String { -return """{ name: envoy.stat_sinks.statsd, + return """{ name: envoy.stat_sinks.statsd, typed_config: { "@type": type.googleapis.com/envoy.config.metrics.v3.StatsdSink, address: { socket_address: { address: 127.0.0.1, port_value: $port } } } }""" diff --git a/mobile/test/kotlin/integration/StreamIdleTimeoutTest.kt b/mobile/test/kotlin/integration/StreamIdleTimeoutTest.kt index c02f38a0bdf6..f2d10e030593 100644 --- a/mobile/test/kotlin/integration/StreamIdleTimeoutTest.kt +++ b/mobile/test/kotlin/integration/StreamIdleTimeoutTest.kt @@ -1,6 +1,5 @@ package test.kotlin.integration -import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.EngineBuilder import io.envoyproxy.envoymobile.EnvoyError import io.envoyproxy.envoymobile.FilterDataStatus @@ -12,6 +11,7 @@ import io.envoyproxy.envoymobile.RequestMethod import io.envoyproxy.envoymobile.ResponseFilter import io.envoyproxy.envoymobile.ResponseHeaders import io.envoyproxy.envoymobile.ResponseTrailers +import io.envoyproxy.envoymobile.Standard import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary import java.nio.ByteBuffer @@ -22,8 +22,9 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test -private const val TEST_RESPONSE_FILTER_TYPE = "type.googleapis.com/" + - "envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" +private const val TEST_RESPONSE_FILTER_TYPE = + "type.googleapis.com/" + + "envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse" class StreamIdleTimeoutTest { @@ -34,9 +35,7 @@ class StreamIdleTimeoutTest { private val filterExpectation = CountDownLatch(1) private val callbackExpectation = CountDownLatch(1) - class IdleTimeoutValidationFilter( - private val latch: CountDownLatch - ) : ResponseFilter { + class IdleTimeoutValidationFilter(private val latch: CountDownLatch) : ResponseFilter { override fun onResponseHeaders( headers: ResponseHeaders, endStream: Boolean, @@ -64,6 +63,7 @@ class StreamIdleTimeoutTest { assertThat(error.errorCode).isEqualTo(4) latch.countDown() } + override fun onComplete(finalStreamIntel: FinalStreamIntel) {} override fun onCancel(finalStreamIntel: FinalStreamIntel) { @@ -73,26 +73,29 @@ class StreamIdleTimeoutTest { @Test fun `stream idle timeout triggers onError callbacks`() { - val engine = EngineBuilder(Standard()) - .addPlatformFilter( - name = "idle_timeout_validation_filter", - factory = { IdleTimeoutValidationFilter(filterExpectation) } - ) - .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") - .addStreamIdleTimeoutSeconds(1) - .build() + val engine = + EngineBuilder(Standard()) + .addPlatformFilter( + name = "idle_timeout_validation_filter", + factory = { IdleTimeoutValidationFilter(filterExpectation) } + ) + .addNativeFilter("test_remote_response", "{'@type': $TEST_RESPONSE_FILTER_TYPE}") + .addStreamIdleTimeoutSeconds(1) + .build() val client = engine.streamClient() - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "example.com", - path = "/test" - ) - .build() - - client.newStreamPrototype() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .build() + + client + .newStreamPrototype() .setOnError { error, _ -> assertThat(error.errorCode).isEqualTo(4) callbackExpectation.countDown() diff --git a/mobile/test/kotlin/integration/TestStatsdServer.kt b/mobile/test/kotlin/integration/TestStatsdServer.kt index 9e380e4e4c14..c8c21988ae50 100644 --- a/mobile/test/kotlin/integration/TestStatsdServer.kt +++ b/mobile/test/kotlin/integration/TestStatsdServer.kt @@ -20,32 +20,33 @@ class TestStatsdServer { val socket = DatagramSocket(port) socket.setSoTimeout(1000) // 1 second - thread = Thread( - fun() { - val buffer = ByteArray(256) - while (shutdownLatch.getCount() != 0L) { - val packet = DatagramPacket(buffer, buffer.size) - try { - socket.receive(packet) - } catch (e: SocketTimeoutException) { - // continue to next loop - continue - } catch (e: Exception) { - // TODO(snowp): Bubble up this error somehow. - return - } + thread = + Thread( + fun() { + val buffer = ByteArray(256) + while (shutdownLatch.getCount() != 0L) { + val packet = DatagramPacket(buffer, buffer.size) + try { + socket.receive(packet) + } catch (e: SocketTimeoutException) { + // continue to next loop + continue + } catch (e: Exception) { + // TODO(snowp): Bubble up this error somehow. + return + } - // TODO(snowp): Parse (or use a parser) so we can extract out individual metric names - // better. - val received = String(packet.getData(), packet.getOffset(), packet.getLength()) - val maybeMatch = matchCriteria.get() - if (maybeMatch != null && maybeMatch.invoke(received)) { - matchCriteria.set(null) - awaitNextStat.get().countDown() + // TODO(snowp): Parse (or use a parser) so we can extract out individual metric names + // better. + val received = String(packet.getData(), packet.getOffset(), packet.getLength()) + val maybeMatch = matchCriteria.get() + if (maybeMatch != null && maybeMatch.invoke(received)) { + matchCriteria.set(null) + awaitNextStat.get().countDown() + } } } - } - ) + ) thread!!.start() } diff --git a/mobile/test/kotlin/integration/TestUtilities.kt b/mobile/test/kotlin/integration/TestUtilities.kt index e450972f2663..acb78ab2870e 100644 --- a/mobile/test/kotlin/integration/TestUtilities.kt +++ b/mobile/test/kotlin/integration/TestUtilities.kt @@ -8,9 +8,7 @@ import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import org.junit.Assert.fail -/** - * Gets the stats in the form of `Map`. - */ +/** Gets the stats in the form of `Map`. */ fun Engine.getStats(): Map { // `dumpStats()` produces the following format: // key1: value1 @@ -18,25 +16,28 @@ fun Engine.getStats(): Map { // key3: value3 // ... val lines = dumpStats().split("\n") - return lines.mapNotNull { - val keyValue = it.split(": ") - if (keyValue.size == 2) { - Pair(keyValue[0], keyValue[1]) - } else { - null + return lines + .mapNotNull { + val keyValue = it.split(": ") + if (keyValue.size == 2) { + Pair(keyValue[0], keyValue[1]) + } else { + null + } } - }.toMap() + .toMap() } /** - * Waits for 5 seconds (default) until the stat of the given [name] is greater - * than equal the specified [expectedValue]. + * Waits for 5 seconds (default) until the stat of the given [name] is greater than equal the + * specified [expectedValue]. * - * @throws java.lang.AssertionError throw when the the operation timed out - * waiting for the stat value to be greater than the [expectedValue]. + * @throws java.lang.AssertionError throw when the the operation timed out waiting for the stat + * value to be greater than the [expectedValue]. */ fun Engine.waitForStatGe( - name: String, expectedValue: Long, + name: String, + expectedValue: Long, timeout: Duration = 5.seconds, ) { val waitTime = Instant.now().plusMillis(timeout.toLong(DurationUnit.MILLISECONDS)) @@ -49,4 +50,3 @@ fun Engine.waitForStatGe( currentValue = getStats()[name] } } - diff --git a/mobile/test/kotlin/integration/XdsTest.kt b/mobile/test/kotlin/integration/XdsTest.kt index 636df24b4be1..6994a5f15b5e 100644 --- a/mobile/test/kotlin/integration/XdsTest.kt +++ b/mobile/test/kotlin/integration/XdsTest.kt @@ -10,8 +10,6 @@ import io.envoyproxy.envoymobile.engine.AndroidJniLibrary import io.envoyproxy.envoymobile.engine.JniLibrary import io.envoyproxy.envoymobile.engine.testing.TestJni import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before import org.junit.Test @@ -33,18 +31,18 @@ class XdsTest { TestJni.startHttp2TestServer() TestJni.initXdsTestServer() val latch = CountDownLatch(1) - engine = AndroidEngineBuilder(appContext) - .addLogLevel(LogLevel.DEBUG) - .setOnEngineRunning { - latch.countDown() - } - .setXds( - XdsBuilder( - TestJni.getXdsTestServerHost(), - TestJni.getXdsTestServerPort(), - ).addClusterDiscoveryService() - ) - .build() + engine = + AndroidEngineBuilder(appContext) + .addLogLevel(LogLevel.DEBUG) + .setOnEngineRunning { latch.countDown() } + .setXds( + XdsBuilder( + TestJni.getXdsTestServerHost(), + TestJni.getXdsTestServerPort(), + ) + .addClusterDiscoveryService() + ) + .build() latch.await() TestJni.startXdsTestServer() } @@ -59,8 +57,9 @@ class XdsTest { @Test fun `test xDS with CDS`() { // There are 2 initial clusters: base and base_clear. - engine.waitForStatGe("cluster_manager.cluster_added", 2) - val cdsResponse = """ + engine.waitForStatGe("cluster_manager.cluster_added", 2) + val cdsResponse = + """ version_info: v1 resources: - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster @@ -84,7 +83,8 @@ class XdsTest { {} type_url: type.googleapis.com/envoy.config.cluster.v3.Cluster nonce: nonce1 - """.trimIndent() + """ + .trimIndent() TestJni.sendDiscoveryResponse(cdsResponse) // There are now 3 clusters: base, base_cluster, and xds_cluster. engine.waitForStatGe("cluster_manager.cluster_added", 3) diff --git a/mobile/test/kotlin/integration/proxying/Proxy.kt b/mobile/test/kotlin/integration/proxying/Proxy.kt index 26f5a31edb63..5228dd160b2e 100644 --- a/mobile/test/kotlin/integration/proxying/Proxy.kt +++ b/mobile/test/kotlin/integration/proxying/Proxy.kt @@ -1,19 +1,16 @@ package test.kotlin.integration.proxying import android.content.Context - import io.envoyproxy.envoymobile.AndroidEngineBuilder import io.envoyproxy.envoymobile.Custom import io.envoyproxy.envoymobile.EngineBuilder -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - // A convenient wrapper for creating an builder for an engine that // proxies network requests. class Proxy constructor(val context: Context, val port: Int) { - fun http(): EngineBuilder { - val config = """ + fun http(): EngineBuilder { + val config = + """ static_resources: listeners: - name: base_api_listener @@ -96,11 +93,12 @@ layered_runtime: # Global stats do not play well with engines with limited lifetimes disallow_global_stats: true """ - return AndroidEngineBuilder(context, Custom(config)) - } + return AndroidEngineBuilder(context, Custom(config)) + } - fun https(): EngineBuilder { - val config = """ + fun https(): EngineBuilder { + val config = + """ static_resources: listeners: - name: base_api_listener @@ -187,6 +185,6 @@ layered_runtime: # Global stats do not play well with engines with limited lifetimes disallow_global_stats: true """ - return AndroidEngineBuilder(context, Custom(config)) - } + return AndroidEngineBuilder(context, Custom(config)) + } } diff --git a/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPRequestUsingProxyTest.kt b/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPRequestUsingProxyTest.kt index b651c34f7411..e848db0f7bf7 100644 --- a/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPRequestUsingProxyTest.kt +++ b/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPRequestUsingProxyTest.kt @@ -1,30 +1,22 @@ package test.kotlin.integration.proxying -import android.content.Intent import android.content.Context +import android.content.Intent import android.net.ConnectivityManager import android.net.Proxy import android.net.ProxyInfo import androidx.test.core.app.ApplicationProvider - import io.envoyproxy.envoymobile.AndroidEngineBuilder -import io.envoyproxy.envoymobile.Custom -import io.envoyproxy.envoymobile.Engine -import io.envoyproxy.envoymobile.engine.JniLibrary import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod -import io.envoyproxy.envoymobile.ResponseHeaders -import io.envoyproxy.envoymobile.StreamIntel - +import io.envoyproxy.envoymobile.engine.JniLibrary import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit - import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.robolectric.RobolectricTestRunner @@ -51,19 +43,22 @@ class PerformHTTPRequestUsingProxy { val context = Mockito.spy(ApplicationProvider.getApplicationContext()) val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java) - Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE) - Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port)) + Mockito.doReturn(connectivityManager) + .`when`(context) + .getSystemService(Context.CONNECTIVITY_SERVICE) + Mockito.`when`(connectivityManager.getDefaultProxy()) + .thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port)) val onProxyEngineRunningLatch = CountDownLatch(1) val onEngineRunningLatch = CountDownLatch(1) val onRespondeHeadersLatch = CountDownLatch(1) - val proxyEngineBuilder = Proxy(context, port) - .http() - val proxyEngine = proxyEngineBuilder - .addLogLevel(LogLevel.DEBUG) - .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } - .build() + val proxyEngineBuilder = Proxy(context, port).http() + val proxyEngine = + proxyEngineBuilder + .addLogLevel(LogLevel.DEBUG) + .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } + .build() onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onProxyEngineRunningLatch.count).isEqualTo(0) @@ -71,22 +66,24 @@ class PerformHTTPRequestUsingProxy { context.sendStickyBroadcast(Intent(Proxy.PROXY_CHANGE_ACTION)) val builder = AndroidEngineBuilder(context) - val engine = builder - .addLogLevel(LogLevel.DEBUG) - .enableProxying(true) - .setOnEngineRunning { onEngineRunningLatch.countDown() } - .build() + val engine = + builder + .addLogLevel(LogLevel.DEBUG) + .enableProxying(true) + .setOnEngineRunning { onEngineRunningLatch.countDown() } + .build() onEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onEngineRunningLatch.count).isEqualTo(0) - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "http", - authority = "api.lyft.com", - path = "/ping" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "http", + authority = "api.lyft.com", + path = "/ping" + ) + .build() engine .streamClient() diff --git a/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestBadHostnameTest.kt b/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestBadHostnameTest.kt index a2f6ce0c3b24..eff4e5f08593 100644 --- a/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestBadHostnameTest.kt +++ b/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestBadHostnameTest.kt @@ -1,30 +1,22 @@ package test.kotlin.integration.proxying -import android.content.Intent import android.content.Context +import android.content.Intent import android.net.ConnectivityManager import android.net.Proxy import android.net.ProxyInfo import androidx.test.core.app.ApplicationProvider - -import io.envoyproxy.envoymobile.LogLevel -import io.envoyproxy.envoymobile.Custom -import io.envoyproxy.envoymobile.Engine import io.envoyproxy.envoymobile.AndroidEngineBuilder +import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod -import io.envoyproxy.envoymobile.ResponseHeaders -import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary - import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit - import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.robolectric.RobolectricTestRunner @@ -51,19 +43,23 @@ class PerformHTTPSRequestBadHostname { val context = Mockito.spy(ApplicationProvider.getApplicationContext()) val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java) - Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE) - Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("loopback", port)) + Mockito.doReturn(connectivityManager) + .`when`(context) + .getSystemService(Context.CONNECTIVITY_SERVICE) + Mockito.`when`(connectivityManager.getDefaultProxy()) + .thenReturn(ProxyInfo.buildDirectProxy("loopback", port)) val onEngineRunningLatch = CountDownLatch(1) val onProxyEngineRunningLatch = CountDownLatch(1) val onErrorLatch = CountDownLatch(1) val proxyEngineBuilder = Proxy(context, port).https() - val proxyEngine = proxyEngineBuilder - .addLogLevel(LogLevel.DEBUG) - .addDNSQueryTimeoutSeconds(2) - .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } - .build() + val proxyEngine = + proxyEngineBuilder + .addLogLevel(LogLevel.DEBUG) + .addDNSQueryTimeoutSeconds(2) + .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } + .build() onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onProxyEngineRunningLatch.count).isEqualTo(0) @@ -71,29 +67,29 @@ class PerformHTTPSRequestBadHostname { context.sendStickyBroadcast(Intent(Proxy.PROXY_CHANGE_ACTION)) val builder = AndroidEngineBuilder(context) - val engine = builder - .addLogLevel(LogLevel.DEBUG) - .enableProxying(true) - .setOnEngineRunning { onEngineRunningLatch.countDown() } - .build() + val engine = + builder + .addLogLevel(LogLevel.DEBUG) + .enableProxying(true) + .setOnEngineRunning { onEngineRunningLatch.countDown() } + .build() onEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onEngineRunningLatch.count).isEqualTo(0) - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "api.lyft.com", - path = "/ping" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "api.lyft.com", + path = "/ping" + ) + .build() engine .streamClient() .newStreamPrototype() - .setOnError { _, _ -> - onErrorLatch.countDown() - } + .setOnError { _, _ -> onErrorLatch.countDown() } .start(Executors.newSingleThreadExecutor()) .sendHeaders(requestHeaders, true) diff --git a/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestUsingAsyncProxyTest.kt b/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestUsingAsyncProxyTest.kt index c69cdfa55eea..ddfd3e7750df 100644 --- a/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestUsingAsyncProxyTest.kt +++ b/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestUsingAsyncProxyTest.kt @@ -1,30 +1,22 @@ package test.kotlin.integration.proxying -import android.content.Intent import android.content.Context +import android.content.Intent import android.net.ConnectivityManager import android.net.Proxy import android.net.ProxyInfo import androidx.test.core.app.ApplicationProvider - -import io.envoyproxy.envoymobile.LogLevel -import io.envoyproxy.envoymobile.Custom -import io.envoyproxy.envoymobile.Engine import io.envoyproxy.envoymobile.AndroidEngineBuilder +import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod -import io.envoyproxy.envoymobile.ResponseHeaders -import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary - import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit - import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.robolectric.RobolectricTestRunner @@ -51,18 +43,22 @@ class PerformHTTPSRequestUsingAsyncProxyTest { val context = Mockito.spy(ApplicationProvider.getApplicationContext()) val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java) - Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE) - Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("localhost", port)) + Mockito.doReturn(connectivityManager) + .`when`(context) + .getSystemService(Context.CONNECTIVITY_SERVICE) + Mockito.`when`(connectivityManager.getDefaultProxy()) + .thenReturn(ProxyInfo.buildDirectProxy("localhost", port)) val onEngineRunningLatch = CountDownLatch(1) val onProxyEngineRunningLatch = CountDownLatch(1) val onRespondeHeadersLatch = CountDownLatch(1) val proxyEngineBuilder = Proxy(ApplicationProvider.getApplicationContext(), port).https() - val proxyEngine = proxyEngineBuilder - .addLogLevel(LogLevel.DEBUG) - .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } - .build() + val proxyEngine = + proxyEngineBuilder + .addLogLevel(LogLevel.DEBUG) + .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } + .build() onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onProxyEngineRunningLatch.count).isEqualTo(0) @@ -70,22 +66,24 @@ class PerformHTTPSRequestUsingAsyncProxyTest { context.sendStickyBroadcast(Intent(Proxy.PROXY_CHANGE_ACTION)) val builder = AndroidEngineBuilder(context) - val engine = builder - .addLogLevel(LogLevel.DEBUG) - .enableProxying(true) - .setOnEngineRunning { onEngineRunningLatch.countDown() } - .build() + val engine = + builder + .addLogLevel(LogLevel.DEBUG) + .enableProxying(true) + .setOnEngineRunning { onEngineRunningLatch.countDown() } + .build() onEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onEngineRunningLatch.count).isEqualTo(0) - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "api.lyft.com", - path = "/ping" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "api.lyft.com", + path = "/ping" + ) + .build() engine .streamClient() diff --git a/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestUsingProxyTest.kt b/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestUsingProxyTest.kt index 2eea4d7f8c11..68aa0e19076e 100644 --- a/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestUsingProxyTest.kt +++ b/mobile/test/kotlin/integration/proxying/ProxyInfoIntentPerformHTTPSRequestUsingProxyTest.kt @@ -1,30 +1,22 @@ package test.kotlin.integration.proxying -import android.content.Intent import android.content.Context +import android.content.Intent import android.net.ConnectivityManager import android.net.Proxy import android.net.ProxyInfo import androidx.test.core.app.ApplicationProvider - -import io.envoyproxy.envoymobile.LogLevel -import io.envoyproxy.envoymobile.Custom -import io.envoyproxy.envoymobile.Engine import io.envoyproxy.envoymobile.AndroidEngineBuilder +import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod -import io.envoyproxy.envoymobile.ResponseHeaders -import io.envoyproxy.envoymobile.StreamIntel import io.envoyproxy.envoymobile.engine.JniLibrary - import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit - import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.robolectric.RobolectricTestRunner @@ -51,18 +43,22 @@ class PerformHTTPSRequestUsingProxy { val context = Mockito.spy(ApplicationProvider.getApplicationContext()) val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java) - Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE) - Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port)) + Mockito.doReturn(connectivityManager) + .`when`(context) + .getSystemService(Context.CONNECTIVITY_SERVICE) + Mockito.`when`(connectivityManager.getDefaultProxy()) + .thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port)) val onEngineRunningLatch = CountDownLatch(1) val onProxyEngineRunningLatch = CountDownLatch(1) val onRespondeHeadersLatch = CountDownLatch(1) val proxyEngineBuilder = Proxy(context, port).https() - val proxyEngine = proxyEngineBuilder - .addLogLevel(LogLevel.DEBUG) - .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } - .build() + val proxyEngine = + proxyEngineBuilder + .addLogLevel(LogLevel.DEBUG) + .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } + .build() onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onProxyEngineRunningLatch.count).isEqualTo(0) @@ -70,22 +66,24 @@ class PerformHTTPSRequestUsingProxy { context.sendStickyBroadcast(Intent(Proxy.PROXY_CHANGE_ACTION)) val builder = AndroidEngineBuilder(context) - val engine = builder - .addLogLevel(LogLevel.DEBUG) - .enableProxying(true) - .setOnEngineRunning { onEngineRunningLatch.countDown() } - .build() + val engine = + builder + .addLogLevel(LogLevel.DEBUG) + .enableProxying(true) + .setOnEngineRunning { onEngineRunningLatch.countDown() } + .build() onEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onEngineRunningLatch.count).isEqualTo(0) - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "https", - authority = "api.lyft.com", - path = "/ping" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "api.lyft.com", + path = "/ping" + ) + .build() engine .streamClient() diff --git a/mobile/test/kotlin/integration/proxying/ProxyPollPerformHTTPRequestUsingProxyTest.kt b/mobile/test/kotlin/integration/proxying/ProxyPollPerformHTTPRequestUsingProxyTest.kt index 31030df2bab4..20b8a4fcca50 100644 --- a/mobile/test/kotlin/integration/proxying/ProxyPollPerformHTTPRequestUsingProxyTest.kt +++ b/mobile/test/kotlin/integration/proxying/ProxyPollPerformHTTPRequestUsingProxyTest.kt @@ -1,30 +1,21 @@ package test.kotlin.integration.proxying -import android.content.Intent import android.content.Context import android.net.ConnectivityManager import android.net.Proxy import android.net.ProxyInfo import androidx.test.core.app.ApplicationProvider - import io.envoyproxy.envoymobile.AndroidEngineBuilder -import io.envoyproxy.envoymobile.Custom -import io.envoyproxy.envoymobile.Engine -import io.envoyproxy.envoymobile.engine.JniLibrary import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod -import io.envoyproxy.envoymobile.ResponseHeaders -import io.envoyproxy.envoymobile.StreamIntel - +import io.envoyproxy.envoymobile.engine.JniLibrary import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit - import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.robolectric.RobolectricTestRunner @@ -51,40 +42,45 @@ class PerformHTTPRequestUsingProxy { val context = Mockito.spy(ApplicationProvider.getApplicationContext()) val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java) - Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE) - Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port)) + Mockito.doReturn(connectivityManager) + .`when`(context) + .getSystemService(Context.CONNECTIVITY_SERVICE) + Mockito.`when`(connectivityManager.getDefaultProxy()) + .thenReturn(ProxyInfo.buildDirectProxy("127.0.0.1", port)) val onProxyEngineRunningLatch = CountDownLatch(1) val onEngineRunningLatch = CountDownLatch(1) val onRespondeHeadersLatch = CountDownLatch(1) - val proxyEngineBuilder = Proxy(context, port) - .http() - val proxyEngine = proxyEngineBuilder - .addLogLevel(LogLevel.DEBUG) - .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } - .build() + val proxyEngineBuilder = Proxy(context, port).http() + val proxyEngine = + proxyEngineBuilder + .addLogLevel(LogLevel.DEBUG) + .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } + .build() onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onProxyEngineRunningLatch.count).isEqualTo(0) val builder = AndroidEngineBuilder(context) - val engine = builder - .addLogLevel(LogLevel.DEBUG) - .enableProxying(true) - .setOnEngineRunning { onEngineRunningLatch.countDown() } - .build() + val engine = + builder + .addLogLevel(LogLevel.DEBUG) + .enableProxying(true) + .setOnEngineRunning { onEngineRunningLatch.countDown() } + .build() onEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onEngineRunningLatch.count).isEqualTo(0) - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "http", - authority = "api.lyft.com", - path = "/ping" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "http", + authority = "api.lyft.com", + path = "/ping" + ) + .build() engine .streamClient() diff --git a/mobile/test/kotlin/integration/proxying/ProxyPollPerformHTTPRequestWithoutUsingPACProxyTest.kt b/mobile/test/kotlin/integration/proxying/ProxyPollPerformHTTPRequestWithoutUsingPACProxyTest.kt index 82eb5f982b75..dc25b7c3e828 100644 --- a/mobile/test/kotlin/integration/proxying/ProxyPollPerformHTTPRequestWithoutUsingPACProxyTest.kt +++ b/mobile/test/kotlin/integration/proxying/ProxyPollPerformHTTPRequestWithoutUsingPACProxyTest.kt @@ -1,31 +1,22 @@ package test.kotlin.integration.proxying -import android.content.Intent import android.content.Context import android.net.ConnectivityManager import android.net.Proxy import android.net.ProxyInfo import android.net.Uri import androidx.test.core.app.ApplicationProvider - import io.envoyproxy.envoymobile.AndroidEngineBuilder -import io.envoyproxy.envoymobile.Custom -import io.envoyproxy.envoymobile.Engine -import io.envoyproxy.envoymobile.engine.JniLibrary import io.envoyproxy.envoymobile.LogLevel import io.envoyproxy.envoymobile.RequestHeadersBuilder import io.envoyproxy.envoymobile.RequestMethod -import io.envoyproxy.envoymobile.ResponseHeaders -import io.envoyproxy.envoymobile.StreamIntel - +import io.envoyproxy.envoymobile.engine.JniLibrary import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit - import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.robolectric.RobolectricTestRunner @@ -54,40 +45,45 @@ class PerformHTTPRequestUsingProxy { val context = Mockito.spy(ApplicationProvider.getApplicationContext()) val connectivityManager: ConnectivityManager = Mockito.mock(ConnectivityManager::class.java) - Mockito.doReturn(connectivityManager).`when`(context).getSystemService(Context.CONNECTIVITY_SERVICE) - Mockito.`when`(connectivityManager.getDefaultProxy()).thenReturn(ProxyInfo.buildPacProxy(Uri.parse("https://example.com"))) + Mockito.doReturn(connectivityManager) + .`when`(context) + .getSystemService(Context.CONNECTIVITY_SERVICE) + Mockito.`when`(connectivityManager.getDefaultProxy()) + .thenReturn(ProxyInfo.buildPacProxy(Uri.parse("https://example.com"))) val onProxyEngineRunningLatch = CountDownLatch(1) val onEngineRunningLatch = CountDownLatch(1) val onRespondeHeadersLatch = CountDownLatch(1) - val proxyEngineBuilder = Proxy(context, port) - .http() - val proxyEngine = proxyEngineBuilder - .addLogLevel(LogLevel.DEBUG) - .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } - .build() + val proxyEngineBuilder = Proxy(context, port).http() + val proxyEngine = + proxyEngineBuilder + .addLogLevel(LogLevel.DEBUG) + .setOnEngineRunning { onProxyEngineRunningLatch.countDown() } + .build() onProxyEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onProxyEngineRunningLatch.count).isEqualTo(0) val builder = AndroidEngineBuilder(context) - val engine = builder - .addLogLevel(LogLevel.DEBUG) - .enableProxying(true) - .setOnEngineRunning { onEngineRunningLatch.countDown() } - .build() + val engine = + builder + .addLogLevel(LogLevel.DEBUG) + .enableProxying(true) + .setOnEngineRunning { onEngineRunningLatch.countDown() } + .build() onEngineRunningLatch.await(10, TimeUnit.SECONDS) assertThat(onEngineRunningLatch.count).isEqualTo(0) - val requestHeaders = RequestHeadersBuilder( - method = RequestMethod.GET, - scheme = "http", - authority = "api.lyft.com", - path = "/ping" - ) - .build() + val requestHeaders = + RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "http", + authority = "api.lyft.com", + path = "/ping" + ) + .build() engine .streamClient() @@ -95,7 +91,7 @@ class PerformHTTPRequestUsingProxy { .setOnResponseHeaders { responseHeaders, _, _ -> val status = responseHeaders.httpStatus ?: 0L assertThat(status).isEqualTo(301) - assertThat(responseHeaders.value("x-proxy-response")).isNull(); + assertThat(responseHeaders.value("x-proxy-response")).isNull() onRespondeHeadersLatch.countDown() } .start(Executors.newSingleThreadExecutor()) diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt index 1703b08690d7..7a7b212f186b 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/EngineBuilderTest.kt @@ -1,10 +1,10 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.EnvoyEngine +import io.envoyproxy.envoymobile.engine.JniLibrary import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.mockito.Mockito.mock -import io.envoyproxy.envoymobile.engine.JniLibrary class EngineBuilderTest { private lateinit var engineBuilder: EngineBuilder @@ -121,7 +121,8 @@ class EngineBuilderTest { engineBuilder.addH2ConnectionKeepaliveIdleIntervalMilliseconds(1234) val engine = engineBuilder.build() as EngineImpl - assertThat(engine.envoyConfiguration.h2ConnectionKeepaliveIdleIntervalMilliseconds).isEqualTo(1234) + assertThat(engine.envoyConfiguration.h2ConnectionKeepaliveIdleIntervalMilliseconds) + .isEqualTo(1234) } @Test @@ -210,9 +211,11 @@ class EngineBuilderTest { xdsBuilder.setAuthenticationToken("x-goog-api-key", "A1B2C3") xdsBuilder.setJwtAuthenticationToken("my_jwt_token") xdsBuilder.setSslRootCerts("my_root_certs") - xdsBuilder.setSni("fake_test_address"); + xdsBuilder.setSni("fake_test_address") xdsBuilder.addRuntimeDiscoveryService("some_rtds_resource") - xdsBuilder.addClusterDiscoveryService("xdstp://fake_test_address/envoy.config.cluster.v3.Cluster/xyz") + xdsBuilder.addClusterDiscoveryService( + "xdstp://fake_test_address/envoy.config.cluster.v3.Cluster/xyz" + ) engineBuilder = EngineBuilder(Standard()) engineBuilder.addEngineType { envoyEngine } engineBuilder.setXds(xdsBuilder) @@ -225,7 +228,8 @@ class EngineBuilderTest { assertThat(engine.envoyConfiguration.xdsRootCerts).isEqualTo("my_root_certs") assertThat(engine.envoyConfiguration.xdsSni).isEqualTo("fake_test_address") assertThat(engine.envoyConfiguration.rtdsResourceName).isEqualTo("some_rtds_resource") - assertThat(engine.envoyConfiguration.cdsResourcesLocator).isEqualTo("xdstp://fake_test_address/envoy.config.cluster.v3.Cluster/xyz") + assertThat(engine.envoyConfiguration.cdsResourcesLocator) + .isEqualTo("xdstp://fake_test_address/envoy.config.cluster.v3.Cluster/xyz") } @Test diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCRequestHeadersBuilderTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCRequestHeadersBuilderTest.kt index 63122ff60f4b..07bf88dd2e13 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCRequestHeadersBuilderTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCRequestHeadersBuilderTest.kt @@ -6,57 +6,59 @@ import org.junit.Test class GRPCRequestHeadersBuilderTest { @Test fun `adds scheme to header`() { - val headers = GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") - .build() + val headers = + GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar").build() assertThat(headers.value(":scheme")).containsExactly("https") assertThat(headers.scheme).isEqualTo("https") } @Test fun `adds authority to header`() { - val headers = GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") - .build() + val headers = + GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar").build() assertThat(headers.value(":authority")).containsExactly("envoyproxy.io") assertThat(headers.authority).isEqualTo("envoyproxy.io") } @Test fun `adds path to header`() { - val headers = GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") - .build() + val headers = + GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar").build() assertThat(headers.value(":path")).containsExactly("/pb.api.v1.Foo/GetBar") assertThat(headers.path).isEqualTo("/pb.api.v1.Foo/GetBar") } @Test fun `adds grpc content type header`() { - val headers = GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") - .build() + val headers = + GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar").build() assertThat(headers.value("content-type")).containsExactly("application/grpc") } @Test fun `uses http post`() { - val headers = GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") - .build() + val headers = + GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar").build() assertThat(headers.method).isEqualTo(RequestMethod.POST) assertThat(headers.value(":method")).containsExactly("POST") } @Test fun `adds timeout header when set to value`() { - val headers = GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") - .addtimeoutMs(200) - .build() + val headers = + GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") + .addtimeoutMs(200) + .build() assertThat(headers.value("grpc-timeout")).containsExactly("200m") } @Test fun `removes timeout header when set to null`() { - val headers = GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") - .addtimeoutMs(200) - .addtimeoutMs(null) - .build() + val headers = + GRPCRequestHeadersBuilder("https", "envoyproxy.io", "/pb.api.v1.Foo/GetBar") + .addtimeoutMs(200) + .addtimeoutMs(null) + .build() assertThat(headers.value("grpc-timeout")).isNull() } } diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCStreamTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCStreamTest.kt index 3cb4a2738ded..37a6c2ffc0ce 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCStreamTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCStreamTest.kt @@ -21,10 +21,7 @@ class GRPCStreamTest { stream.onRequestData = { data, _ -> sentData.write(data.array()) } } - GRPCClient(streamClient) - .newGRPCStreamPrototype() - .start(Executor {}) - .sendMessage(message1) + GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).sendMessage(message1) assertThat(sentData.size()).isEqualTo(5 + message1.array().count()) } @@ -36,10 +33,7 @@ class GRPCStreamTest { stream.onRequestData = { data, _ -> sentData.write(data.array()) } } - GRPCClient(streamClient) - .newGRPCStreamPrototype() - .start(Executor {}) - .sendMessage(message1) + GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).sendMessage(message1) assertThat(sentData.toByteArray()[0]).isEqualTo(0) } @@ -51,12 +45,10 @@ class GRPCStreamTest { stream.onRequestData = { data, _ -> sentData.write(data.array()) } } - GRPCClient(streamClient) - .newGRPCStreamPrototype() - .start(Executor {}) - .sendMessage(message1) + GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).sendMessage(message1) - val size = ByteBuffer.wrap(sentData.toByteArray().sliceArray(1 until 5)).order(ByteOrder.BIG_ENDIAN).int + val size = + ByteBuffer.wrap(sentData.toByteArray().sliceArray(1 until 5)).order(ByteOrder.BIG_ENDIAN).int assertThat(size).isEqualTo(message1.array().count()) } @@ -67,27 +59,20 @@ class GRPCStreamTest { stream.onRequestData = { data, _ -> sentData.write(data.array()) } } - GRPCClient(streamClient) - .newGRPCStreamPrototype() - .start(Executor {}) - .sendMessage(message1) + GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).sendMessage(message1) - assertThat(sentData.toByteArray().sliceArray(5 until sentData.size())).isEqualTo(message1.array()) + assertThat(sentData.toByteArray().sliceArray(5 until sentData.size())) + .isEqualTo(message1.array()) } @Test fun `cancel calls a stream callback`() { val countDownLatch = CountDownLatch(1) val streamClient = MockStreamClient { stream -> - stream.onCancel = { - countDownLatch.countDown() - } + stream.onCancel = { countDownLatch.countDown() } } - GRPCClient(streamClient) - .newGRPCStreamPrototype() - .start(Executor {}) - .cancel() + GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).cancel() assertThat(countDownLatch.await(2000, TimeUnit.MILLISECONDS)).isTrue() } @@ -97,7 +82,8 @@ class GRPCStreamTest { @Test(timeout = 1000L) fun `headers callback passes headers`() { val countDownLatch = CountDownLatch(1) - val expectedHeaders = ResponseHeaders(mapOf("grpc-status" to listOf("1"), "x-other" to listOf("foo", "bar"))) + val expectedHeaders = + ResponseHeaders(mapOf("grpc-status" to listOf("1"), "x-other" to listOf("foo", "bar"))) var stream: MockStream? = null val streamClient = MockStreamClient { stream = it } @@ -117,14 +103,16 @@ class GRPCStreamTest { @Test(timeout = 1000L) fun `trailers callback passes trailers`() { val countDownLatch = CountDownLatch(1) - val expectedTrailers = ResponseTrailers(mapOf("x-foo" to listOf("bar"), "x-baz" to listOf("1", "2"))) + val expectedTrailers = + ResponseTrailers(mapOf("x-foo" to listOf("bar"), "x-baz" to listOf("1", "2"))) var stream: MockStream? = null val streamClient = MockStreamClient { stream = it } GRPCClient(streamClient) .newGRPCStreamPrototype() .setOnResponseTrailers { trailers, _ -> - assertThat(trailers.caseSensitiveHeaders()).isEqualTo(expectedTrailers.caseSensitiveHeaders()) + assertThat(trailers.caseSensitiveHeaders()) + .isEqualTo(expectedTrailers.caseSensitiveHeaders()) countDownLatch.countDown() } .start(Executor {}) @@ -164,21 +152,30 @@ class GRPCStreamTest { val streamClient = MockStreamClient { stream = it } val firstMessage = byteArrayOf(0x1, 0x2, 0x3, 0x4, 0x5) - val firstMessageBuffer = ByteBuffer.wrap( - byteArrayOf( - 0x0, // Compression flag - 0x0, 0x0, 0x0, 0x5 // Length bytes - ) + firstMessage - ) + val firstMessageBuffer = + ByteBuffer.wrap( + byteArrayOf( + 0x0, // Compression flag + 0x0, + 0x0, + 0x0, + 0x5 // Length bytes + ) + firstMessage + ) val secondMessage = byteArrayOf(0x6, 0x7, 0x8, 0x9, 0x0, 0x1) - val secondMessageBufferPart1 = ByteBuffer.wrap( - byteArrayOf( - 0x0, // Compression flag - 0x0, 0x0, 0x0, 0x6 // Length bytes - ) + secondMessage.sliceArray(0 until 2) - ) - val secondMessageBufferPart2 = ByteBuffer.wrap(secondMessage.sliceArray(2 until secondMessage.count())) + val secondMessageBufferPart1 = + ByteBuffer.wrap( + byteArrayOf( + 0x0, // Compression flag + 0x0, + 0x0, + 0x0, + 0x6 // Length bytes + ) + secondMessage.sliceArray(0 until 2) + ) + val secondMessageBufferPart2 = + ByteBuffer.wrap(secondMessage.sliceArray(2 until secondMessage.count())) GRPCClient(streamClient) .newGRPCStreamPrototype() @@ -212,12 +209,16 @@ class GRPCStreamTest { } .start(Executor {}) - val emptyMessage = ByteBuffer.wrap( - byteArrayOf( - 0x0, // Compression flag - 0x0, 0x0, 0x0, 0x0 // Length bytes + val emptyMessage = + ByteBuffer.wrap( + byteArrayOf( + 0x0, // Compression flag + 0x0, + 0x0, + 0x0, + 0x0 // Length bytes + ) ) - ) stream?.receiveData(emptyMessage, false) countDownLatch.await() @@ -229,20 +230,28 @@ class GRPCStreamTest { var stream: MockStream? = null val streamClient = MockStreamClient { stream = it } - val emptyMessageBuffer = ByteBuffer.wrap( - byteArrayOf( - 0x0, // Compression flag - 0x0, 0x0, 0x0, 0x0 // Length bytes + val emptyMessageBuffer = + ByteBuffer.wrap( + byteArrayOf( + 0x0, // Compression flag + 0x0, + 0x0, + 0x0, + 0x0 // Length bytes + ) ) - ) val secondMessage = byteArrayOf(0x6, 0x7, 0x8, 0x9, 0x0, 0x1) - val secondMessageBuffer = ByteBuffer.wrap( - byteArrayOf( - 0x0, // Compression flag - 0x0, 0x0, 0x0, 0x6 // Length bytes - ) + secondMessage - ) + val secondMessageBuffer = + ByteBuffer.wrap( + byteArrayOf( + 0x0, // Compression flag + 0x0, + 0x0, + 0x0, + 0x6 // Length bytes + ) + secondMessage + ) GRPCClient(streamClient) .newGRPCStreamPrototype() diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/HeadersBuilderTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/HeadersBuilderTest.kt index f67a90dfc362..0cbdcc09d1be 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/HeadersBuilderTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/HeadersBuilderTest.kt @@ -6,92 +6,89 @@ import org.junit.Test class HeadersBuilderTest { @Test fun `adding new header adds to list of header keys`() { - val headers = RequestHeadersBuilder(mutableMapOf()) - .add("x-foo", "1") - .add("x-foo", "2") - .build() + val headers = RequestHeadersBuilder(mutableMapOf()).add("x-foo", "1").add("x-foo", "2").build() assertThat(headers.value("x-foo")).containsExactly("1", "2") } @Test fun `adding header performs a case-insensitive header name lookup`() { - val headers = RequestHeadersBuilder(mutableMapOf()) - .add("fOo", "abc") - .add("foo", "123") - .build() + val headers = RequestHeadersBuilder(mutableMapOf()).add("fOo", "abc").add("foo", "123").build() assertThat(headers.caseSensitiveHeaders()).isEqualTo(mapOf("fOo" to listOf("abc", "123"))) } @Test fun `removing specific header key removes all of its values`() { - val headers = RequestHeadersBuilder(mutableMapOf()) - .add("x-foo", "1") - .add("x-foo", "2") - .remove("x-foo") - .build() + val headers = + RequestHeadersBuilder(mutableMapOf()) + .add("x-foo", "1") + .add("x-foo", "2") + .remove("x-foo") + .build() assertThat(headers.caseSensitiveHeaders()).doesNotContainKey("x-foo") } @Test fun `removing specific header key does not remove other keys`() { - val headers = RequestHeadersBuilder(mutableMapOf()) - .add("x-foo", "123") - .add("x-bar", "abc") - .build() + val headers = + RequestHeadersBuilder(mutableMapOf()).add("x-foo", "123").add("x-bar", "abc").build() assertThat(headers.value("x-foo")).containsExactly("123") assertThat(headers.value("x-bar")).containsExactly("abc") } @Test fun `removing specific header key performs a case-insensitive header name lookup`() { - val headers = RequestHeadersBuilder(mutableMapOf()) - .set("foo", mutableListOf("123")) - .remove("fOo") - .build() + val headers = + RequestHeadersBuilder(mutableMapOf()).set("foo", mutableListOf("123")).remove("fOo").build() assertThat(headers.caseSensitiveHeaders()).isEmpty() } @Test fun `setting header replaces existing headers with matching name`() { - val headers = RequestHeadersBuilder(mutableMapOf()) - .add("x-foo", "123") - .set("x-foo", mutableListOf("abc")) - .build() + val headers = + RequestHeadersBuilder(mutableMapOf()) + .add("x-foo", "123") + .set("x-foo", mutableListOf("abc")) + .build() assertThat(headers.value("x-foo")).containsExactly("abc") } @Test fun `setting header replaces performs a case-insensitive header name lookup`() { - val headers = RequestHeadersBuilder(mapOf()) - .set("foo", mutableListOf("123")) - .set("fOo", mutableListOf("abc")) - .build() + val headers = + RequestHeadersBuilder(mapOf()) + .set("foo", mutableListOf("123")) + .set("fOo", mutableListOf("abc")) + .build() assertThat(headers.caseSensitiveHeaders()).isEqualTo(mapOf("fOo" to listOf("abc"))) } @Test fun `test initialization is case-insensitive, preserves casing and processes headers in alphabetical order`() { - val headers = RequestHeadersBuilder(mutableMapOf("a" to mutableListOf("456"), "A" to mutableListOf("123"))).build() + val headers = + RequestHeadersBuilder(mutableMapOf("a" to mutableListOf("456"), "A" to mutableListOf("123"))) + .build() assertThat(headers.caseSensitiveHeaders()).isEqualTo(mapOf("A" to listOf("123", "456"))) } @Test fun `test restricted headers are not settable`() { - val headers = RequestHeadersBuilder(method = RequestMethod.GET, authority = "example.com", path = "/") - .add("host", "example.com") - .add("Host", "example.com") - .add("hostWithSuffix", "foo.bar") - .set(":scheme", mutableListOf("http")) - .set(":path", mutableListOf("/nope")) - .build() - .caseSensitiveHeaders() - val expected = mapOf( - ":authority" to listOf("example.com"), - ":path" to listOf("/"), - ":method" to listOf("GET"), - ":scheme" to listOf("https"), - "hostWithSuffix" to listOf("foo.bar"), - ) + val headers = + RequestHeadersBuilder(method = RequestMethod.GET, authority = "example.com", path = "/") + .add("host", "example.com") + .add("Host", "example.com") + .add("hostWithSuffix", "foo.bar") + .set(":scheme", mutableListOf("http")) + .set(":path", mutableListOf("/nope")) + .build() + .caseSensitiveHeaders() + val expected = + mapOf( + ":authority" to listOf("example.com"), + ":path" to listOf("/"), + ":method" to listOf("GET"), + ":scheme" to listOf("https"), + "hostWithSuffix" to listOf("foo.bar"), + ) assertThat(headers).isEqualTo(expected) } } diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/HeadersContainerTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/HeadersContainerTest.kt index 483c4591810a..e0740e0d20ed 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/HeadersContainerTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/HeadersContainerTest.kt @@ -13,13 +13,17 @@ class HeadersContainerest { @Test fun `instantiation with mutable list of values is case-insensitive, preserves casing and processes in alphabetical order`() { - val container = HeadersContainer(mapOf("a" to mutableListOf("456"), "A" to mutableListOf("123"))) + val container = + HeadersContainer( + mapOf("a" to mutableListOf("456"), "A" to mutableListOf("123")) + ) assertThat(container.caseSensitiveHeaders()).isEqualTo(mapOf("A" to listOf("123", "456"))) } @Test fun `creation with immutable list of values is case-insensitive, preserves casing and processes in alphabetical order`() { - val container = HeadersContainer.create(mapOf("a" to listOf("456"), "A" to listOf("123"))) + val container = + HeadersContainer.create(mapOf("a" to listOf("456"), "A" to listOf("123"))) assertThat(container.caseSensitiveHeaders()).isEqualTo(mapOf("A" to listOf("123", "456"))) } @@ -46,7 +50,7 @@ class HeadersContainerest { val container = HeadersContainer(mapOf()) container.set("x-foo", mutableListOf("abc")) - assertThat( container.value("x-foo")).isEqualTo(listOf("abc")) + assertThat(container.value("x-foo")).isEqualTo(listOf("abc")) } @Test diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/PulseClientImplTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/PulseClientImplTest.kt index 2f2960480092..99a2403cbe64 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/PulseClientImplTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/PulseClientImplTest.kt @@ -14,11 +14,9 @@ import org.mockito.MockitoAnnotations class PulseClientImplTest { private var envoyEngine: EnvoyEngine = mock(EnvoyEngine::class.java) - @Captor - private lateinit var elementsCaptor: ArgumentCaptor + @Captor private lateinit var elementsCaptor: ArgumentCaptor - @Captor - private lateinit var tagsCaptor: ArgumentCaptor> + @Captor private lateinit var tagsCaptor: ArgumentCaptor> @Before fun setup() { @@ -31,9 +29,8 @@ class PulseClientImplTest { val counter = pulseClient.counter(Element("test"), Element("stat")) counter.increment() val countCaptor = ArgumentCaptor.forClass(Int::class.java) - verify(envoyEngine).recordCounterInc( - elementsCaptor.capture(), tagsCaptor.capture(), countCaptor.capture() - ) + verify(envoyEngine) + .recordCounterInc(elementsCaptor.capture(), tagsCaptor.capture(), countCaptor.capture()) assertThat(elementsCaptor.getValue()).isEqualTo("test.stat") assertThat(countCaptor.getValue()).isEqualTo(1) assertThat(tagsCaptor.getValue().size).isEqualTo(0) @@ -42,15 +39,16 @@ class PulseClientImplTest { @Test fun `counter delegates to engine with tags and count`() { val pulseClient = PulseClientImpl(envoyEngine) - val counter = pulseClient.counter( - Element("test"), Element("stat"), - tags = TagsBuilder().add("testKey1", "testValue1").add("testKey2", "testValue2").build() - ) + val counter = + pulseClient.counter( + Element("test"), + Element("stat"), + tags = TagsBuilder().add("testKey1", "testValue1").add("testKey2", "testValue2").build() + ) counter.increment(5) val countCaptor = ArgumentCaptor.forClass(Int::class.java) - verify(envoyEngine).recordCounterInc( - elementsCaptor.capture(), tagsCaptor.capture(), countCaptor.capture() - ) + verify(envoyEngine) + .recordCounterInc(elementsCaptor.capture(), tagsCaptor.capture(), countCaptor.capture()) assertThat(elementsCaptor.getValue()).isEqualTo("test.stat") assertThat(countCaptor.getValue()).isEqualTo(5) diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt index cc71d0c22ff2..fbf33a0176dd 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt @@ -6,11 +6,14 @@ import org.junit.Test class RequestHeadersBuilderTest { @Test fun `adds method to headers`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .build() assertThat(headers.value(":method")).containsExactly("POST") assertThat(headers.method).isEqualTo(RequestMethod.POST) @@ -18,11 +21,14 @@ class RequestHeadersBuilderTest { @Test fun `adds scheme to headers`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .build() assertThat(headers.value(":scheme")).containsExactly("https") assertThat(headers.scheme).isEqualTo("https") @@ -30,11 +36,14 @@ class RequestHeadersBuilderTest { @Test fun `adds authority to headers`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .build() assertThat(headers.value(":authority")).containsExactly("envoyproxy.io") assertThat(headers.authority).isEqualTo("envoyproxy.io") @@ -42,11 +51,14 @@ class RequestHeadersBuilderTest { @Test fun `adds path to headers`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .build() assertThat(headers.value(":path")).containsExactly("/mock") assertThat(headers.path).isEqualTo("/mock") @@ -54,28 +66,34 @@ class RequestHeadersBuilderTest { @Test fun `joins header values with the same key`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .add("x-foo", "1") - .add("x-foo", "2") - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .add("x-foo", "1") + .add("x-foo", "2") + .build() assertThat(headers.value("x-foo")).containsExactly("1", "2") } @Test fun `cannot publicly add headers with restricted prefix`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .add(":x-foo", "123") - .add("x-envoy-mobile-foo", "abc") - .add("host", "example.com") - .add("hostWithSuffix", "foo.bar") - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .add(":x-foo", "123") + .add("x-envoy-mobile-foo", "abc") + .add("host", "example.com") + .add("hostWithSuffix", "foo.bar") + .build() assertThat(headers.caseSensitiveHeaders()).doesNotContainKey(":x-foo") assertThat(headers.caseSensitiveHeaders()).doesNotContainKey("x-envoy-mobile-foo") @@ -85,13 +103,16 @@ class RequestHeadersBuilderTest { @Test fun `cannot publicly set headers with restricted prefix`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .set(":x-foo", mutableListOf("123")) - .set("x-envoy-mobile-foo", mutableListOf("abc")) - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .set(":x-foo", mutableListOf("123")) + .set("x-envoy-mobile-foo", mutableListOf("abc")) + .build() assertThat(headers.caseSensitiveHeaders()).doesNotContainKey(":x-foo") assertThat(headers.caseSensitiveHeaders()).doesNotContainKey("x-envoy-mobile-foo") @@ -99,25 +120,31 @@ class RequestHeadersBuilderTest { @Test fun `cannot publicly remove headers with restricted prefix`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .remove(":path") - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .remove(":path") + .build() assertThat(headers.value(":path")).contains("/mock") } @Test fun `can internally set headers with restricted prefix`() { - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .internalSet(":x-foo", mutableListOf("123")) - .internalSet("x-envoy-mobile-foo", mutableListOf("abc")) - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .internalSet(":x-foo", mutableListOf("123")) + .internalSet("x-envoy-mobile-foo", mutableListOf("abc")) + .build() assertThat(headers.value(":x-foo")).containsExactly("123") assertThat(headers.value("x-envoy-mobile-foo")).containsExactly("abc") @@ -125,49 +152,60 @@ class RequestHeadersBuilderTest { @Test fun `includes retry policy headers`() { - val retryPolicy = RetryPolicy( - maxRetryCount = 123, - retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR), - perRetryTimeoutMS = 9001 - ) + val retryPolicy = + RetryPolicy( + maxRetryCount = 123, + retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR), + perRetryTimeoutMS = 9001 + ) val retryPolicyHeaders = retryPolicy.outboundHeaders() - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .addRetryPolicy(retryPolicy) - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .addRetryPolicy(retryPolicy) + .build() assertThat(headers.caseSensitiveHeaders()).containsAllEntriesOf(retryPolicyHeaders) } @Test fun `retry policy takes precedence over manually set retry headers`() { - val retryPolicy = RetryPolicy( - maxRetryCount = 123, - retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR), - perRetryTimeoutMS = 9001 - ) - - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .add("x-envoy-max-retries", "override") - .addRetryPolicy(retryPolicy) - .build() + val retryPolicy = + RetryPolicy( + maxRetryCount = 123, + retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR), + perRetryTimeoutMS = 9001 + ) + + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .add("x-envoy-max-retries", "override") + .addRetryPolicy(retryPolicy) + .build() assertThat(headers.value("x-envoy-max-retries")).containsExactly("123") } @Test fun `converting to request headers and back maintains equality`() { - val headers1 = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .build() + val headers1 = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .build() val headers2 = headers1.toRequestHeadersBuilder().build() assertThat(headers1.caseSensitiveHeaders()).isEqualTo(headers2.caseSensitiveHeaders()) @@ -175,28 +213,32 @@ class RequestHeadersBuilderTest { @Test fun `converting retry policy to headers and back creates the same retry policy`() { - val retryPolicy = RetryPolicy( - maxRetryCount = 123, - retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR), - perRetryTimeoutMS = 9001 - ) - - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .addRetryPolicy(retryPolicy) - .build() - - assertThat(retryPolicy.outboundHeaders()).isEqualTo(RetryPolicy.from(headers)!!.outboundHeaders()) + val retryPolicy = + RetryPolicy( + maxRetryCount = 123, + retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR), + perRetryTimeoutMS = 9001 + ) + + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .addRetryPolicy(retryPolicy) + .build() + + assertThat(retryPolicy.outboundHeaders()) + .isEqualTo(RetryPolicy.from(headers)!!.outboundHeaders()) } @Test fun `converting request method to string and back creates the same request method`() { assertThat(RequestMethod.enumValue(RequestMethod.DELETE.stringValue)) .isEqualTo(RequestMethod.DELETE) - assertThat(RequestMethod.enumValue(RequestMethod.GET.stringValue)) - .isEqualTo(RequestMethod.GET) + assertThat(RequestMethod.enumValue(RequestMethod.GET.stringValue)).isEqualTo(RequestMethod.GET) assertThat(RequestMethod.enumValue(RequestMethod.HEAD.stringValue)) .isEqualTo(RequestMethod.HEAD) assertThat(RequestMethod.enumValue(RequestMethod.OPTIONS.stringValue)) @@ -205,8 +247,7 @@ class RequestHeadersBuilderTest { .isEqualTo(RequestMethod.PATCH) assertThat(RequestMethod.enumValue(RequestMethod.POST.stringValue)) .isEqualTo(RequestMethod.POST) - assertThat(RequestMethod.enumValue(RequestMethod.PUT.stringValue)) - .isEqualTo(RequestMethod.PUT) + assertThat(RequestMethod.enumValue(RequestMethod.PUT.stringValue)).isEqualTo(RequestMethod.PUT) assertThat(RequestMethod.enumValue(RequestMethod.TRACE.stringValue)) .isEqualTo(RequestMethod.TRACE) } diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/ResponseHeadersTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/ResponseHeadersTest.kt index 1aaf2e7bc7ff..261f9d8cc08e 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/ResponseHeadersTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/ResponseHeadersTest.kt @@ -30,17 +30,13 @@ class ResponseHeadersTest { @Test fun `adding HTTP status code sets the appropriate header`() { - val headers = ResponseHeadersBuilder() - .addHttpStatus(200) - .build() + val headers = ResponseHeadersBuilder().addHttpStatus(200).build() assertThat(headers.value(":status")).containsExactly("200") } @Test fun `adding negative HTTP status code no-ops`() { - val headers = ResponseHeadersBuilder() - .addHttpStatus(-123) - .build() + val headers = ResponseHeadersBuilder().addHttpStatus(-123).build() assertThat(headers.value(":status")).isNull() } diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/RetryPolicyMapperTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/RetryPolicyMapperTest.kt index 5f41594724ab..52989f9df44c 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/RetryPolicyMapperTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/RetryPolicyMapperTest.kt @@ -6,59 +6,70 @@ import org.junit.Test class RetryPolicyMapperTest { @Test fun `converting to headers with per retry timeout includes all headers`() { - val retryPolicy = RetryPolicy( - maxRetryCount = 3, - retryOn = listOf( - RetryRule.STATUS_5XX, - RetryRule.GATEWAY_ERROR, - RetryRule.CONNECT_FAILURE, - RetryRule.REFUSED_STREAM, - RetryRule.RETRIABLE_4XX, - RetryRule.RETRIABLE_HEADERS, - RetryRule.RESET - ), - retryStatusCodes = listOf(400, 422, 500), - perRetryTimeoutMS = 15000, - totalUpstreamTimeoutMS = 60000 - ) + val retryPolicy = + RetryPolicy( + maxRetryCount = 3, + retryOn = + listOf( + RetryRule.STATUS_5XX, + RetryRule.GATEWAY_ERROR, + RetryRule.CONNECT_FAILURE, + RetryRule.REFUSED_STREAM, + RetryRule.RETRIABLE_4XX, + RetryRule.RETRIABLE_HEADERS, + RetryRule.RESET + ), + retryStatusCodes = listOf(400, 422, 500), + perRetryTimeoutMS = 15000, + totalUpstreamTimeoutMS = 60000 + ) - assertThat(retryPolicy.outboundHeaders()).isEqualTo( - mapOf( - "x-envoy-max-retries" to listOf("3"), - "x-envoy-retriable-status-codes" to listOf("400", "422", "500"), - "x-envoy-retry-on" to listOf( - "5xx", "gateway-error", "connect-failure", "refused-stream", "retriable-4xx", - "retriable-headers", "reset", "retriable-status-codes" - ), - "x-envoy-upstream-rq-per-try-timeout-ms" to listOf("15000"), - "x-envoy-upstream-rq-timeout-ms" to listOf("60000") + assertThat(retryPolicy.outboundHeaders()) + .isEqualTo( + mapOf( + "x-envoy-max-retries" to listOf("3"), + "x-envoy-retriable-status-codes" to listOf("400", "422", "500"), + "x-envoy-retry-on" to + listOf( + "5xx", + "gateway-error", + "connect-failure", + "refused-stream", + "retriable-4xx", + "retriable-headers", + "reset", + "retriable-status-codes" + ), + "x-envoy-upstream-rq-per-try-timeout-ms" to listOf("15000"), + "x-envoy-upstream-rq-timeout-ms" to listOf("60000") + ) ) - ) } @Test fun `converting from header values delimited with comma yields individual enum values`() { - val retryPolicy = RetryPolicy( - maxRetryCount = 3, - retryOn = listOf( - RetryRule.STATUS_5XX, - RetryRule.GATEWAY_ERROR - ), - retryStatusCodes = listOf(400, 422, 500), - perRetryTimeoutMS = 15000, - totalUpstreamTimeoutMS = 60000 - ) + val retryPolicy = + RetryPolicy( + maxRetryCount = 3, + retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR), + retryStatusCodes = listOf(400, 422, 500), + perRetryTimeoutMS = 15000, + totalUpstreamTimeoutMS = 60000 + ) - val headers = RequestHeadersBuilder( - method = RequestMethod.POST, scheme = "https", - authority = "envoyproxy.io", path = "/mock" - ) - .add("x-envoy-max-retries", "3") - .add("x-envoy-retriable-status-codes", "400,422,500") - .add("x-envoy-retry-on", "5xx,gateway-error") - .add("x-envoy-upstream-rq-per-try-timeout-ms", "15000") - .add("x-envoy-upstream-rq-timeout-ms", "60000") - .build() + val headers = + RequestHeadersBuilder( + method = RequestMethod.POST, + scheme = "https", + authority = "envoyproxy.io", + path = "/mock" + ) + .add("x-envoy-max-retries", "3") + .add("x-envoy-retriable-status-codes", "400,422,500") + .add("x-envoy-retry-on", "5xx,gateway-error") + .add("x-envoy-upstream-rq-per-try-timeout-ms", "15000") + .add("x-envoy-upstream-rq-timeout-ms", "60000") + .build() val retryPolicyFromHeaders = RetryPolicy.from(headers)!! @@ -67,10 +78,11 @@ class RetryPolicyMapperTest { @Test fun `converting to headers without retry timeout excludes per retry timeout header`() { - val retryPolicy = RetryPolicy( - maxRetryCount = 123, - retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR) - ) + val retryPolicy = + RetryPolicy( + maxRetryCount = 123, + retryOn = listOf(RetryRule.STATUS_5XX, RetryRule.GATEWAY_ERROR) + ) assertThat(retryPolicy.outboundHeaders()) .doesNotContainKey("x-envoy-upstream-rq-per-try-timeout-ms") @@ -78,19 +90,21 @@ class RetryPolicyMapperTest { @Test fun `converting to headers without upstream timeout includes zero for timeout header`() { - val retryPolicy = RetryPolicy( - maxRetryCount = 123, - retryOn = listOf(RetryRule.STATUS_5XX), - totalUpstreamTimeoutMS = null - ) + val retryPolicy = + RetryPolicy( + maxRetryCount = 123, + retryOn = listOf(RetryRule.STATUS_5XX), + totalUpstreamTimeoutMS = null + ) - assertThat(retryPolicy.outboundHeaders()).isEqualTo( - mapOf( - "x-envoy-max-retries" to listOf("123"), - "x-envoy-retry-on" to listOf("5xx"), - "x-envoy-upstream-rq-timeout-ms" to listOf("0") + assertThat(retryPolicy.outboundHeaders()) + .isEqualTo( + mapOf( + "x-envoy-max-retries" to listOf("123"), + "x-envoy-retry-on" to listOf("5xx"), + "x-envoy-upstream-rq-timeout-ms" to listOf("0") + ) ) - ) } @Test(expected = IllegalArgumentException::class) @@ -105,20 +119,22 @@ class RetryPolicyMapperTest { @Test fun `converting headers without retry status code does not set retriable status code headers`() { - val retryPolicy = RetryPolicy( - maxRetryCount = 123, - retryOn = listOf( - RetryRule.STATUS_5XX, - RetryRule.GATEWAY_ERROR, - RetryRule.CONNECT_FAILURE, - RetryRule.REFUSED_STREAM, - RetryRule.RETRIABLE_4XX, - RetryRule.RETRIABLE_HEADERS, - RetryRule.RESET - ), - retryStatusCodes = emptyList(), - totalUpstreamTimeoutMS = null - ) + val retryPolicy = + RetryPolicy( + maxRetryCount = 123, + retryOn = + listOf( + RetryRule.STATUS_5XX, + RetryRule.GATEWAY_ERROR, + RetryRule.CONNECT_FAILURE, + RetryRule.REFUSED_STREAM, + RetryRule.RETRIABLE_4XX, + RetryRule.RETRIABLE_HEADERS, + RetryRule.RESET + ), + retryStatusCodes = emptyList(), + totalUpstreamTimeoutMS = null + ) val headers = retryPolicy.outboundHeaders() assertThat(headers["x-envoy-retriable-status-codes"]).isNull() 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 c055f5a6217f..5511b3488878 100644 --- a/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc +++ b/mobile/test/non_hermetic/gcp_traffic_director_integration_test.cc @@ -37,10 +37,10 @@ using ::Envoy::Grpc::SotwOrDelta; using ::Envoy::Network::Address::IpVersion; // The One-Platform API endpoint for Traffic Director. -constexpr char TD_API_ENDPOINT[] = "staging-trafficdirectorconsumermesh.sandbox.googleapis.com"; +constexpr char TD_API_ENDPOINT[] = "trafficdirectorconsumermesh.googleapis.com"; // The project number of the project, found on the main page of the project in // Google Cloud Console. -constexpr char PROJECT_ID[] = "947171374466"; +constexpr char PROJECT_ID[] = "33303528656"; // 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. @@ -65,9 +65,10 @@ class GcpTrafficDirectorIntegrationTest 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."); + // API key for the `bct-prod-td-consumer-mesh` GCP test project. + const char* api_key = std::getenv("GCP_TEST_PROJECT_PROD_API_KEY"); + RELEASE_ASSERT(api_key != nullptr, + "GCP_TEST_PROJECT_PROD_API_KEY environment variable not set."); Platform::XdsBuilder xds_builder(/*xds_server_address=*/std::string(TD_API_ENDPOINT), /*xds_server_port=*/443); diff --git a/mobile/tools/check_format.sh b/mobile/tools/check_format.sh index 0a350b8f5e63..a0e7d4768a63 100755 --- a/mobile/tools/check_format.sh +++ b/mobile/tools/check_format.sh @@ -30,7 +30,7 @@ FORMAT_ARGS=( ./Envoy.xcodeproj/ ./dist/ ./bazel/envoy_mobile_swift_bazel_support.bzl ./bazel/envoy_mobile_repositories.bzl - ./examples/swift/swiftpm/Packages/Envoy.xcframework + ./examples/swift/swiftpm/Packages/Envoy.xcframework ./tmp --skip_envoy_build_rule_check) if [[ -n "$TARGET_PATH" ]]; then FORMAT_ARGS+=("$TARGET_PATH") @@ -47,3 +47,20 @@ FORMAT_ARGS+=( ./test/swift ./experimental/swift) export ENVOY_BAZEL_PREFIX="@envoy" && ./bazelw run @envoy//tools/code_format:check_format -- "${ENVOY_FORMAT_ACTION}" --path "$PWD" "${FORMAT_ARGS[@]}" + +KTFMT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"/ktfmt.sh +KOTLIN_DIRS=( + "library/kotlin" + "test/kotlin" + "examples/kotlin" +) +if [[ "${ENVOY_FORMAT_ACTION}" == "fix" ]]; then + "${KTFMT}" "${KOTLIN_DIRS[@]}" +else + NEEDS_FORMAT=$("${KTFMT}" --dry-run "${KOTLIN_DIRS[@]}") + if [[ -n "${NEEDS_FORMAT}" ]]; then + echo "ERROR: Run 'tools/check_format.sh fix' to fix" + echo "${NEEDS_FORMAT}" + exit 1 + fi +fi diff --git a/mobile/tools/ktfmt.sh b/mobile/tools/ktfmt.sh new file mode 100755 index 000000000000..59ce244ab79c --- /dev/null +++ b/mobile/tools/ktfmt.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +set -euo pipefail + +ktfmt_version="0.46" +readonly ktfmt_version + +ktfmt_url="https://repo1.maven.org/maven2/com/facebook/ktfmt/${ktfmt_version}/ktfmt-${ktfmt_version}-jar-with-dependencies.jar" +readonly ktfmt_url + +ktfmt_sha256="97fc7fbd194d01a9fa45d8147c0552403003d55bac4ab89d84d7bb4d5e3f48de" +readonly ktfmt_sha256 + +jdk_url="https://cdn.azul.com/zulu/bin/zulu11.1+23-ea-jdk11-linux_x64.tar.gz" +readonly jdk_url + +jdk_sha256="7cd09d542fa5623df5a59447304c3a41c0b682d3ca26b5e9d99e5214cf21fdd7" +readonly jdk_sha256 + +script_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +readonly script_root + +ktfmt_jar="$script_root/tmp/ktfmt/versions/ktfmt-0.46.jar" +readonly ktfmt_jar + +jdk="$script_root/tmp/jdk/versions/jdk11" +readonly jdk + +java="${jdk}"/bin/java +readonly java + +check_sha256sum() { + sha256="$1" + binary="$2" + sha_check=$(echo "${sha256}" "${binary}" | sha256sum --quiet --check || true) + echo "${sha_check}" + if [[ -n "${sha_check}" ]]; then + echo "Deleting ${binary}" >&2 + rm -f "$binary" + exit 1 + fi +} + +download_jdk() { + mkdir -p "${jdk}" + + download_temp_dir=$(mktemp -d) + jdk_tar_gz="${download_temp_dir}/jdk.tar.gz" + curl --fail -L --retry 5 --retry-connrefused --silent --progress-bar \ + --output "${jdk_tar_gz}" "$jdk_url" + + check_sha256sum "${jdk_sha256}" "${jdk_tar_gz}" + + tar -C "${jdk}" -xf "${jdk_tar_gz}" --strip-components=1 +} + +download_ktfmt() { + mkdir -p "$(dirname "${ktfmt_jar}")" + + curl --fail -L --retry 5 --retry-connrefused --silent --progress-bar \ + --output "$ktfmt_jar" "$ktfmt_url" + + check_sha256sum "${ktfmt_sha256}" "${ktfmt_jar}" +} + +# TODO(fredyw): Use CI's JDK when available. +if [[ ! -f "${java}" ]]; then + download_jdk +fi + +if [[ ! -f "${ktfmt_jar}" ]]; then + download_ktfmt +fi + +"${java}" -jar "${ktfmt_jar}" --google-style "$@" diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 2f16cfe09f0b..57bcfc33acb0 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1226,7 +1226,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt // Rewrites the host of CONNECT-UDP requests. if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.enable_connect_udp_support") && - HeaderUtility::isConnectUdp(*request_headers_) && + HeaderUtility::isConnectUdpRequest(*request_headers_) && !HeaderUtility::rewriteAuthorityForConnectUdp(*request_headers_)) { sendLocalReply(Code::NotFound, "The path is incorrect for CONNECT-UDP", nullptr, absl::nullopt, StreamInfo::ResponseCodeDetails::get().InvalidPath); diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index 398cdab44ac9..cbc01ec7df7c 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -239,11 +239,19 @@ bool HeaderUtility::isConnect(const RequestHeaderMap& headers) { return headers.Method() && headers.Method()->value() == Http::Headers::get().MethodValues.Connect; } -bool HeaderUtility::isConnectUdp(const RequestHeaderMap& headers) { +bool HeaderUtility::isConnectUdpRequest(const RequestHeaderMap& headers) { return headers.Upgrade() && absl::EqualsIgnoreCase(headers.getUpgradeValue(), Http::Headers::get().UpgradeValues.ConnectUdp); } +bool HeaderUtility::isConnectUdpResponse(const ResponseHeaderMap& headers) { + // In connect-udp case, Envoy will transform the H2 headers to H1 upgrade headers. + // A valid response should have SwitchingProtocol status and connect-udp upgrade. + return headers.Upgrade() && Utility::getResponseStatus(headers) == 101 && + absl::EqualsIgnoreCase(headers.getUpgradeValue(), + Http::Headers::get().UpgradeValues.ConnectUdp); +} + bool HeaderUtility::isConnectResponse(const RequestHeaderMap* request_headers, const ResponseHeaderMap& response_headers) { return request_headers && isConnect(*request_headers) && diff --git a/source/common/http/header_utility.h b/source/common/http/header_utility.h index fb0e863a69b3..58c765a8a27a 100644 --- a/source/common/http/header_utility.h +++ b/source/common/http/header_utility.h @@ -167,7 +167,12 @@ class HeaderUtility { /** * @brief a helper function to determine if the headers represent a CONNECT-UDP request. */ - static bool isConnectUdp(const RequestHeaderMap& headers); + static bool isConnectUdpRequest(const RequestHeaderMap& headers); + + /** + * @brief a helper function to determine if the headers represent a CONNECT-UDP response. + */ + static bool isConnectUdpResponse(const ResponseHeaderMap& headers); /** * @brief a helper function to determine if the headers represent an accepted CONNECT response. diff --git a/source/common/protobuf/BUILD b/source/common/protobuf/BUILD index ba96f6cb5c95..3353892dbec3 100644 --- a/source/common/protobuf/BUILD +++ b/source/common/protobuf/BUILD @@ -105,8 +105,8 @@ envoy_cc_library( 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//bazel/cc_proto_descriptor_library:create_dynamic_message", + "@envoy_api//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", diff --git a/source/common/quic/envoy_quic_client_session.cc b/source/common/quic/envoy_quic_client_session.cc index 152e2a484b93..45e6f620de00 100644 --- a/source/common/quic/envoy_quic_client_session.cc +++ b/source/common/quic/envoy_quic_client_session.cc @@ -212,7 +212,7 @@ void EnvoyQuicClientSession::setHttp3Options( } static_cast(connection()) ->setNumPtosForPortMigration(PROTOBUF_GET_WRAPPED_OR_DEFAULT( - http3_options.quic_protocol_options(), num_timeouts_to_trigger_port_migration, 1)); + http3_options.quic_protocol_options(), num_timeouts_to_trigger_port_migration, 4)); if (http3_options_->quic_protocol_options().has_connection_keepalive()) { const uint64_t initial_interval = PROTOBUF_GET_MS_OR_DEFAULT( diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index d90c326b2da3..4a131edc6538 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -91,9 +91,9 @@ Http::Status EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& #ifdef ENVOY_ENABLE_HTTP_DATAGRAMS if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.enable_connect_udp_support") && (Http::HeaderUtility::isCapsuleProtocol(headers) || - Http::HeaderUtility::isConnectUdp(headers))) { + Http::HeaderUtility::isConnectUdpRequest(headers))) { useCapsuleProtocol(); - if (Http::HeaderUtility::isConnectUdp(headers)) { + if (Http::HeaderUtility::isConnectUdpRequest(headers)) { // HTTP/3 Datagrams sent over CONNECT-UDP are already congestion controlled, so make it // bypass the default Datagram queue. session()->SetForceFlushForDefaultQueue(true); diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index 47acf20f16d1..a89cef722fab 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -270,11 +270,11 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, #ifdef ENVOY_ENABLE_HTTP_DATAGRAMS if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.enable_connect_udp_support") && (Http::HeaderUtility::isCapsuleProtocol(*headers) || - Http::HeaderUtility::isConnectUdp(*headers))) { + Http::HeaderUtility::isConnectUdpRequest(*headers))) { useCapsuleProtocol(); // HTTP/3 Datagrams sent over CONNECT-UDP are already congestion controlled, so make it bypass // the default Datagram queue. - if (Http::HeaderUtility::isConnectUdp(*headers)) { + if (Http::HeaderUtility::isConnectUdpRequest(*headers)) { session()->SetForceFlushForDefaultQueue(true); } } diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index b7bc5fd2169d..59b8747de748 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -1598,7 +1598,7 @@ RouteConstSharedPtr ConnectRouteEntryImpl::matches(const Http::RequestHeaderMap& uint64_t random_value) const { if ((Http::HeaderUtility::isConnect(headers) || (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.enable_connect_udp_support") && - Http::HeaderUtility::isConnectUdp(headers))) && + Http::HeaderUtility::isConnectUdpRequest(headers))) && RouteEntryImplBase::matchRoute(headers, stream_info, random_value)) { return clusterEntry(headers, random_value); } diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 89a688cead20..da6a4e4c0e4e 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -816,7 +816,7 @@ Filter::createConnPool(Upstream::ThreadLocalCluster& thread_local_cluster) { if (route_entry_->connectConfig().has_value()) { auto method = downstream_headers_->getMethodValue(); if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.enable_connect_udp_support") && - Http::HeaderUtility::isConnectUdp(*downstream_headers_)) { + Http::HeaderUtility::isConnectUdpRequest(*downstream_headers_)) { upstream_protocol = UpstreamProtocol::UDP; } else if (method == Http::Headers::get().MethodValues.Connect || (route_entry_->connectConfig()->allow_post() && diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index ddde1b1c795a..de2fc4c2a298 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -72,7 +72,7 @@ RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); RUNTIME_GUARD(envoy_reloadable_features_overload_manager_error_unknown_action); RUNTIME_GUARD(envoy_reloadable_features_prohibit_route_refresh_after_response_headers_sent); -RUNTIME_GUARD(envoy_reloadable_features_quic_defer_logging_to_ack_listener); +RUNTIME_GUARD(envoy_reloadable_features_proxy_status_upstream_request_timeout); RUNTIME_GUARD(envoy_reloadable_features_sanitize_original_path); RUNTIME_GUARD(envoy_reloadable_features_send_header_raw_value); RUNTIME_GUARD(envoy_reloadable_features_service_sanitize_non_utf8_strings); @@ -125,6 +125,8 @@ FALSE_RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_universal_header_validator); // TODO(wbpcode): enable by default after a complete deprecation period. FALSE_RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name); +// TODO(pksohn): enable after fixing https://github.com/envoyproxy/envoy/issues/29930 +FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_defer_logging_to_ack_listener); // 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/stream_info/BUILD b/source/common/stream_info/BUILD index 0f470829c667..9acfe214baaf 100644 --- a/source/common/stream_info/BUILD +++ b/source/common/stream_info/BUILD @@ -43,6 +43,7 @@ envoy_cc_library( "//envoy/http:codes_interface", "//envoy/stream_info:stream_info_interface", "//source/common/http:default_server_string_lib", + "//source/common/runtime:runtime_features_lib", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], ) diff --git a/source/common/stream_info/utility.cc b/source/common/stream_info/utility.cc index ab720c88eda2..1835f531db06 100644 --- a/source/common/stream_info/utility.cc +++ b/source/common/stream_info/utility.cc @@ -5,6 +5,7 @@ #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" #include "source/common/http/default_server_string.h" +#include "source/common/runtime/runtime_features.h" #include "absl/strings/str_format.h" @@ -313,7 +314,7 @@ ProxyStatusUtils::proxyStatusErrorToString(const ProxyStatusError proxy_status) case ProxyStatusError::TlsProtocolError: return TLS_PROTOCOL_ERROR; case ProxyStatusError::TlsCertificateError: - return TLS_CERTIFICATE_ERORR; + return TLS_CERTIFICATE_ERROR; case ProxyStatusError::TlsAlertReceived: return TLS_ALERT_RECEIVED; case ProxyStatusError::HttpRequestError: @@ -366,7 +367,11 @@ ProxyStatusUtils::fromStreamInfo(const StreamInfo& stream_info) { } else if (stream_info.hasResponseFlag(ResponseFlag::NoHealthyUpstream)) { return ProxyStatusError::DestinationUnavailable; } else if (stream_info.hasResponseFlag(ResponseFlag::UpstreamRequestTimeout)) { - return ProxyStatusError::ConnectionTimeout; + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.proxy_status_upstream_request_timeout")) { + return ProxyStatusError::ConnectionTimeout; + } + return ProxyStatusError::HttpResponseTimeout; } else if (stream_info.hasResponseFlag(ResponseFlag::LocalReset)) { return ProxyStatusError::ConnectionTimeout; } else if (stream_info.hasResponseFlag(ResponseFlag::UpstreamRemoteReset)) { diff --git a/source/common/stream_info/utility.h b/source/common/stream_info/utility.h index 5f8c84a1bc36..51f3e69f792b 100644 --- a/source/common/stream_info/utility.h +++ b/source/common/stream_info/utility.h @@ -249,7 +249,7 @@ class ProxyStatusUtils { constexpr static absl::string_view CONNECTION_WRITE_TIMEOUT = "connection_write_timeout"; constexpr static absl::string_view CONNECTION_LIMIT_REACHED = "connection_limit_reached"; constexpr static absl::string_view TLS_PROTOCOL_ERROR = "tls_protocol_error"; - constexpr static absl::string_view TLS_CERTIFICATE_ERORR = "tls_certificate_error"; + constexpr static absl::string_view TLS_CERTIFICATE_ERROR = "tls_certificate_error"; constexpr static absl::string_view TLS_ALERT_RECEIVED = "tls_alert_received"; constexpr static absl::string_view HTTP_REQUEST_ERROR = "http_request_error"; constexpr static absl::string_view HTTP_REQUEST_DENIED = "http_request_denied"; diff --git a/source/extensions/filters/http/oauth2/config.cc b/source/extensions/filters/http/oauth2/config.cc index e3b3a3ebf3ee..4a228db410ad 100644 --- a/source/extensions/filters/http/oauth2/config.cc +++ b/source/extensions/filters/http/oauth2/config.cc @@ -73,7 +73,7 @@ Http::FilterFactoryCb OAuth2Config::createFilterFactoryFromProtoTyped( [&context, config, &cluster_manager](Http::FilterChainFactoryCallbacks& callbacks) -> void { std::unique_ptr oauth_client = std::make_unique(cluster_manager, config->oauthTokenEndpoint()); - callbacks.addStreamDecoderFilter( + callbacks.addStreamFilter( std::make_shared(config, std::move(oauth_client), context.timeSource())); }; } diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index 57984a648a36..2ddf6256a545 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -194,7 +194,8 @@ FilterConfig::FilterConfig( forward_bearer_token_(proto_config.forward_bearer_token()), pass_through_header_matchers_(headerMatchers(proto_config.pass_through_matcher())), cookie_names_(proto_config.credentials().cookie_names()), - auth_type_(getAuthType(proto_config.auth_type())) { + auth_type_(getAuthType(proto_config.auth_type())), + use_refresh_token_(proto_config.use_refresh_token().value()) { if (!cluster_manager.clusters().hasCluster(oauth_token_endpoint_.cluster())) { throw EnvoyException(fmt::format("OAuth2 filter: unknown cluster '{}' in config. Please " "specify which cluster to direct OAuth requests to.", @@ -230,6 +231,10 @@ void OAuth2CookieValidator::setParams(const Http::RequestHeaderMap& headers, secret_.assign(secret.begin(), secret.end()); } +bool OAuth2CookieValidator::canUpdateTokenByRefreshToken() const { + return (!token_.empty() && !refresh_token_.empty()); +} + bool OAuth2CookieValidator::hmacIsValid() const { return ( (encodeHmacBase64(secret_, host_, expires_, token_, id_token_, refresh_token_) == hmac_) || @@ -251,8 +256,8 @@ bool OAuth2CookieValidator::isValid() const { return hmacIsValid() && timestampI OAuth2Filter::OAuth2Filter(FilterConfigSharedPtr config, std::unique_ptr&& oauth_client, TimeSource& time_source) : validator_(std::make_shared(time_source, config->cookieNames())), - oauth_client_(std::move(oauth_client)), config_(std::move(config)), - time_source_(time_source) { + was_refresh_token_flow_(false), oauth_client_(std::move(oauth_client)), + config_(std::move(config)), time_source_(time_source) { oauth_client_->setCallbacks(*this); } @@ -351,6 +356,16 @@ Http::FilterHeadersStatus OAuth2Filter::decodeHeaders(Http::RequestHeaderMap& he // The following conditional could be replaced with a regex pattern-match, // if we're concerned about strict matching against the callback path. if (!config_->redirectPathMatcher().match(path_str)) { + + // Check if we can update the access token via a refresh token. + if (config_->useRefreshToken() && validator_->canUpdateTokenByRefreshToken()) { + // try to update access token by refresh token + oauth_client_->asyncRefreshAccessToken(validator_->refreshToken(), config_->clientId(), + config_->clientSecret(), config_->authType()); + // pause while we await the next step from the OAuth server + return Http::FilterHeadersStatus::StopAllIterationAndWatermark; + } + ENVOY_LOG(debug, "path {} does not match with redirect matcher. redirecting to OAuth server.", path_str); redirectToOAuthServer(headers); @@ -397,6 +412,15 @@ Http::FilterHeadersStatus OAuth2Filter::decodeHeaders(Http::RequestHeaderMap& he return Http::FilterHeadersStatus::StopAllIterationAndBuffer; } +Http::FilterHeadersStatus OAuth2Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { + if (was_refresh_token_flow_) { + addResponseCookies(headers, getEncodedToken()); + was_refresh_token_flow_ = false; + } + + return Http::FilterHeadersStatus::Continue; +} + // Defines a sequence of checks determining whether we should initiate a new OAuth flow or skip to // the next filter in the chain. bool OAuth2Filter::canSkipOAuth(Http::RequestHeaderMap& headers) const { @@ -518,6 +542,15 @@ void OAuth2Filter::onGetAccessTokenSuccess(const std::string& access_code, finishGetAccessTokenFlow(); } +void OAuth2Filter::onRefreshAccessTokenSuccess(const std::string& access_code, + const std::string& id_token, + const std::string& refresh_token, + std::chrono::seconds expires_in) { + ASSERT(config_->useRefreshToken()); + updateTokens(access_code, id_token, refresh_token, expires_in); + finishRefreshAccessTokenFlow(); +} + void OAuth2Filter::finishGetAccessTokenFlow() { // At this point we have all of the pieces needed to authorize a user. // Now, we construct a redirect request to return the user to their @@ -533,6 +566,48 @@ void OAuth2Filter::finishGetAccessTokenFlow() { config_->stats().oauth_success_.inc(); } +void OAuth2Filter::finishRefreshAccessTokenFlow() { + ASSERT(config_->useRefreshToken()); + // At this point we have updated all of the pieces need to authorize a user + // We need to actualize keys in the cookie header of the current request related + // with authorization. So, the upstream can use updated cookies for itself purpose + const CookieNames& cookie_names = config_->cookieNames(); + + absl::flat_hash_map cookies = + Http::Utility::parseCookies(*request_headers_); + + cookies.insert_or_assign(cookie_names.oauth_hmac_, getEncodedToken()); + cookies.insert_or_assign(cookie_names.oauth_expires_, new_expires_); + + if (config_->forwardBearerToken()) { + cookies.insert_or_assign(cookie_names.bearer_token_, access_token_); + if (!id_token_.empty()) { + cookies.insert_or_assign(cookie_names.id_token_, id_token_); + } + if (!refresh_token_.empty()) { + cookies.insert_or_assign(cookie_names.refresh_token_, refresh_token_); + } + } + + std::string new_cookies(absl::StrJoin(cookies, "; ", absl::PairFormatter("="))); + request_headers_->addReferenceKey(Http::Headers::get().Cookie, new_cookies); + if (config_->forwardBearerToken() && !access_token_.empty()) { + setBearerToken(*request_headers_, access_token_); + } + + was_refresh_token_flow_ = true; + + config_->stats().oauth_refreshtoken_success_.inc(); + config_->stats().oauth_success_.inc(); + decoder_callbacks_->continueDecoding(); +} + +void OAuth2Filter::onRefreshAccessTokenFailure() { + config_->stats().oauth_refreshtoken_failure_.inc(); + // We failed to get an access token via the refresh token, so send the user to the oauth endpoint. + redirectToOAuthServer(*request_headers_); +} + void OAuth2Filter::addResponseCookies(Http::ResponseHeaderMap& headers, const std::string& encoded_token) const { std::string max_age; @@ -565,13 +640,13 @@ void OAuth2Filter::addResponseCookies(Http::ResponseHeaderMap& headers, headers.addReferenceKey( Http::Headers::get().SetCookie, absl::StrCat(cookie_names.bearer_token_, "=", access_token_, cookie_attribute_httponly)); - if (id_token_ != EMPTY_STRING) { + if (!id_token_.empty()) { headers.addReferenceKey( Http::Headers::get().SetCookie, absl::StrCat(cookie_names.id_token_, "=", id_token_, cookie_attribute_httponly)); } - if (refresh_token_ != EMPTY_STRING) { + if (!refresh_token_.empty()) { headers.addReferenceKey(Http::Headers::get().SetCookie, absl::StrCat(cookie_names.refresh_token_, "=", refresh_token_, cookie_attribute_httponly)); diff --git a/source/extensions/filters/http/oauth2/filter.h b/source/extensions/filters/http/oauth2/filter.h index 64e4ee002f11..9e1b54f6b937 100644 --- a/source/extensions/filters/http/oauth2/filter.h +++ b/source/extensions/filters/http/oauth2/filter.h @@ -85,7 +85,9 @@ class SDSSecretReader : public SecretReader { COUNTER(oauth_unauthorized_rq) \ COUNTER(oauth_failure) \ COUNTER(oauth_passthrough) \ - COUNTER(oauth_success) + COUNTER(oauth_success) \ + COUNTER(oauth_refreshtoken_success) \ + COUNTER(oauth_refreshtoken_failure) /** * Wrapper struct filter stats. @see stats_macros.h @@ -157,6 +159,7 @@ class FilterConfig { const std::string& encodedResourceQueryParams() const { return encoded_resource_query_params_; } const CookieNames& cookieNames() const { return cookie_names_; } const AuthType& authType() const { return auth_type_; } + bool useRefreshToken() const { return use_refresh_token_; } private: static FilterStats generateStats(const std::string& prefix, Stats::Scope& scope); @@ -178,6 +181,7 @@ class FilterConfig { const std::vector pass_through_header_matchers_; const CookieNames cookie_names_; const AuthType auth_type_; + const bool use_refresh_token_{}; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -200,6 +204,7 @@ class CookieValidator { virtual const std::string& refreshToken() const PURE; virtual void setParams(const Http::RequestHeaderMap& headers, const std::string& secret) PURE; virtual bool isValid() const PURE; + virtual bool canUpdateTokenByRefreshToken() const PURE; }; class OAuth2CookieValidator : public CookieValidator { @@ -214,6 +219,7 @@ class OAuth2CookieValidator : public CookieValidator { bool isValid() const override; bool hmacIsValid() const; bool timestampIsValid() const; + bool canUpdateTokenByRefreshToken() const override; private: std::string token_; @@ -232,25 +238,36 @@ class OAuth2CookieValidator : public CookieValidator { * receive incoming requests and decide at what state of the OAuth workflow they are in. Logic * beyond that is broken into component classes. */ -class OAuth2Filter : public Http::PassThroughDecoderFilter, +class OAuth2Filter : public Http::PassThroughFilter, FilterCallbacks, Logger::Loggable { public: OAuth2Filter(FilterConfigSharedPtr config, std::unique_ptr&& oauth_client, TimeSource& time_source); - // Http::PassThroughDecoderFilter + // Http::PassThroughFilter Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override; + Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, bool) override; // FilterCallbacks void onGetAccessTokenSuccess(const std::string& access_code, const std::string& id_token, const std::string& refresh_token, std::chrono::seconds expires_in) override; + + void onRefreshAccessTokenSuccess(const std::string& access_code, const std::string& id_token, + const std::string& refresh_token, + std::chrono::seconds expires_in) override; + + void onRefreshAccessTokenFailure() override; + // a catch-all function used for request failures. we don't retry, as a user can simply refresh // the page in the case of a network blip. void sendUnauthorizedResponse() override; void finishGetAccessTokenFlow(); + void finishRefreshAccessTokenFlow(); + void updateTokens(const std::string& access_token, const std::string& id_token, + const std::string& refresh_token, std::chrono::seconds expires_in); private: friend class OAuth2Test; @@ -267,6 +284,7 @@ class OAuth2Filter : public Http::PassThroughDecoderFilter, absl::string_view host_; std::string state_; Http::RequestHeaderMap* request_headers_{nullptr}; + bool was_refresh_token_flow_; std::unique_ptr oauth_client_; FilterConfigSharedPtr config_; @@ -276,8 +294,6 @@ class OAuth2Filter : public Http::PassThroughDecoderFilter, // connection is mTLS, etc.) bool canSkipOAuth(Http::RequestHeaderMap& headers) const; void redirectToOAuthServer(Http::RequestHeaderMap& headers) const; - void updateTokens(const std::string& access_token, const std::string& id_token, - const std::string& refresh_token, std::chrono::seconds expires_in); Http::FilterHeadersStatus signOutUser(const Http::RequestHeaderMap& headers); diff --git a/source/extensions/filters/http/oauth2/oauth.h b/source/extensions/filters/http/oauth2/oauth.h index 261050ed7c1c..f7850d35fd99 100644 --- a/source/extensions/filters/http/oauth2/oauth.h +++ b/source/extensions/filters/http/oauth2/oauth.h @@ -23,6 +23,13 @@ class FilterCallbacks { const std::string& refresh_token, std::chrono::seconds expires_in) PURE; + virtual void onRefreshAccessTokenSuccess(const std::string& access_token, + const std::string& id_token, + const std::string& refresh_token, + std::chrono::seconds expires_in) PURE; + + virtual void onRefreshAccessTokenFailure() PURE; + virtual void sendUnauthorizedResponse() PURE; }; diff --git a/source/extensions/filters/http/oauth2/oauth_client.cc b/source/extensions/filters/http/oauth2/oauth_client.cc index 21fd343beb09..b7be673c761d 100644 --- a/source/extensions/filters/http/oauth2/oauth_client.cc +++ b/source/extensions/filters/http/oauth2/oauth_client.cc @@ -22,12 +22,18 @@ namespace HttpFilters { namespace Oauth2 { namespace { -constexpr const char* UrlBodyTemplateWithCredentials = +constexpr const char* UrlBodyTemplateWithCredentialsForAuthCode = "grant_type=authorization_code&code={0}&client_id={1}&client_secret={2}&redirect_uri={3}"; -constexpr const char* UrlBodyTemplateWithoutCredentials = +constexpr const char* UrlBodyTemplateWithoutCredentialsForAuthCode = "grant_type=authorization_code&code={0}&redirect_uri={1}"; +constexpr const char* UrlBodyTemplateWithCredentialsForRefreshToken = + "grant_type=refresh_token&refresh_token={0}&client_id={1}&client_secret={2}"; + +constexpr const char* UrlBodyTemplateWithoutCredentialsForRefreshToken = + "grant_type=refresh_token&refresh_token={0}"; + } // namespace void OAuth2ClientImpl::asyncGetAccessToken(const std::string& auth_code, @@ -39,7 +45,7 @@ void OAuth2ClientImpl::asyncGetAccessToken(const std::string& auth_code, switch (auth_type) { case AuthType::UrlEncodedBody: - body = fmt::format(UrlBodyTemplateWithCredentials, auth_code, + body = fmt::format(UrlBodyTemplateWithCredentialsForAuthCode, auth_code, Http::Utility::PercentEncoding::encode(client_id, ":/=&?"), Http::Utility::PercentEncoding::encode(secret, ":/=&?"), encoded_cb_url); break; @@ -49,7 +55,7 @@ void OAuth2ClientImpl::asyncGetAccessToken(const std::string& auth_code, const auto basic_auth_header_value = absl::StrCat("Basic ", encoded_token); request->headers().appendCopy(Http::CustomHeaders::get().Authorization, basic_auth_header_value); - body = fmt::format(UrlBodyTemplateWithoutCredentials, auth_code, encoded_cb_url); + body = fmt::format(UrlBodyTemplateWithoutCredentialsForAuthCode, auth_code, encoded_cb_url); break; } @@ -62,6 +68,39 @@ void OAuth2ClientImpl::asyncGetAccessToken(const std::string& auth_code, state_ = OAuthState::PendingAccessToken; } +void OAuth2ClientImpl::asyncRefreshAccessToken(const std::string& refresh_token, + const std::string& client_id, + const std::string& secret, AuthType auth_type) { + Http::RequestMessagePtr request = createPostRequest(); + std::string body; + + switch (auth_type) { + case AuthType::UrlEncodedBody: + body = fmt::format(UrlBodyTemplateWithCredentialsForRefreshToken, + Http::Utility::PercentEncoding::encode(refresh_token, ":/=&?"), + Http::Utility::PercentEncoding::encode(client_id, ":/=&?"), + Http::Utility::PercentEncoding::encode(secret, ":/=&?")); + break; + case AuthType::BasicAuth: + const auto basic_auth_token = absl::StrCat(client_id, ":", secret); + const auto encoded_token = Base64::encode(basic_auth_token.data(), basic_auth_token.size()); + const auto basic_auth_header_value = absl::StrCat("Basic ", encoded_token); + request->headers().appendCopy(Http::CustomHeaders::get().Authorization, + basic_auth_header_value); + body = fmt::format(UrlBodyTemplateWithoutCredentialsForRefreshToken, + Http::Utility::PercentEncoding::encode(refresh_token)); + break; + } + + request->body().add(body); + request->headers().setContentLength(body.length()); + ENVOY_LOG(debug, "Dispatching OAuth request for update access token by refresh token."); + dispatchRequest(std::move(request)); + + ASSERT(state_ == OAuthState::Idle); + state_ = OAuthState::PendingAccessTokenByRefreshToken; +} + void OAuth2ClientImpl::dispatchRequest(Http::RequestMessagePtr&& msg) { const auto thread_local_cluster = cm_.getThreadLocalCluster(uri_.cluster()); if (thread_local_cluster != nullptr) { @@ -78,15 +117,27 @@ void OAuth2ClientImpl::onSuccess(const Http::AsyncClient::Request&, Http::ResponseMessagePtr&& message) { in_flight_request_ = nullptr; - ASSERT(state_ == OAuthState::PendingAccessToken); + ASSERT(state_ == OAuthState::PendingAccessToken || + state_ == OAuthState::PendingAccessTokenByRefreshToken); + const OAuthState oldState = state_; state_ = OAuthState::Idle; // Check that the auth cluster returned a happy response. const auto response_code = message->headers().Status()->value().getStringView(); + if (response_code != "200") { ENVOY_LOG(debug, "Oauth response code: {}", response_code); ENVOY_LOG(debug, "Oauth response body: {}", message->bodyAsString()); - parent_->sendUnauthorizedResponse(); + switch (oldState) { + case OAuthState::PendingAccessToken: + parent_->sendUnauthorizedResponse(); + break; + case OAuthState::PendingAccessTokenByRefreshToken: + parent_->onRefreshAccessTokenFailure(); + break; + default: + PANIC("Malformed oauth client state"); + } return; } @@ -117,14 +168,35 @@ void OAuth2ClientImpl::onSuccess(const Http::AsyncClient::Request&, PROTOBUF_GET_WRAPPED_OR_DEFAULT(response, refresh_token, EMPTY_STRING)}; const std::chrono::seconds expires_in{PROTOBUF_GET_WRAPPED_REQUIRED(response, expires_in)}; - parent_->onGetAccessTokenSuccess(access_token, id_token, refresh_token, expires_in); + switch (oldState) { + case OAuthState::PendingAccessToken: + parent_->onGetAccessTokenSuccess(access_token, id_token, refresh_token, expires_in); + break; + case OAuthState::PendingAccessTokenByRefreshToken: + parent_->onRefreshAccessTokenSuccess(access_token, id_token, refresh_token, expires_in); + break; + default: + PANIC("Malformed oauth client state"); + } } void OAuth2ClientImpl::onFailure(const Http::AsyncClient::Request&, Http::AsyncClient::FailureReason) { ENVOY_LOG(debug, "OAuth request failed."); in_flight_request_ = nullptr; - parent_->sendUnauthorizedResponse(); + const OAuthState oldState = state_; + state_ = OAuthState::Idle; + + switch (oldState) { + case OAuthState::PendingAccessToken: + parent_->sendUnauthorizedResponse(); + break; + case OAuthState::PendingAccessTokenByRefreshToken: + parent_->onRefreshAccessTokenFailure(); + break; + default: + PANIC("Malformed oauth client state"); + } } } // namespace Oauth2 diff --git a/source/extensions/filters/http/oauth2/oauth_client.h b/source/extensions/filters/http/oauth2/oauth_client.h index a659714699ea..d18823c0ccbf 100644 --- a/source/extensions/filters/http/oauth2/oauth_client.h +++ b/source/extensions/filters/http/oauth2/oauth_client.h @@ -28,6 +28,11 @@ class OAuth2Client : public Http::AsyncClient::Callbacks { virtual void asyncGetAccessToken(const std::string& auth_code, const std::string& client_id, const std::string& secret, const std::string& cb_url, AuthType auth_type = AuthType::UrlEncodedBody) PURE; + + virtual void asyncRefreshAccessToken(const std::string& refresh_token, + const std::string& client_id, const std::string& secret, + AuthType auth_type = AuthType::UrlEncodedBody) PURE; + virtual void setCallbacks(FilterCallbacks& callbacks) PURE; // Http::AsyncClient::Callbacks @@ -55,11 +60,15 @@ class OAuth2ClientImpl : public OAuth2Client, Logger::Loggableset_inline_string( + config.proxy_host()); + proxy_host_formatter_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + proxy_substitution_format_config, context); + + if (config.has_proxy_port()) { + uint32_t port = config.proxy_port().value(); + if (port == 0 || port > 65535) { + throw EnvoyException("Port value not in range"); + } + + proxy_port_ = port; + } + + envoy::config::core::v3::SubstitutionFormatString target_substitution_format_config; + target_substitution_format_config.mutable_text_format_source()->set_inline_string( + config.target_host()); + target_host_formatter_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + target_substitution_format_config, context); +} + UdpProxyFilterConfigImpl::UdpProxyFilterConfigImpl( Server::Configuration::ListenerFactoryContext& context, const envoy::extensions::filters::udp::udp_proxy::v3::UdpProxyConfig& config) @@ -15,8 +74,12 @@ UdpProxyFilterConfigImpl::UdpProxyFilterConfigImpl( use_per_packet_load_balancing_(config.use_per_packet_load_balancing()), stats_(generateStats(config.stat_prefix(), context.scope())), // Default prefer_gro to true for upstream client traffic. - upstream_socket_config_(config.upstream_socket_config(), true), - random_(context.api().randomGenerator()) { + upstream_socket_config_(config.upstream_socket_config(), true) { + if (use_per_packet_load_balancing_ && config.has_tunneling_config()) { + throw EnvoyException( + "Only one of use_per_packet_load_balancing or tunneling_config can be used."); + } + if (use_per_packet_load_balancing_ && !config.session_filters().empty()) { throw EnvoyException( "Only one of use_per_packet_load_balancing or session_filters can be used."); @@ -44,6 +107,10 @@ UdpProxyFilterConfigImpl::UdpProxyFilterConfigImpl( hash_policy_ = std::make_unique(config.hash_policies()); } + if (config.has_tunneling_config()) { + tunneling_config_ = std::make_unique(config.tunneling_config(), context); + } + for (const auto& filter : config.session_filters()) { ENVOY_LOG(debug, " UDP session filter #{}", filter_factories_.size()); ENVOY_LOG(debug, " name: {}", filter.name()); diff --git a/source/extensions/filters/udp/udp_proxy/config.h b/source/extensions/filters/udp/udp_proxy/config.h index d0f6f943d1c4..8ec35334cbd5 100644 --- a/source/extensions/filters/udp/udp_proxy/config.h +++ b/source/extensions/filters/udp/udp_proxy/config.h @@ -11,6 +11,46 @@ namespace Extensions { namespace UdpFilters { namespace UdpProxy { +using TunnelingConfig = + envoy::extensions::filters::udp::udp_proxy::v3::UdpProxyConfig::UdpTunnelingConfig; + +class TunnelingConfigImpl : public UdpTunnelingConfig { +public: + TunnelingConfigImpl(const TunnelingConfig& config, + Server::Configuration::FactoryContext& context); + + const std::string proxyHost(const StreamInfo::StreamInfo& stream_info) const override { + return proxy_host_formatter_->formatWithContext({}, stream_info); + } + + const std::string targetHost(const StreamInfo::StreamInfo& stream_info) const override { + return target_host_formatter_->formatWithContext({}, stream_info); + } + + const absl::optional& proxyPort() const override { return proxy_port_; }; + uint32_t defaultTargetPort() const override { return target_port_; }; + bool usePost() const override { return use_post_; }; + const std::string& postPath() const override { return post_path_; } + Http::HeaderEvaluator& headerEvaluator() const override { return *header_parser_; }; + uint32_t maxConnectAttempts() const override { return max_connect_attempts_; }; + bool bufferEnabled() const override { return buffer_enabled_; }; + uint32_t maxBufferedDatagrams() const override { return max_buffered_datagrams_; }; + uint64_t maxBufferedBytes() const override { return max_buffered_bytes_; }; + +private: + std::unique_ptr header_parser_; + Formatter::FormatterPtr proxy_host_formatter_; + absl::optional proxy_port_; + Formatter::FormatterPtr target_host_formatter_; + const uint32_t target_port_; + bool use_post_; + std::string post_path_; + const uint32_t max_connect_attempts_; + bool buffer_enabled_; + uint32_t max_buffered_datagrams_; + uint64_t max_buffered_bytes_; +}; + class UdpProxyFilterConfigImpl : public UdpProxyFilterConfig, public FilterChainFactory, Logger::Loggable { @@ -34,7 +74,6 @@ class UdpProxyFilterConfigImpl : public UdpProxyFilterConfig, const Udp::HashPolicy* hashPolicy() const override { return hash_policy_.get(); } UdpProxyDownstreamStats& stats() const override { return stats_; } TimeSource& timeSource() const override { return time_source_; } - Random::RandomGenerator& randomGenerator() const override { return random_; } const Network::ResolvedUdpSocketConfig& upstreamSocketConfig() const override { return upstream_socket_config_; } @@ -46,6 +85,7 @@ class UdpProxyFilterConfigImpl : public UdpProxyFilterConfig, } const FilterChainFactory& sessionFilterFactory() const override { return *this; }; bool hasSessionFilters() const override { return !filter_factories_.empty(); } + const UdpTunnelingConfigPtr& tunnelingConfig() const override { return tunneling_config_; }; // FilterChainFactory void createFilterChain(FilterChainFactoryCallbacks& callbacks) const override { @@ -73,7 +113,7 @@ class UdpProxyFilterConfigImpl : public UdpProxyFilterConfig, const Network::ResolvedUdpSocketConfig upstream_socket_config_; std::vector session_access_logs_; std::vector proxy_access_logs_; - Random::RandomGenerator& random_; + UdpTunnelingConfigPtr tunneling_config_; std::list filter_factories_; }; diff --git a/source/extensions/filters/udp/udp_proxy/hash_policy_impl.cc b/source/extensions/filters/udp/udp_proxy/hash_policy_impl.cc index a4d5701d4452..c72bbeb29fec 100644 --- a/source/extensions/filters/udp/udp_proxy/hash_policy_impl.cc +++ b/source/extensions/filters/udp/udp_proxy/hash_policy_impl.cc @@ -1,5 +1,7 @@ #include "source/extensions/filters/udp/udp_proxy/hash_policy_impl.h" +#include "envoy/common/exception.h" + #include "source/common/common/assert.h" #include "source/common/common/macros.h" diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/BUILD b/source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/BUILD index a011c830e8a2..264c6840f882 100644 --- a/source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/BUILD +++ b/source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/BUILD @@ -28,6 +28,7 @@ envoy_cc_extension( name = "config", srcs = ["config.cc"], hdrs = ["config.h"], + visibility = ["//visibility:public"], deps = [ ":http_capsule_filter_lib", "//source/extensions/filters/udp/udp_proxy/session_filters:factory_base_lib", 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 779d396059e4..7725d50623af 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc @@ -2,6 +2,7 @@ #include "envoy/network/listener.h" +#include "source/common/buffer/buffer_impl.h" #include "source/common/network/socket_option_factory.h" namespace Envoy { @@ -163,7 +164,14 @@ UdpProxyFilter::ClusterInfo::createSession(Network::UdpRecvData::LocalPeerAddres UdpProxyFilter::ActiveSession* UdpProxyFilter::ClusterInfo::createSessionWithOptionalHost( Network::UdpRecvData::LocalPeerAddresses&& addresses, const Upstream::HostConstSharedPtr& host) { - auto new_session = std::make_unique(*this, std::move(addresses), host); + ActiveSessionPtr new_session; + if (filter_.config_->tunnelingConfig()) { + ASSERT(!host); + new_session = std::make_unique(*this, std::move(addresses)); + } else { + new_session = std::make_unique(*this, std::move(addresses), host); + } + new_session->createFilterChain(); new_session->onNewSession(); auto new_session_ptr = new_session.get(); @@ -186,7 +194,7 @@ UdpProxyFilter::StickySessionClusterInfo::StickySessionClusterInfo( HeterogeneousActiveSessionEqual(false))) {} Network::FilterStatus UdpProxyFilter::StickySessionClusterInfo::onData(Network::UdpRecvData& data) { - bool defer_socket = filter_.config_->hasSessionFilters(); + bool defer_socket = filter_.config_->hasSessionFilters() || filter_.config_->tunnelingConfig(); const auto active_session_it = sessions_.find(data.addresses_); ActiveSession* active_session; if (active_session_it == sessions_.end()) { @@ -580,14 +588,7 @@ void UdpProxyFilter::UdpActiveSession::processPacket( Network::UdpRecvData recv_data{ {std::move(local_address), std::move(peer_address)}, std::move(buffer), receive_time}; - for (auto& active_write_filter : write_filters_) { - auto status = active_write_filter->write_filter_->onWrite(recv_data); - if (status == WriteFilterStatus::StopIteration) { - return; - } - } - - writeDownstream(recv_data); + processUpstreamDatagram(recv_data); } void UdpProxyFilter::ActiveSession::resetIdleTimer() { @@ -598,6 +599,17 @@ void UdpProxyFilter::ActiveSession::resetIdleTimer() { idle_timer_->enableTimer(cluster_.filter_.config_->sessionTimeout()); } +void UdpProxyFilter::ActiveSession::processUpstreamDatagram(Network::UdpRecvData& recv_data) { + for (auto& active_write_filter : write_filters_) { + auto status = active_write_filter->write_filter_->onWrite(recv_data); + if (status == WriteFilterStatus::StopIteration) { + return; + } + } + + writeDownstream(recv_data); +} + void UdpProxyFilter::ActiveSession::writeDownstream(Network::UdpRecvData& recv_data) { const uint64_t tx_buffer_length = recv_data.buffer_->length(); ENVOY_LOG(trace, "writing {} byte datagram downstream: downstream={} local={} upstream={}", @@ -650,8 +662,11 @@ void HttpUpstreamImpl::setRequestEncoder(Http::RequestEncoder& request_encoder, headers->addReferenceKey(Http::Headers::get().Method, "POST"); headers->addReferenceKey(Http::Headers::get().Path, tunnel_config_.postPath()); } else { - headers->addReferenceKey(Http::Headers::get().Method, "CONNECT"); - headers->addReferenceKey(Http::Headers::get().Protocol, "connect-udp"); + // The Envoy HTTP/2 and HTTP/3 clients expect the request header map to be in the form of HTTP/1 + // upgrade to issue an extended CONNECT request. + headers->addReferenceKey(Http::Headers::get().Method, "GET"); + headers->addReferenceKey(Http::Headers::get().Connection, "Upgrade"); + headers->addReferenceKey(Http::Headers::get().Upgrade, "connect-udp"); headers->addReferenceKey(Http::Headers::get().CapsuleProtocol, "?1"); const std::string target_tunnel_path = resolveTargetTunnelPath(); headers->addReferenceKey(Http::Headers::get().Path, target_tunnel_path); @@ -773,6 +788,200 @@ TunnelingConnectionPoolPtr TunnelingConnectionPoolFactory::createConnPool( return (pool->valid() ? std::move(pool) : nullptr); } +UdpProxyFilter::TunnelingActiveSession::TunnelingActiveSession( + ClusterInfo& cluster, Network::UdpRecvData::LocalPeerAddresses&& addresses) + : ActiveSession(cluster, std::move(addresses), nullptr) {} + +void UdpProxyFilter::TunnelingActiveSession::createUpstream() { + if (conn_pool_factory_) { + // A session filter may call on continueFilterChain(), after already creating the upstream, + // so we first check that the factory was not created already. + return; + } + + conn_pool_factory_ = std::make_unique(); + load_balancer_context_ = std::make_unique( + cluster_.filter_.config_->hashPolicy(), addresses_.peer_); + + establishUpstreamConnection(); +} + +void UdpProxyFilter::TunnelingActiveSession::establishUpstreamConnection() { + if (!createConnectionPool()) { + ENVOY_LOG(debug, "failed to create upstream connection pool"); + cluster_.cluster_stats_.sess_tunnel_failure_.inc(); + cluster_.removeSession(this); + } +} + +bool UdpProxyFilter::TunnelingActiveSession::createConnectionPool() { + ASSERT(conn_pool_factory_); + + // Check this here because the TCP conn pool will queue our request waiting for a connection that + // will never be released. + if (!cluster_.cluster_.info() + ->resourceManager(Upstream::ResourcePriority::Default) + .connections() + .canCreate()) { + cluster_.cluster_.info()->trafficStats()->upstream_cx_overflow_.inc(); + return false; + } + + if (connect_attempts_ >= cluster_.filter_.config_->tunnelingConfig()->maxConnectAttempts()) { + cluster_.cluster_.info()->trafficStats()->upstream_cx_connect_attempts_exceeded_.inc(); + return false; + } else if (connect_attempts_ >= 1) { + cluster_.cluster_.info()->trafficStats()->upstream_rq_retry_.inc(); + } + + conn_pool_ = conn_pool_factory_->createConnPool(cluster_.cluster_, load_balancer_context_.get(), + *cluster_.filter_.config_->tunnelingConfig(), + *this, udp_session_info_); + + if (conn_pool_) { + connecting_ = true; + connect_attempts_++; + conn_pool_->newStream(*this); + return true; + } + + return false; +} + +void UdpProxyFilter::TunnelingActiveSession::onStreamFailure( + ConnectionPool::PoolFailureReason reason, absl::string_view failure_reason, + Upstream::HostDescriptionConstSharedPtr) { + ENVOY_LOG(debug, "Failed to create upstream stream: {}", failure_reason); + + conn_pool_.reset(); + upstream_.reset(); + + switch (reason) { + case ConnectionPool::PoolFailureReason::Overflow: + case ConnectionPool::PoolFailureReason::LocalConnectionFailure: + onUpstreamEvent(Network::ConnectionEvent::LocalClose); + break; + case ConnectionPool::PoolFailureReason::Timeout: + case ConnectionPool::PoolFailureReason::RemoteConnectionFailure: + onUpstreamEvent(Network::ConnectionEvent::RemoteClose); + break; + } +} + +void UdpProxyFilter::TunnelingActiveSession::onStreamReady(StreamInfo::StreamInfo* upstream_info, + std::unique_ptr&& upstream, + Upstream::HostDescriptionConstSharedPtr&, + const Network::ConnectionInfoProvider&, + Ssl::ConnectionInfoConstSharedPtr) { + // TODO(ohadvano): save the host description to host_ field. This requires refactoring because + // currently host_ is of type HostConstSharedPtr and not HostDescriptionConstSharedPtr. + ENVOY_LOG(debug, "Upstream connection [C{}] attached to session ID [S{}]", + upstream_info->downstreamAddressProvider().connectionID().value(), sessionId()); + + upstream_ = std::move(upstream); + conn_pool_.reset(); + connecting_ = false; + can_send_upstream_ = true; + cluster_.cluster_stats_.sess_tunnel_success_.inc(); + flushBuffer(); +} + +void UdpProxyFilter::TunnelingActiveSession::onUpstreamEvent(Network::ConnectionEvent event) { + if (event == Network::ConnectionEvent::Connected || + event == Network::ConnectionEvent::ConnectedZeroRtt) { + return; + } + + bool connecting = connecting_; + connecting_ = false; + + if (event == Network::ConnectionEvent::RemoteClose || + event == Network::ConnectionEvent::LocalClose) { + upstream_.reset(); + + if (connecting) { + establishUpstreamConnection(); + } else { + cluster_.removeSession(this); + } + } +} + +void UdpProxyFilter::TunnelingActiveSession::onAboveWriteBufferHighWatermark() { + can_send_upstream_ = false; +} + +void UdpProxyFilter::TunnelingActiveSession::onBelowWriteBufferLowWatermark() { + can_send_upstream_ = true; + flushBuffer(); +} + +void UdpProxyFilter::TunnelingActiveSession::flushBuffer() { + while (!datagrams_buffer_.empty()) { + BufferedDatagramPtr buffered_datagram = std::move(datagrams_buffer_.front()); + datagrams_buffer_.pop(); + buffered_bytes_ -= buffered_datagram->buffer_->length(); + upstream_->encodeData(*buffered_datagram->buffer_); + } +} + +void UdpProxyFilter::TunnelingActiveSession::maybeBufferDatagram(Network::UdpRecvData& data) { + if (!cluster_.filter_.config_->tunnelingConfig()->bufferEnabled()) { + return; + } + + if (datagrams_buffer_.size() == + cluster_.filter_.config_->tunnelingConfig()->maxBufferedDatagrams() || + buffered_bytes_ + data.buffer_->length() > + cluster_.filter_.config_->tunnelingConfig()->maxBufferedBytes()) { + cluster_.cluster_stats_.sess_tunnel_buffer_overflow_.inc(); + return; + } + + auto buffered_datagram = std::make_unique(); + buffered_datagram->addresses_ = {std::move(data.addresses_.local_), + std::move(data.addresses_.peer_)}; + buffered_datagram->buffer_ = std::move(data.buffer_); + buffered_datagram->receive_time_ = data.receive_time_; + buffered_bytes_ += buffered_datagram->buffer_->length(); + datagrams_buffer_.push(std::move(buffered_datagram)); +} + +void UdpProxyFilter::TunnelingActiveSession::writeUpstream(Network::UdpRecvData& data) { + if (!upstream_ || !can_send_upstream_) { + maybeBufferDatagram(data); + return; + } + + if (upstream_) { + upstream_->encodeData(*data.buffer_); + } +} + +void UdpProxyFilter::TunnelingActiveSession::onUpstreamData(Buffer::Instance& data, bool) { + const uint64_t rx_buffer_length = data.length(); + ENVOY_LOG(trace, "received {} byte datagram from upstream: downstream={} local={} upstream={}", + rx_buffer_length, addresses_.peer_->asStringView(), addresses_.local_->asStringView(), + host_ != nullptr ? host_->address()->asStringView() : "unknown"); + + cluster_.cluster_stats_.sess_rx_datagrams_.inc(); + cluster_.cluster_.info()->trafficStats()->upstream_cx_rx_bytes_total_.add(rx_buffer_length); + resetIdleTimer(); + + Network::UdpRecvData recv_data{{addresses_.local_, addresses_.peer_}, + std::make_unique(data), + cluster_.filter_.config_->timeSource().monotonicTime()}; + processUpstreamDatagram(recv_data); +} + +void UdpProxyFilter::TunnelingActiveSession::onIdleTimer() { + ENVOY_LOG(debug, "session idle timeout: downstream={} local={}", addresses_.peer_->asStringView(), + addresses_.local_->asStringView()); + cluster_.filter_.config_->stats().idle_timeout_.inc(); + upstream_->onDownstreamEvent(Network::ConnectionEvent::LocalClose); + cluster_.removeSession(this); +} + } // namespace UdpProxy } // namespace UdpFilters } // namespace Extensions 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 45ae14ea00bb..ddbb6ee6ea5f 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h @@ -72,6 +72,9 @@ struct UdpProxyDownstreamStats { COUNTER(sess_rx_datagrams_dropped) \ COUNTER(sess_rx_errors) \ COUNTER(sess_tx_datagrams) \ + COUNTER(sess_tunnel_success) \ + COUNTER(sess_tunnel_failure) \ + COUNTER(sess_tunnel_buffer_overflow) \ COUNTER(sess_tx_errors) /** @@ -95,6 +98,10 @@ class UdpTunnelingConfig { virtual bool usePost() const PURE; virtual const std::string& postPath() const PURE; virtual Http::HeaderEvaluator& headerEvaluator() const PURE; + virtual uint32_t maxConnectAttempts() const PURE; + virtual bool bufferEnabled() const PURE; + virtual uint32_t maxBufferedDatagrams() const PURE; + virtual uint64_t maxBufferedBytes() const PURE; }; using UdpTunnelingConfigPtr = std::unique_ptr; @@ -113,12 +120,12 @@ class UdpProxyFilterConfig { virtual const Udp::HashPolicy* hashPolicy() const PURE; virtual UdpProxyDownstreamStats& stats() const PURE; virtual TimeSource& timeSource() const PURE; - virtual Random::RandomGenerator& randomGenerator() const PURE; virtual const Network::ResolvedUdpSocketConfig& upstreamSocketConfig() const PURE; virtual const std::vector& sessionAccessLogs() const PURE; virtual const std::vector& proxyAccessLogs() const PURE; virtual const FilterChainFactory& sessionFilterFactory() const PURE; virtual bool hasSessionFilters() const PURE; + virtual const UdpTunnelingConfigPtr& tunnelingConfig() const PURE; }; using UdpProxyFilterConfigSharedPtr = std::shared_ptr; @@ -294,7 +301,13 @@ class HttpUpstreamImpl : public HttpUpstream, protected Http::StreamCallbacks { } void decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) override { - bool is_valid_response = Http::CodeUtility::is2xx(Http::Utility::getResponseStatus(*headers)); + bool is_valid_response; + if (parent_.tunnel_config_.usePost()) { + auto status = Http::Utility::getResponseStatus(*headers); + is_valid_response = Http::CodeUtility::is2xx(status); + } else { + is_valid_response = Http::HeaderUtility::isConnectUdpResponse(*headers); + } if (!is_valid_response || end_stream) { parent_.resetEncoder(Network::ConnectionEvent::LocalClose); @@ -494,6 +507,7 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, void onNewSession(); void onData(Network::UdpRecvData& data); + void processUpstreamDatagram(Network::UdpRecvData& data); void writeDownstream(Network::UdpRecvData& data); void resetIdleTimer(); @@ -612,6 +626,58 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, const bool use_original_src_ip_; }; + /** + * This type of active session is used when tunneling is enabled by configuration. + * In this type of session, the upstream is HTTP stream, either a connect-udp request, + * or a POST request. + */ + class TunnelingActiveSession : public ActiveSession, + public UpstreamTunnelCallbacks, + public HttpStreamCallbacks { + public: + TunnelingActiveSession(ClusterInfo& parent, + Network::UdpRecvData::LocalPeerAddresses&& addresses); + ~TunnelingActiveSession() override = default; + + // ActiveSession + void createUpstream() override; + void writeUpstream(Network::UdpRecvData& data) override; + void onIdleTimer() override; + + // UpstreamTunnelCallbacks + void onUpstreamEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override; + void onBelowWriteBufferLowWatermark() override; + void onUpstreamData(Buffer::Instance& data, bool end_stream) override; + + // HttpStreamCallbacks + void onStreamReady(StreamInfo::StreamInfo*, std::unique_ptr&&, + Upstream::HostDescriptionConstSharedPtr&, + const Network::ConnectionInfoProvider&, + Ssl::ConnectionInfoConstSharedPtr) override; + + void onStreamFailure(ConnectionPool::PoolFailureReason, absl::string_view, + Upstream::HostDescriptionConstSharedPtr) override; + + private: + using BufferedDatagramPtr = std::unique_ptr; + + void establishUpstreamConnection(); + bool createConnectionPool(); + void maybeBufferDatagram(Network::UdpRecvData& data); + void flushBuffer(); + + TunnelingConnectionPoolFactoryPtr conn_pool_factory_; + std::unique_ptr load_balancer_context_; + TunnelingConnectionPoolPtr conn_pool_; + std::unique_ptr upstream_; + uint32_t connect_attempts_{}; + bool connecting_{}; + bool can_send_upstream_{}; + uint64_t buffered_bytes_{}; + std::queue datagrams_buffer_; + }; + struct LocalPeerHostAddresses { const Network::UdpRecvData::LocalPeerAddresses& local_peer_addresses_; absl::optional> host_; diff --git a/source/extensions/upstreams/tcp/generic/BUILD b/source/extensions/upstreams/tcp/generic/BUILD index 551a4ca75359..a29fa7133934 100644 --- a/source/extensions/upstreams/tcp/generic/BUILD +++ b/source/extensions/upstreams/tcp/generic/BUILD @@ -19,7 +19,9 @@ envoy_cc_extension( visibility = ["//visibility:public"], deps = [ "//envoy/stream_info:bool_accessor_interface", + "//envoy/stream_info:filter_state_interface", "//source/common/http:codec_client_lib", + "//source/common/stream_info:bool_accessor_lib", "//source/common/tcp_proxy:upstream_lib", "@envoy_api//envoy/extensions/upstreams/tcp/generic/v3:pkg_cc_proto", ], diff --git a/source/extensions/upstreams/tcp/generic/config.cc b/source/extensions/upstreams/tcp/generic/config.cc index 709323a2a04b..e688ab84a510 100644 --- a/source/extensions/upstreams/tcp/generic/config.cc +++ b/source/extensions/upstreams/tcp/generic/config.cc @@ -1,9 +1,11 @@ #include "source/extensions/upstreams/tcp/generic/config.h" #include "envoy/stream_info/bool_accessor.h" +#include "envoy/stream_info/filter_state.h" #include "envoy/upstream/cluster_manager.h" #include "source/common/http/codec_client.h" +#include "source/common/stream_info/bool_accessor_impl.h" #include "source/common/tcp_proxy/upstream.h" namespace Envoy { @@ -47,6 +49,19 @@ bool GenericConnPoolFactory::disableTunnelingByFilterState( REGISTER_FACTORY(GenericConnPoolFactory, TcpProxy::GenericConnPoolFactory); +class DisableTunnelingObjectFactory : public StreamInfo::FilterState::ObjectFactory { +public: + std::string name() const override { + return std::string(TcpProxy::DisableTunnelingFilterStateKey); + } + std::unique_ptr + createFromBytes(absl::string_view data) const override { + return std::make_unique(data == "true"); + } +}; + +REGISTER_FACTORY(DisableTunnelingObjectFactory, StreamInfo::FilterState::ObjectFactory); + } // namespace Generic } // namespace Tcp } // namespace Upstreams diff --git a/support/README.md b/support/README.md index c1dce995fd60..1a0d2a19e12e 100644 --- a/support/README.md +++ b/support/README.md @@ -69,5 +69,5 @@ To run clang-tidy under Docker, run the following (this creates a full compilation db and takes a long time): ```console -./ci/run_envoy_docker.sh ci/do_ci.sh bazel.clang_tidy +./ci/run_envoy_docker.sh ci/do_ci.sh clang_tidy ``` diff --git a/test/common/http/codec_impl_corpus/h10_empty_hostname b/test/common/http/codec_impl_corpus/h10_empty_hostname new file mode 100644 index 000000000000..f541da572451 --- /dev/null +++ b/test/common/http/codec_impl_corpus/h10_empty_hostname @@ -0,0 +1,33 @@ +h1_settings { + server { + accept_http_10: true + default_host_for_http_10: "\000\000\000\000\000\000\000\000" + } +} +actions { + new_stream { + request_headers { + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/" + } + } + end_stream: true + } +} +actions { + mutate { + offset: 5 + value: 48 + } +} +actions { + mutate { + offset: 48 + value: 48 + } +} diff --git a/test/common/http/codec_impl_fuzz_test.cc b/test/common/http/codec_impl_fuzz_test.cc index 42dc4e692c01..46db8ea12b5c 100644 --- a/test/common/http/codec_impl_fuzz_test.cc +++ b/test/common/http/codec_impl_fuzz_test.cc @@ -66,6 +66,12 @@ Http1Settings fromHttp1Settings(const test::common::http::Http1ServerSettings& s h1_settings.accept_http_10_ = settings.accept_http_10(); h1_settings.default_host_for_http_10_ = settings.default_host_for_http_10(); + // If the server accepts a HTTP/1.0 then the default host must be valid. + if (h1_settings.accept_http_10_ && + !HeaderUtility::authorityIsValid(h1_settings.default_host_for_http_10_)) { + throw EnvoyException("Invalid Http1ServerSettings, HTTP/1.0 is enabled and " + "'default_host_for_http_10' has invalid hostname, skipping test."); + } return h1_settings; } diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index beb5dad69afe..5e06e3e5cc4c 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -4341,6 +4341,9 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetailsAndResponseCodeAndServerNa } TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetailsAndResponseCode) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); proxy_status_config_ = std::make_unique(); proxy_status_config_->set_remove_details(false); proxy_status_config_->set_set_recommended_response_code(true); @@ -4353,13 +4356,16 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetailsAndResponseCode) { ASSERT_TRUE(altered_headers); ASSERT_TRUE(altered_headers->ProxyStatus()); EXPECT_EQ(altered_headers->getProxyStatusValue(), - "custom_server_name; error=connection_timeout; details=\"bar; UT\""); + "custom_server_name; error=http_response_timeout; details=\"bar; UT\""); // Changed from request, since set_recommended_response_code is true. Here, // 504 is the recommended response code for UpstreamRequestTimeout. EXPECT_EQ(altered_headers->getStatusValue(), "504"); } TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetails) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); proxy_status_config_ = std::make_unique(); proxy_status_config_->set_remove_details(false); proxy_status_config_->set_remove_connection_termination_details(false); @@ -4374,7 +4380,7 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetails) { ASSERT_TRUE(altered_headers); ASSERT_TRUE(altered_headers->ProxyStatus()); EXPECT_EQ(altered_headers->getProxyStatusValue(), - "custom_server_name; error=connection_timeout; details=\"bar; UT\""); + "custom_server_name; error=http_response_timeout; details=\"bar; UT\""); // Unchanged from request, since set_recommended_response_code is false. Here, // 504 would be the recommended response code for UpstreamRequestTimeout, EXPECT_NE(altered_headers->getStatusValue(), "504"); @@ -4382,6 +4388,9 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetails) { } TEST_F(ProxyStatusTest, PopulateProxyStatusWithoutDetails) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); proxy_status_config_ = std::make_unique(); proxy_status_config_->set_remove_details(true); proxy_status_config_->set_set_recommended_response_code(false); @@ -4393,7 +4402,8 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusWithoutDetails) { ASSERT_TRUE(altered_headers); ASSERT_TRUE(altered_headers->ProxyStatus()); - EXPECT_EQ(altered_headers->getProxyStatusValue(), "custom_server_name; error=connection_timeout"); + EXPECT_EQ(altered_headers->getProxyStatusValue(), + "custom_server_name; error=http_response_timeout"); // Unchanged. EXPECT_EQ(altered_headers->getStatusValue(), "403"); // Since remove_details=true, we should not have "baz", the value of @@ -4402,6 +4412,9 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusWithoutDetails) { } TEST_F(ProxyStatusTest, PopulateProxyStatusAppendToPreviousValue) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); proxy_status_config_ = std::make_unique(); proxy_status_config_->set_remove_details(false); @@ -4415,7 +4428,7 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusAppendToPreviousValue) { ASSERT_TRUE(altered_headers->ProxyStatus()); // Expect to see the appended previous value: "SomeCDN; custom_server_name; ...". EXPECT_EQ(altered_headers->getProxyStatusValue(), - "SomeCDN, custom_server_name; error=connection_timeout; details=\"baz; UT\""); + "SomeCDN, custom_server_name; error=http_response_timeout; details=\"baz; UT\""); } } // namespace Http diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index 1119ca4b1503..53a4e60e1c08 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -1148,16 +1148,16 @@ TEST(HeaderIsValidTest, IsConnect) { EXPECT_FALSE(HeaderUtility::isConnect(Http::TestRequestHeaderMapImpl{})); } -TEST(HeaderIsValidTest, IsConnectUdp) { - EXPECT_TRUE( - HeaderUtility::isConnectUdp(Http::TestRequestHeaderMapImpl{{"upgrade", "connect-udp"}})); +TEST(HeaderIsValidTest, IsConnectUdpRequest) { + EXPECT_TRUE(HeaderUtility::isConnectUdpRequest( + Http::TestRequestHeaderMapImpl{{"upgrade", "connect-udp"}})); // Should use case-insensitive comparison for the upgrade values. - EXPECT_TRUE( - HeaderUtility::isConnectUdp(Http::TestRequestHeaderMapImpl{{"upgrade", "CONNECT-UDP"}})); + EXPECT_TRUE(HeaderUtility::isConnectUdpRequest( + Http::TestRequestHeaderMapImpl{{"upgrade", "CONNECT-UDP"}})); // Extended CONNECT requests should be normalized to HTTP/1.1. - EXPECT_FALSE(HeaderUtility::isConnectUdp( + EXPECT_FALSE(HeaderUtility::isConnectUdpRequest( Http::TestRequestHeaderMapImpl{{":method", "CONNECT"}, {":protocol", "connect-udp"}})); - EXPECT_FALSE(HeaderUtility::isConnectUdp(Http::TestRequestHeaderMapImpl{})); + EXPECT_FALSE(HeaderUtility::isConnectUdpRequest(Http::TestRequestHeaderMapImpl{})); } TEST(HeaderIsValidTest, IsConnectResponse) { diff --git a/test/common/protobuf/BUILD b/test/common/protobuf/BUILD index 5870810eeff2..e45bd54b7be8 100644 --- a/test/common/protobuf/BUILD +++ b/test/common/protobuf/BUILD @@ -67,8 +67,8 @@ envoy_cc_test( name = "proto_descriptor_test", srcs = ["proto_descriptor_test.cc"], deps = [ - "//bazel/cc_proto_descriptor_library:create_dynamic_message", - "//bazel/cc_proto_descriptor_library:text_format_transcoder", + "@envoy_api//bazel/cc_proto_descriptor_library:create_dynamic_message", + "@envoy_api//bazel/cc_proto_descriptor_library:text_format_transcoder", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto_descriptor", ], diff --git a/test/common/stream_info/BUILD b/test/common/stream_info/BUILD index e34276643ad3..89795b60e010 100644 --- a/test/common/stream_info/BUILD +++ b/test/common/stream_info/BUILD @@ -66,6 +66,7 @@ envoy_cc_test( deps = [ "//source/common/stream_info:utility_lib", "//test/mocks/stream_info:stream_info_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], ) diff --git a/test/common/stream_info/utility_test.cc b/test/common/stream_info/utility_test.cc index ed7f6747b4f2..9e7dabfdf72a 100644 --- a/test/common/stream_info/utility_test.cc +++ b/test/common/stream_info/utility_test.cc @@ -4,6 +4,7 @@ #include "source/common/stream_info/utility.h" #include "test/mocks/stream_info/mocks.h" +#include "test/test_common/test_runtime.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -315,11 +316,14 @@ TEST(ProxyStatusErrorToString, TestAll) { } TEST(ProxyStatusFromStreamInfo, TestAll) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); for (const auto& [response_flag, proxy_status_error] : std::vector>{ {ResponseFlag::FailedLocalHealthCheck, ProxyStatusError::DestinationUnavailable}, {ResponseFlag::NoHealthyUpstream, ProxyStatusError::DestinationUnavailable}, - {ResponseFlag::UpstreamRequestTimeout, ProxyStatusError::ConnectionTimeout}, + {ResponseFlag::UpstreamRequestTimeout, ProxyStatusError::HttpResponseTimeout}, {ResponseFlag::LocalReset, ProxyStatusError::ConnectionTimeout}, {ResponseFlag::UpstreamRemoteReset, ProxyStatusError::ConnectionTerminated}, {ResponseFlag::UpstreamConnectionFailure, ProxyStatusError::ConnectionRefused}, @@ -343,6 +347,16 @@ TEST(ProxyStatusFromStreamInfo, TestAll) { } } +TEST(ProxyStatusFromStreamInfo, TestUpstreamRequestTimeout) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "false"}}); + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(ResponseFlag::UpstreamRequestTimeout)) + .WillByDefault(Return(true)); + EXPECT_THAT(ProxyStatusUtils::fromStreamInfo(stream_info), ProxyStatusError::ConnectionTimeout); +} + } // namespace } // namespace StreamInfo } // namespace Envoy 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 be89f0691935..622d9bacd4ed 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 @@ -429,59 +429,6 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, } } - // Send response data with small chunk size in STREAMED mode. - void streamingDataWithSmallChunks(const int last_chunk_size, const bool mutate_last_chunk, - std::string response_body) { - proto_config_.mutable_processing_mode()->set_response_body_mode(ProcessingMode::STREAMED); - proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); - proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); - initializeConfig(); - HttpIntegrationTest::initialize(); - - auto response = sendDownstreamRequest(absl::nullopt); - 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(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); - - // Send four chunks in total with last chunk end_stream flag set to be true.. - int chunk_number = 3; - for (int i = 0; i < chunk_number; i++) { - upstream_request_->encodeData(1, false); - } - upstream_request_->encodeData(last_chunk_size, true); - - // First chunk response. - processResponseBodyMessage( - *grpc_upstreams_[0], true, [](const HttpBody& body, BodyResponse& body_resp) { - auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); - body_mut->set_body(body.body() + " First "); - return true; - }); - - for (int i = 0; i < chunk_number - 1; i++) { - processResponseBodyMessage( - *grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse& body_resp) { - auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); - body_mut->set_body(body.body() + " The Rest "); - return true; - }); - } - - if (mutate_last_chunk) { - processResponseBodyMessage( - *grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse& body_resp) { - auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); - body_mut->set_body(body.body() + " The Last "); - return true; - }); - } else { - processResponseBodyMessage(*grpc_upstreams_[0], false, absl::nullopt); - } - verifyDownstreamResponse(*response, 200); - EXPECT_EQ(response_body, response->body()); - } - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_{}; uint32_t max_message_timeout_ms_{0}; std::vector grpc_upstreams_; @@ -3027,93 +2974,6 @@ TEST_P(ExtProcIntegrationTest, SkipHeaderTrailerSendBodyClientSendAll) { verifyDownstreamResponse(*response, 200); } -// Send response data with small chunk size. -TEST_P(ExtProcIntegrationTest, StreamingResponseDataWithSmallChunks) { - streamingDataWithSmallChunks(1, true, "a First a The Rest a The Rest a The Last "); -} - -// Send response data with small chunk size terminated with an empty string. -TEST_P(ExtProcIntegrationTest, StreamingResponseDataSmallChunksTerminateEmptyString) { - streamingDataWithSmallChunks(0, true, "a First a The Rest a The Rest The Last "); -} - -// Send response data with small chunk size terminated with an empty string and no mutation -// on it. Since the last chunk data size after mutation is zero, Envoy won't inject it. -TEST_P(ExtProcIntegrationTest, StreamingResponseDataSmallChunksNoMutationLastChunk) { - streamingDataWithSmallChunks(0, false, "a First a The Rest a The Rest "); -} - -TEST_P(ExtProcIntegrationTest, StreamingRequestDataSmallChunks) { - proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::STREAMED); - proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); - 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); - - int chunk_number = 5; - for (int i = 0; i < chunk_number; i++) { - codec_client_->sendData(*request_encoder_, i + 1, false); - } - codec_client_->sendData(*request_encoder_, chunk_number + 1, true); - - processRequestBodyMessage(*grpc_upstreams_[0], true, absl::nullopt); - for (int i = 0; i < chunk_number - 1; i++) { - processRequestBodyMessage(*grpc_upstreams_[0], false, absl::nullopt); - } - // ext_proc server responds to clear the last chunk body. - processRequestBodyMessage( - *grpc_upstreams_[0], false, [](const HttpBody& body, BodyResponse& body_resp) { - EXPECT_TRUE(body.end_of_stream()); - auto* body_mut = body_resp.mutable_response()->mutable_body_mutation(); - body_mut->set_clear_body(true); - return true; - }); - - handleUpstreamRequest(); - verifyDownstreamResponse(*response, 200); -} - -TEST_P(ExtProcIntegrationTest, StreamingRequestBodyWithTrailer) { - proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::STREAMED); - proto_config_.mutable_processing_mode()->set_request_trailer_mode(ProcessingMode::SEND); - proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); - proto_config_.mutable_processing_mode()->set_request_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); - - int chunk_number = 5; - // Sending streamed body. - for (int i = 0; i < chunk_number; i++) { - codec_client_->sendData(*request_encoder_, i + 1, false); - } - - Http::TestRequestTrailerMapImpl request_trailers{{"request", "trailer"}}; - codec_client_->sendTrailers(*request_encoder_, request_trailers); - - processRequestBodyMessage(*grpc_upstreams_[0], true, absl::nullopt); - for (int i = 0; i < chunk_number - 1; i++) { - processRequestBodyMessage(*grpc_upstreams_[0], false, absl::nullopt); - } - processRequestTrailersMessage(*grpc_upstreams_[0], false, absl::nullopt); - - handleUpstreamRequest(); - verifyDownstreamResponse(*response, 200); -} - TEST_P(ExtProcIntegrationTest, SendBodyBufferedPartialWithTrailer) { proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::BUFFERED_PARTIAL); proto_config_.mutable_processing_mode()->set_request_trailer_mode(ProcessingMode::SEND); diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index fcf4b4421c1b..77d0479c3dd8 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -454,6 +454,79 @@ class HttpFilterTest : public testing::Test { } } + void StreamingSmallChunksWithBodyMutation(bool empty_last_chunk, bool mutate_last_chunk) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SKIP" + response_header_mode: "SKIP" + request_body_mode: "NONE" + response_body_mode: "STREAMED" + request_trailer_mode: "SKIP" + response_trailer_mode: "SKIP" + )EOF"); + + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); + + Buffer::OwnedImpl first_chunk("foo"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(first_chunk, false)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + + response_headers_.addCopy(LowerCaseString(":status"), "200"); + response_headers_.addCopy(LowerCaseString("content-type"), "text/plain"); + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, false)); + + Buffer::OwnedImpl want_response_body; + Buffer::OwnedImpl got_response_body; + EXPECT_CALL(encoder_callbacks_, injectEncodedDataToFilterChain(_, _)) + .WillRepeatedly(Invoke([&got_response_body](Buffer::Instance& data, Unused) { + got_response_body.move(data); + })); + uint32_t chunk_number = 3; + for (uint32_t i = 0; i < chunk_number; i++) { + Buffer::OwnedImpl resp_data(std::to_string(i)); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + processResponseBody( + [i, &want_response_body](const HttpBody& body, ProcessingResponse&, BodyResponse& resp) { + auto* body_mut = resp.mutable_response()->mutable_body_mutation(); + body_mut->set_body(body.body() + " " + std::to_string(i) + " "); + want_response_body.add(body.body() + " " + std::to_string(i) + " "); + }, + false); + } + + std::string last_chunk_str = ""; + Buffer::OwnedImpl resp_data; + if (!empty_last_chunk) { + last_chunk_str = std::to_string(chunk_number); + } + resp_data.add(last_chunk_str); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_data, true)); + if (mutate_last_chunk) { + processResponseBody( + [&chunk_number, &want_response_body](const HttpBody& body, ProcessingResponse&, + BodyResponse& resp) { + auto* body_mut = resp.mutable_response()->mutable_body_mutation(); + body_mut->set_body(body.body() + " " + std::to_string(chunk_number) + " "); + want_response_body.add(body.body() + " " + std::to_string(chunk_number) + " "); + }, + true); + } else { + processResponseBody(absl::nullopt, true); + want_response_body.add(last_chunk_str); + } + + EXPECT_EQ(want_response_body.toString(), got_response_body.toString()); + filter_->onDestroy(); + + EXPECT_EQ(1, config_->stats().streams_started_.value()); + EXPECT_EQ(4, config_->stats().stream_msgs_sent_.value()); + EXPECT_EQ(4, config_->stats().stream_msgs_received_.value()); + EXPECT_EQ(1, config_->stats().streams_closed_.value()); + } + // The metadata configured as part of ext_proc filter should be in the filter state. // In addition, bytes sent/received should also be stored. void expectFilterState(const Envoy::ProtobufWkt::Struct& expected_metadata) { @@ -1405,6 +1478,22 @@ TEST_F(HttpFilterTest, StreamingDataSmallChunk) { checkGrpcCallStatsAll(envoy::config::core::v3::TrafficDirection::OUTBOUND, 2 * chunk_number); } +TEST_F(HttpFilterTest, StreamingBodyMutateLastEmptyChunk) { + StreamingSmallChunksWithBodyMutation(true, true); +} + +TEST_F(HttpFilterTest, StreamingBodyNotMutateLastEmptyChunk) { + StreamingSmallChunksWithBodyMutation(true, false); +} + +TEST_F(HttpFilterTest, StreamingBodyMutateLastChunk) { + StreamingSmallChunksWithBodyMutation(false, true); +} + +TEST_F(HttpFilterTest, StreamingBodyNotMutateLastChunk) { + StreamingSmallChunksWithBodyMutation(false, false); +} + // gRPC call fails when streaming sends small chunk request data. TEST_F(HttpFilterTest, StreamingSendRequestDataGrpcFail) { initializeTestSendAll(); diff --git a/test/extensions/filters/http/oauth2/config_test.cc b/test/extensions/filters/http/oauth2/config_test.cc index a44afe3fbd4a..fcefd3e7542f 100644 --- a/test/extensions/filters/http/oauth2/config_test.cc +++ b/test/extensions/filters/http/oauth2/config_test.cc @@ -132,7 +132,7 @@ TEST(ConfigTest, CreateFilter) { EXPECT_CALL(context, getTransportSocketFactoryContext()); Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(*proto_config, "stats", context); Http::MockFilterChainFactoryCallbacks filter_callback; - EXPECT_CALL(filter_callback, addStreamDecoderFilter(_)); + EXPECT_CALL(filter_callback, addStreamFilter(_)); cb(filter_callback); } diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index 97038bff5be4..bfa26863db4f 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -10,6 +10,7 @@ #include "source/common/http/message_impl.h" #include "source/common/protobuf/message_validator_impl.h" #include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_protos.h" #include "source/common/secret/secret_manager_impl.h" #include "source/extensions/filters/http/oauth2/filter.h" @@ -59,6 +60,8 @@ class MockOAuth2CookieValidator : public CookieValidator { MOCK_METHOD(std::string&, username, (), (const)); MOCK_METHOD(std::string&, token, (), (const)); MOCK_METHOD(std::string&, refreshToken, (), (const)); + + MOCK_METHOD(bool, canUpdateTokenByRefreshToken, (), (const)); MOCK_METHOD(bool, isValid, (), (const)); MOCK_METHOD(void, setParams, (const Http::RequestHeaderMap& headers, const std::string& secret)); }; @@ -74,6 +77,10 @@ class MockOAuth2Client : public OAuth2Client { MOCK_METHOD(void, asyncGetAccessToken, (const std::string&, const std::string&, const std::string&, const std::string&, Envoy::Extensions::HttpFilters::Oauth2::AuthType)); + + MOCK_METHOD(void, asyncRefreshAccessToken, + (const std::string&, const std::string&, const std::string&, + Envoy::Extensions::HttpFilters::Oauth2::AuthType)); }; class OAuth2Test : public testing::TestWithParam { @@ -93,12 +100,17 @@ class OAuth2Test : public testing::TestWithParam { config_ = config; filter_ = std::make_shared(config_, std::move(oauth_client_ptr), test_time_); filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); validator_ = std::make_shared(); filter_->validator_ = validator_; } // Set up proto fields with standard config. - FilterConfigSharedPtr getConfig(bool forward_bearer_token = true) { + FilterConfigSharedPtr + getConfig(bool forward_bearer_token = true, bool use_refresh_token = false, + ::envoy::extensions::filters::http::oauth2::v3::OAuth2Config_AuthType auth_type = + ::envoy::extensions::filters::http::oauth2::v3::OAuth2Config_AuthType:: + OAuth2Config_AuthType_URL_ENCODED_BODY) { envoy::extensions::filters::http::oauth2::v3::OAuth2Config p; auto* endpoint = p.mutable_token_endpoint(); endpoint->set_cluster("auth.example.com"); @@ -109,6 +121,10 @@ class OAuth2Test : public testing::TestWithParam { p.set_authorization_endpoint("https://auth.example.com/oauth/authorize/"); p.mutable_signout_path()->mutable_path()->set_exact("/_signout"); p.set_forward_bearer_token(forward_bearer_token); + + auto* useRefreshToken = p.mutable_use_refresh_token(); + useRefreshToken->set_value(use_refresh_token); + p.set_auth_type(auth_type); p.add_auth_scopes("user"); p.add_auth_scopes("openid"); p.add_auth_scopes("email"); @@ -169,11 +185,13 @@ class OAuth2Test : public testing::TestWithParam { auto cookie_validator = std::make_shared(test_time_, cookie_names); EXPECT_EQ(cookie_validator->token(), ""); + EXPECT_EQ(cookie_validator->refreshToken(), ""); cookie_validator->setParams(request_headers, "mock-secret"); EXPECT_TRUE(cookie_validator->hmacIsValid()); EXPECT_TRUE(cookie_validator->timestampIsValid()); EXPECT_TRUE(cookie_validator->isValid()); + EXPECT_FALSE(cookie_validator->canUpdateTokenByRefreshToken()); // If we advance time beyond 10s the timestamp should no longer be valid. test_time_.advanceTimeWait(std::chrono::seconds(11)); @@ -185,6 +203,7 @@ class OAuth2Test : public testing::TestWithParam { NiceMock* attachmentTimeout_timer_{}; NiceMock factory_context_; NiceMock decoder_callbacks_; + NiceMock encoder_callbacks_; NiceMock cm_; std::shared_ptr validator_; std::shared_ptr filter_; @@ -971,6 +990,25 @@ TEST_F(OAuth2Test, CookieValidatorInvalidExpiresAt) { } } +// Validates the behavior of the cookie validator when the expires_at value is not a valid integer. +TEST_F(OAuth2Test, CookieValidatorCanUpdateToken) { + Http::TestRequestHeaderMapImpl request_headers{ + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Path.get(), "/anypath"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Cookie.get(), "OauthExpires=notanumber;version=test"}, + {Http::Headers::get().Cookie.get(), + "BearerToken=xyztoken;version=test;RefreshToken=dsdtoken;"}, + }; + + auto cookie_validator = std::make_shared( + test_time_, + CookieNames("BearerToken", "OauthHMAC", "OauthExpires", "IdToken", "RefreshToken")); + cookie_validator->setParams(request_headers, "mock-secret"); + + EXPECT_TRUE(cookie_validator->canUpdateTokenByRefreshToken()); +} + // Verify that we 401 the request if the state query param doesn't contain a valid URL. TEST_F(OAuth2Test, OAuthTestInvalidUrlInStateQueryParam) { Http::TestRequestHeaderMapImpl request_headers{ @@ -1365,6 +1403,97 @@ TEST_F(OAuth2Test, OAuthTestFullFlowPostWithParametersLegacyEncoding) { } } +TEST_F(OAuth2Test, OAuthTestFullFlowPostWithParametersFillRefreshAndIdToken) { + // First construct the initial request to the oauth filter with URI parameters. + Http::TestRequestHeaderMapImpl first_request_headers{ + {Http::Headers::get().Path.get(), "/test?name=admin&level=trace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + // This is the immediate response - a redirect to the auth cluster. + Http::TestResponseHeaderMapImpl first_response_headers{ + {Http::Headers::get().Status.get(), "302"}, + {Http::Headers::get().Location.get(), + "https://auth.example.com/oauth/" + "authorize/?client_id=" + + TEST_CLIENT_ID + + "&redirect_uri=https%3A%2F%2Ftraffic.example.com%2F_oauth" + "&response_type=code" + "&scope=" + + TEST_ENCODED_AUTH_SCOPES + + "&state=https%3A%2F%2Ftraffic.example.com%2Ftest%3Fname%3Dadmin%26level%3Dtrace" + "&resource=oauth2-resource&resource=http%3A%2F%2Fexample.com" + "&resource=https%3A%2F%2Fexample.com%2Fsome%2Fpath%252F..%252F%2Futf8%C3%83%3Bfoo%3Dbar%" + "3Fvar1%3D1%26var2%3D2"}, + }; + + // Fail the validation to trigger the OAuth flow. + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + + // Check that the redirect includes the escaped parameter characters, '?', '&' and '='. + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&first_response_headers), true)); + + // This represents the beginning of the OAuth filter. + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(first_request_headers, false)); + + // This represents the callback request from the authorization server. + Http::TestRequestHeaderMapImpl second_request_headers{ + {Http::Headers::get().Path.get(), "/_oauth?code=123&state=https%3A%2F%2Ftraffic.example.com%" + "2Ftest%3Fname%3Dadmin%26level%3Dtrace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + // Deliberately fail the HMAC validation check. + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + + EXPECT_CALL(*oauth_client_, asyncGetAccessToken("123", TEST_CLIENT_ID, "asdf_client_secret_fdsa", + "https://traffic.example.com" + TEST_CALLBACK, + AuthType::UrlEncodedBody)); + + // Invoke the callback logic. As a side effect, state_ will be populated. + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, + filter_->decodeHeaders(second_request_headers, false)); + + EXPECT_EQ(1, config_->stats().oauth_unauthorized_rq_.value()); + EXPECT_EQ(config_->clusterName(), "auth.example.com"); + + // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. + test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); + const std::chrono::seconds expiredTime(10); + filter_->updateTokens("accessToken", "idToken", "refreshToken", expiredTime); + + // Expected response after the callback & validation is complete - verifying we kept the + // state and method of the original request, including the query string parameters. + Http::TestRequestHeaderMapImpl second_response_headers{ + {Http::Headers::get().Status.get(), "302"}, + {Http::Headers::get().SetCookie.get(), "OauthHMAC=" + "OYnODPsSGabEpZ2LAiPxyjAFgN/7/5Xg24G7jUoUbyI=;" + "version=1;path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "OauthExpires=10;version=1;path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "BearerToken=accessToken;version=1;path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "IdToken=idToken;version=1;path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "RefreshToken=refreshToken;version=1;path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().Location.get(), + "https://traffic.example.com/test?name=admin&level=trace"}, + }; + + EXPECT_CALL(decoder_callbacks_, + encodeHeaders_(HeaderMapEqualRef(&second_response_headers), true)); + + filter_->finishGetAccessTokenFlow(); +} + // This test adds %-encoded UTF-8 characters to the URL and shows that // the new decoding correctly handles that case. TEST_F(OAuth2Test, OAuthTestFullFlowPostWithParameters) { @@ -1801,6 +1930,335 @@ TEST_P(OAuth2Test, CookieValidatorInTransition) { EXPECT_TRUE(cookie_validator->hmacIsValid()); } +// - The filter receives the initial request +// - The filter redirects a user to the authorization endpoint +// - The filter receives the callback request from the authorization endpoint +// - The filter gets a bearer and refresh tokens from the authorization endpoint +// - The filter redirects a user to the user agent with actual authorization data +// - The filter receives an other request when a bearer token is expired +// - The filter tries to update a bearer token via the refresh token instead of redirect user to the +// authorization endpoint +// - The filter gets a new bearer and refresh tokens via the current refresh token +// - The filter continues to handler the request without redirection to the user agent +TEST_F(OAuth2Test, OAuthTestFullFlowWithUseRefreshToken) { + init(getConfig(true /* forward_bearer_token */, true /* use_refresh_token */)); + // First construct the initial request to the oauth filter with URI parameters. + Http::TestRequestHeaderMapImpl first_request_headers{ + {Http::Headers::get().Path.get(), "/test?name=admin&level=trace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + // This is the immediate response - a redirect to the auth cluster. + Http::TestResponseHeaderMapImpl first_response_headers{ + {Http::Headers::get().Status.get(), "302"}, + {Http::Headers::get().Location.get(), + "https://auth.example.com/oauth/" + "authorize/?client_id=" + + TEST_CLIENT_ID + + "&redirect_uri=https%3A%2F%2Ftraffic.example.com%2F_oauth" + "&response_type=code" + "&scope=" + + TEST_ENCODED_AUTH_SCOPES + + "&state=https%3A%2F%2Ftraffic.example.com%2Ftest%3Fname%3Dadmin%26level%3Dtrace" + "&resource=oauth2-resource&resource=http%3A%2F%2Fexample.com" + "&resource=https%3A%2F%2Fexample.com%2Fsome%2Fpath%252F..%252F%2Futf8%C3%83%3Bfoo%3Dbar%" + "3Fvar1%3D1%26var2%3D2"}, + }; + + // Fail the validation to trigger the OAuth flow. + + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + EXPECT_CALL(*validator_, canUpdateTokenByRefreshToken()).WillOnce(Return(false)); + + // Check that the redirect includes the escaped parameter characters, '?', '&' and '='. + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&first_response_headers), true)); + + // This represents the beginning of the OAuth filter. + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(first_request_headers, false)); + + // This represents the callback request from the authorization server. + Http::TestRequestHeaderMapImpl second_request_headers{ + {Http::Headers::get().Path.get(), "/_oauth?code=123&state=https%3A%2F%2Ftraffic.example.com%" + "2Ftest%3Fname%3Dadmin%26level%3Dtrace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + // Deliberately fail the HMAC validation check. + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + + EXPECT_CALL(*oauth_client_, asyncGetAccessToken("123", TEST_CLIENT_ID, "asdf_client_secret_fdsa", + "https://traffic.example.com" + TEST_CALLBACK, + AuthType::UrlEncodedBody)); + + // Invoke the callback logic. As a side effect, state_ will be populated. + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, + filter_->decodeHeaders(second_request_headers, false)); + + EXPECT_EQ(1, config_->stats().oauth_unauthorized_rq_.value()); + EXPECT_EQ(config_->clusterName(), "auth.example.com"); + + // Expected response after the callback & validation is complete - verifying we kept the + // state and method of the original request, including the query string parameters. + Http::TestRequestHeaderMapImpl second_response_headers{ + {Http::Headers::get().Status.get(), "302"}, + {Http::Headers::get().SetCookie.get(), "OauthHMAC=" + "fV62OgLipChTQQC3UFgDp+l5sCiSb3zt7nCoJiVivWw=;" + "version=1;path=/;Max-Age=;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "OauthExpires=;version=1;path=/;Max-Age=;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "BearerToken=;version=1;path=/;Max-Age=;secure;HttpOnly"}, + {Http::Headers::get().Location.get(), + "https://traffic.example.com/test?name=admin&level=trace"}, + }; + + EXPECT_CALL(decoder_callbacks_, + encodeHeaders_(HeaderMapEqualRef(&second_response_headers), true)); + + filter_->finishGetAccessTokenFlow(); + + // the third request to the oauth filter with URI parameters. + Http::TestRequestHeaderMapImpl third_request_headers{ + {Http::Headers::get().Path.get(), "/test?name=admin&level=trace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + std::string legit_refresh_token{"legit_refresh_token"}; + EXPECT_CALL(*validator_, refreshToken()).WillRepeatedly(ReturnRef(legit_refresh_token)); + + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + EXPECT_CALL(*validator_, canUpdateTokenByRefreshToken()).WillOnce(Return(true)); + + EXPECT_CALL(*oauth_client_, + asyncRefreshAccessToken(legit_refresh_token, TEST_CLIENT_ID, + "asdf_client_secret_fdsa", AuthType::UrlEncodedBody)); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(third_request_headers, false)); + + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + + filter_->finishRefreshAccessTokenFlow(); + EXPECT_EQ(1, config_->stats().oauth_refreshtoken_success_.value()); + EXPECT_EQ(2, config_->stats().oauth_success_.value()); +} + +TEST_F(OAuth2Test, OAuthTestRefreshAccessTokenSuccess) { + + init(getConfig(true /* forward_bearer_token */, true /* use_refresh_token */)); + // First construct the initial request to the oauth filter with URI parameters. + Http::TestRequestHeaderMapImpl first_request_headers{ + {Http::Headers::get().Path.get(), "/test?name=admin&level=trace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + std::string legit_token{"legit_token"}; + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); + + std::string legit_refresh_token{"legit_refresh_token"}; + EXPECT_CALL(*validator_, refreshToken()).WillRepeatedly(ReturnRef(legit_refresh_token)); + + // Fail the validation to trigger the OAuth flow with trying to get the access token using by + // refresh token. + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + EXPECT_CALL(*validator_, canUpdateTokenByRefreshToken()).WillOnce(Return(true)); + + EXPECT_CALL(*oauth_client_, + asyncRefreshAccessToken(legit_refresh_token, TEST_CLIENT_ID, + "asdf_client_secret_fdsa", AuthType::UrlEncodedBody)); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(first_request_headers, false)); + + Http::TestResponseHeaderMapImpl redirect_response_headers{ + {Http::Headers::get().Status.get(), "302"}, + {Http::Headers::get().Location.get(), + "https://auth.example.com/oauth/" + "authorize/?client_id=" + + TEST_CLIENT_ID + + "&redirect_uri=https%3A%2F%2Ftraffic.example.com%2F_oauth" + "&response_type=code" + "&scope=" + + TEST_ENCODED_AUTH_SCOPES + + "&state=https%3A%2F%2Ftraffic.example.com%2Ftest%3Fname%3Dadmin%26level%3Dtrace" + "&resource=oauth2-resource&resource=http%3A%2F%2Fexample.com" + "&resource=https%3A%2F%2Fexample.com"}, + }; + + // Check that the redirect includes the escaped parameter characters, '?', '&' and '='. + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + + filter_->onRefreshAccessTokenSuccess("", "", "", std::chrono::seconds(10)); + + EXPECT_EQ(1, config_->stats().oauth_refreshtoken_success_.value()); + EXPECT_EQ(1, config_->stats().oauth_success_.value()); +} + +TEST_F(OAuth2Test, OAuthTestRefreshAccessTokenFail) { + + init(getConfig(true /* forward_bearer_token */, true /* use_refresh_token */)); + // First construct the initial request to the oauth filter with URI parameters. + Http::TestRequestHeaderMapImpl first_request_headers{ + {Http::Headers::get().Path.get(), "/test?name=admin&level=trace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + std::string legit_token{"legit_token"}; + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); + + std::string legit_refresh_token{"legit_refresh_token"}; + EXPECT_CALL(*validator_, refreshToken()).WillRepeatedly(ReturnRef(legit_refresh_token)); + + // Fail the validation to trigger the OAuth flow with trying to get the access token using by + // refresh token. + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + EXPECT_CALL(*validator_, canUpdateTokenByRefreshToken()).WillOnce(Return(true)); + + EXPECT_CALL(*oauth_client_, + asyncRefreshAccessToken(legit_refresh_token, TEST_CLIENT_ID, + "asdf_client_secret_fdsa", AuthType::UrlEncodedBody)); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(first_request_headers, false)); + + Http::TestResponseHeaderMapImpl redirect_response_headers{ + {Http::Headers::get().Status.get(), "302"}, + {Http::Headers::get().Location.get(), + "https://auth.example.com/oauth/" + "authorize/?client_id=" + + TEST_CLIENT_ID + + "&redirect_uri=https%3A%2F%2Ftraffic.example.com%2F_oauth" + "&response_type=code" + "&scope=" + + TEST_ENCODED_AUTH_SCOPES + + "&state=https%3A%2F%2Ftraffic.example.com%2Ftest%3Fname%3Dadmin%26level%3Dtrace" + "&resource=oauth2-resource&resource=http%3A%2F%2Fexample.com" + "&resource=https%3A%2F%2Fexample.com%2Fsome%2Fpath%252F..%252F%2Futf8%C3%83%3Bfoo%3Dbar%" + "3Fvar1%3D1%26var2%3D2"}, + }; + + // Check that the redirect includes the escaped parameter characters, '?', '&' and '='. + EXPECT_CALL(decoder_callbacks_, + encodeHeaders_(HeaderMapEqualRef(&redirect_response_headers), true)); + + filter_->onRefreshAccessTokenFailure(); + + EXPECT_EQ(1, config_->stats().oauth_unauthorized_rq_.value()); + EXPECT_EQ(1, config_->stats().oauth_refreshtoken_failure_.value()); +} + +TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessToken) { + + init(getConfig(true /* forward_bearer_token */, true /* use_refresh_token */)); + + // the third request to the oauth filter with URI parameters. + Http::TestRequestHeaderMapImpl request_headers{ + {Http::Headers::get().Path.get(), "/test?name=admin&level=trace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + std::string legit_refresh_token{"legit_refresh_token"}; + EXPECT_CALL(*validator_, refreshToken()).WillRepeatedly(ReturnRef(legit_refresh_token)); + + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + EXPECT_CALL(*validator_, canUpdateTokenByRefreshToken()).WillOnce(Return(true)); + + EXPECT_CALL(*oauth_client_, + asyncRefreshAccessToken(legit_refresh_token, TEST_CLIENT_ID, + "asdf_client_secret_fdsa", AuthType::UrlEncodedBody)); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers, false)); + + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + + filter_->finishRefreshAccessTokenFlow(); + + Http::TestResponseHeaderMapImpl response_headers{}; + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); + + Http::TestResponseHeaderMapImpl expected_response_headers{ + {Http::Headers::get().SetCookie.get(), "OauthHMAC=" + "fV62OgLipChTQQC3UFgDp+l5sCiSb3zt7nCoJiVivWw=;" + "version=1;path=/;Max-Age=;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "OauthExpires=;version=1;path=/;Max-Age=;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "BearerToken=;version=1;path=/;Max-Age=;secure;HttpOnly"}, + }; + + EXPECT_THAT(response_headers, HeaderMapEqualRef(&expected_response_headers)); +} + +TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenWithBasicAuth) { + + init(getConfig(true /* forward_bearer_token */, true /* use_refresh_token */, + ::envoy::extensions::filters::http::oauth2::v3::OAuth2Config_AuthType:: + OAuth2Config_AuthType_BASIC_AUTH + /* authType */)); + + Http::TestRequestHeaderMapImpl request_headers{ + {Http::Headers::get().Path.get(), "/test?name=admin&level=trace"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, + {Http::Headers::get().Scheme.get(), "https"}, + }; + + std::string legit_refresh_token{"legit_refresh_token"}; + EXPECT_CALL(*validator_, refreshToken()).WillRepeatedly(ReturnRef(legit_refresh_token)); + + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + EXPECT_CALL(*validator_, canUpdateTokenByRefreshToken()).WillOnce(Return(true)); + + EXPECT_CALL(*oauth_client_, + asyncRefreshAccessToken(legit_refresh_token, TEST_CLIENT_ID, + "asdf_client_secret_fdsa", AuthType::BasicAuth)); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers, false)); + + EXPECT_CALL(decoder_callbacks_, continueDecoding()); + + filter_->finishRefreshAccessTokenFlow(); + + Http::TestResponseHeaderMapImpl response_headers{}; + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); + + Http::TestResponseHeaderMapImpl expected_response_headers{ + {Http::Headers::get().SetCookie.get(), "OauthHMAC=" + "fV62OgLipChTQQC3UFgDp+l5sCiSb3zt7nCoJiVivWw=;" + "version=1;path=/;Max-Age=;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "OauthExpires=;version=1;path=/;Max-Age=;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "BearerToken=;version=1;path=/;Max-Age=;secure;HttpOnly"}, + }; + + EXPECT_THAT(response_headers, HeaderMapEqualRef(&expected_response_headers)); +} + } // namespace Oauth2 } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/oauth2/oauth_integration_test.cc b/test/extensions/filters/http/oauth2/oauth_integration_test.cc index ce4e68dfe4db..bcef5fe19e16 100644 --- a/test/extensions/filters/http/oauth2/oauth_integration_test.cc +++ b/test/extensions/filters/http/oauth2/oauth_integration_test.cc @@ -175,6 +175,21 @@ class OauthIntegrationTest : public HttpIntegrationTest, addFakeUpstream(Http::CodecType::HTTP2); } + void cleanup() { + codec_client_->close(); + if (fake_oauth2_connection_ != nullptr) { + AssertionResult result = fake_oauth2_connection_->close(); + RELEASE_ASSERT(result, result.message()); + result = fake_oauth2_connection_->waitForDisconnect(); + RELEASE_ASSERT(result, result.message()); + } + if (fake_upstream_connection_ != nullptr) { + AssertionResult result = fake_upstream_connection_->close(); + RELEASE_ASSERT(result, result.message()); + result = fake_upstream_connection_->waitForDisconnect(); + RELEASE_ASSERT(result, result.message()); + } + } virtual void setOauthConfig() { // This config is same as when the 'auth_type: "URL_ENCODED_BODY"' is set as it's the default // value @@ -209,6 +224,7 @@ name: oauth path_config_source: path: "{{ test_tmpdir }}/hmac_secret.yaml" resource_api_version: V3 + use_refresh_token: true auth_scopes: - user - openid @@ -228,6 +244,8 @@ name: oauth Http::Utility::parseSetCookieValue(headers, default_cookie_names_.bearer_token_); std::string hmac = Http::Utility::parseSetCookieValue(headers, default_cookie_names_.oauth_hmac_); + std::string refreshToken = + Http::Utility::parseSetCookieValue(headers, default_cookie_names_.refresh_token_); Http::TestRequestHeaderMapImpl validate_headers{{":authority", std::string(host)}}; @@ -239,13 +257,17 @@ name: oauth validate_headers.addReferenceKey(Http::Headers::get().Cookie, absl::StrCat(default_cookie_names_.bearer_token_, "=", token)); + validate_headers.addReferenceKey( + Http::Headers::get().Cookie, + absl::StrCat(default_cookie_names_.refresh_token_, "=", refreshToken)); + OAuth2CookieValidator validator{api_->timeSource(), default_cookie_names_}; validator.setParams(validate_headers, std::string(hmac_secret)); return validator.isValid(); } virtual void checkClientSecretInRequest(absl::string_view token_secret) { - std::string request_body = upstream_request_->body().toString(); + std::string request_body = oauth2_request_->body().toString(); const auto query_parameters = Http::Utility::parseFromBody(request_body); auto it = query_parameters.find("client_secret"); @@ -253,6 +275,32 @@ name: oauth EXPECT_EQ(it->second, token_secret); } + void waitForOAuth2Response(absl::string_view token_secret) { + AssertionResult result = + fake_upstreams_.back()->waitForHttpConnection(*dispatcher_, fake_oauth2_connection_); + RELEASE_ASSERT(result, result.message()); + result = fake_oauth2_connection_->waitForNewStream(*dispatcher_, oauth2_request_); + RELEASE_ASSERT(result, result.message()); + result = oauth2_request_->waitForEndStream(*dispatcher_); + RELEASE_ASSERT(result, result.message()); + + ASSERT_TRUE(oauth2_request_->waitForHeadersComplete()); + + checkClientSecretInRequest(token_secret); + + oauth2_request_->encodeHeaders( + Http::TestRequestHeaderMapImpl{{":status", "200"}, {"content-type", "application/json"}}, + false); + + envoy::extensions::http_filters::oauth2::OAuthResponse oauth_response; + oauth_response.mutable_access_token()->set_value("bar"); + oauth_response.mutable_refresh_token()->set_value("foo"); + oauth_response.mutable_expires_in()->set_value(DateUtil::nowToSeconds(api_->timeSource()) + 10); + + Buffer::OwnedImpl buffer(MessageUtil::getJsonStringFromMessageOrError(oauth_response)); + oauth2_request_->encodeData(buffer, true); + } + void doAuthenticationFlow(absl::string_view token_secret, absl::string_view hmac_secret) { codec_client_ = makeHttpConnection(lookupPort("http")); @@ -268,22 +316,7 @@ name: oauth request_encoder_ = &encoder_decoder.first; auto response = std::move(encoder_decoder.second); - waitForNextUpstreamRequest(std::vector({0, 1, 2, 3})); - - ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - - checkClientSecretInRequest(token_secret); - - upstream_request_->encodeHeaders( - Http::TestRequestHeaderMapImpl{{":status", "200"}, {"content-type", "application/json"}}, - false); - - envoy::extensions::http_filters::oauth2::OAuthResponse oauth_response; - oauth_response.mutable_access_token()->set_value("bar"); - oauth_response.mutable_expires_in()->set_value(DateUtil::nowToSeconds(api_->timeSource()) + 10); - - Buffer::OwnedImpl buffer(MessageUtil::getJsonStringFromMessageOrError(oauth_response)); - upstream_request_->encodeData(buffer, true); + waitForOAuth2Response(token_secret); // We should get an immediate redirect back. response->waitForHeaders(); @@ -298,7 +331,7 @@ name: oauth response->headers(), default_cookie_names_.oauth_expires_); RELEASE_ASSERT(response->waitForEndStream(), "unexpected timeout"); - codec_client_->close(); + cleanup(); // Now try sending the cookies back codec_client_ = makeHttpConnection(lookupPort("http")); @@ -319,7 +352,43 @@ name: oauth EXPECT_EQ("http://traffic.example.com/not/_oauth", response->headers().Location()->value().getStringView()); RELEASE_ASSERT(response->waitForEndStream(), "unexpected timeout"); - codec_client_->close(); + cleanup(); + } + + void doRefreshTokenFlow(absl::string_view token_secret, absl::string_view hmac_secret) { + codec_client_ = makeHttpConnection(lookupPort("http")); + + Http::TestRequestHeaderMapImpl headers{ + {":method", "GET"}, {":path", "/request1"}, + {":scheme", "http"}, {"x-forwarded-proto", "http"}, + {":authority", "authority"}, {"Cookie", "RefreshToken=efddf321;BearerToken=ff1234fc"}, + {":authority", "authority"}, {"authority", "Bearer token"}}; + + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + + waitForOAuth2Response(token_secret); + + AssertionResult result = + fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_); + RELEASE_ASSERT(result, result.message()); + result = fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_); + RELEASE_ASSERT(result, result.message()); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request_->encodeData(response_size_, true); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + + EXPECT_TRUE( + validateHmac(response->headers(), headers.Host()->value().getStringView(), hmac_secret)); + + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_EQ(response_size_, response->body().size()); + + cleanup(); } const CookieNames default_cookie_names_{"BearerToken", "OauthHMAC", "OauthExpires", "IdToken", @@ -328,6 +397,10 @@ name: oauth std::string listener_name_{"http"}; FakeHttpConnectionPtr lds_connection_; FakeStreamPtr lds_stream_{}; + const uint64_t response_size_ = 512; + + FakeHttpConnectionPtr fake_oauth2_connection_{}; + FakeStreamPtr oauth2_request_{}; }; INSTANTIATE_TEST_SUITE_P(IpVersionsAndGrpcTypes, OauthIntegrationTest, @@ -354,6 +427,8 @@ TEST_P(OauthIntegrationTest, UnauthenticatedFlow) { // We should get an immediate redirect back. response->waitForHeaders(); EXPECT_EQ("302", response->headers().getStatusValue()); + + cleanup(); } TEST_P(OauthIntegrationTest, AuthenticationFlow) { @@ -380,6 +455,30 @@ TEST_P(OauthIntegrationTest, AuthenticationFlow) { doAuthenticationFlow("token_secret_1", "hmac_secret_1"); } +TEST_P(OauthIntegrationTest, RefreshTokenFlow) { + on_server_init_function_ = [&]() { + createLdsStream(); + sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "initial"); + }; + + initialize(); + + // 1. Do one authentication flow. + doRefreshTokenFlow("token_secret", "hmac_secret"); + + // 2. Reload secrets. + EXPECT_EQ(test_server_->counter("sds.token.update_success")->value(), 1); + EXPECT_EQ(test_server_->counter("sds.hmac.update_success")->value(), 1); + TestEnvironment::renameFile(TestEnvironment::temporaryPath("token_secret_1.yaml"), + TestEnvironment::temporaryPath("token_secret.yaml")); + test_server_->waitForCounterEq("sds.token.update_success", 2, std::chrono::milliseconds(5000)); + TestEnvironment::renameFile(TestEnvironment::temporaryPath("hmac_secret_1.yaml"), + TestEnvironment::temporaryPath("hmac_secret.yaml")); + test_server_->waitForCounterEq("sds.hmac.update_success", 2, std::chrono::milliseconds(5000)); + // 3. Do another one refresh token flow. + doRefreshTokenFlow("token_secret_1", "hmac_secret_1"); +} + // Regression test(issue #22678) where (incorrectly)using server's init manager(initialized state) // to add init target by the secret manager led to the assertion failure. TEST_P(OauthIntegrationTest, LoadListenerAfterServerIsInitialized) { @@ -481,13 +580,12 @@ name: oauth } void checkClientSecretInRequest(absl::string_view token_secret) override { - EXPECT_FALSE( - upstream_request_->headers().get(Http::CustomHeaders::get().Authorization).empty()); + EXPECT_FALSE(oauth2_request_->headers().get(Http::CustomHeaders::get().Authorization).empty()); const std::string basic_auth_token = absl::StrCat("foo:", token_secret); const std::string encoded_token = Base64::encode(basic_auth_token.data(), basic_auth_token.size()); const auto token_secret_expected = absl::StrCat("Basic ", encoded_token); - EXPECT_EQ(token_secret_expected, upstream_request_->headers() + EXPECT_EQ(token_secret_expected, oauth2_request_->headers() .get(Http::CustomHeaders::get().Authorization)[0] ->value() .getStringView()); @@ -527,6 +625,8 @@ TEST_P(OauthIntegrationTest, MissingStateParam) { // contain the state param. response->waitForHeaders(); EXPECT_EQ("401", response->headers().getStatusValue()); + + cleanup(); } } // namespace diff --git a/test/extensions/filters/http/oauth2/oauth_test.cc b/test/extensions/filters/http/oauth2/oauth_test.cc index 114d09367868..388aedf8aeb7 100644 --- a/test/extensions/filters/http/oauth2/oauth_test.cc +++ b/test/extensions/filters/http/oauth2/oauth_test.cc @@ -30,6 +30,9 @@ class MockCallbacks : public FilterCallbacks { MOCK_METHOD(void, sendUnauthorizedResponse, ()); MOCK_METHOD(void, onGetAccessTokenSuccess, (const std::string&, const std::string&, const std::string&, std::chrono::seconds)); + MOCK_METHOD(void, onRefreshAccessTokenSuccess, + (const std::string&, const std::string&, const std::string&, std::chrono::seconds)); + MOCK_METHOD(void, onRefreshAccessTokenFailure, ()); }; class OAuth2ClientTest : public testing::Test { @@ -191,6 +194,113 @@ TEST_F(OAuth2ClientTest, RequestAccessTokenInvalidResponse) { [&](auto* callback) { callback->onSuccess(request, std::move(mock_response)); })); } +TEST_F(OAuth2ClientTest, RequestRefreshAccessTokenSuccess) { + std::string json = R"EOF( + { + "access_token": "golden ticket", + "expires_in": 1000 + } + )EOF"; + Http::ResponseHeaderMapPtr mock_response_headers{new Http::TestResponseHeaderMapImpl{ + {Http::Headers::get().Status.get(), "200"}, + {Http::Headers::get().ContentType.get(), "application/json"}, + }}; + Http::ResponseMessagePtr mock_response( + new Http::ResponseMessageImpl(std::move(mock_response_headers))); + mock_response->body().add(json); + + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly(Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) + -> Http::AsyncClient::Request* { + EXPECT_EQ(Http::Headers::get().MethodValues.Post, + message->headers().Method()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().ContentTypeValues.FormUrlEncoded, + message->headers().ContentType()->value().getStringView()); + EXPECT_NE("", message->headers().getContentLengthValue()); + EXPECT_TRUE( + !message->headers().get(Http::CustomHeaders::get().Accept).empty() && + message->headers().get(Http::CustomHeaders::get().Accept)[0]->value().getStringView() == + Http::Headers::get().ContentTypeValues.Json); + callbacks_.push_back(&cb); + return &request_; + })); + + client_->setCallbacks(*mock_callbacks_); + client_->asyncRefreshAccessToken("a", "b", "c"); + EXPECT_EQ(1, callbacks_.size()); + EXPECT_CALL(*mock_callbacks_, onRefreshAccessTokenSuccess(_, _, _, _)); + Http::MockAsyncClientRequest request(&cm_.thread_local_cluster_.async_client_); + ASSERT_TRUE(popPendingCallback( + [&](auto* callback) { callback->onSuccess(request, std::move(mock_response)); })); +} + +TEST_F(OAuth2ClientTest, RequestSuccessBasicAuthType) { + std::string json = R"EOF( +{ + "access_token": "golden ticket", + "expires_in": 1000 +} +)EOF"; + Http::ResponseHeaderMapPtr mock_response_headers{new Http::TestResponseHeaderMapImpl{ + {Http::Headers::get().Status.get(), "200"}, + {Http::Headers::get().ContentType.get(), "application/json"}, + }}; + Http::ResponseMessagePtr mock_response( + new Http::ResponseMessageImpl(std::move(mock_response_headers))); + mock_response->body().add(json); + + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly(Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) + -> Http::AsyncClient::Request* { + EXPECT_EQ(Http::Headers::get().MethodValues.Post, + message->headers().Method()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().ContentTypeValues.FormUrlEncoded, + message->headers().ContentType()->value().getStringView()); + EXPECT_NE("", message->headers().getContentLengthValue()); + EXPECT_TRUE( + !message->headers().get(Http::CustomHeaders::get().Accept).empty() && + message->headers().get(Http::CustomHeaders::get().Accept)[0]->value().getStringView() == + Http::Headers::get().ContentTypeValues.Json); + callbacks_.push_back(&cb); + return &request_; + })); + + client_->setCallbacks(*mock_callbacks_); + client_->asyncRefreshAccessToken("a", "b", "c", AuthType::BasicAuth); + EXPECT_EQ(1, callbacks_.size()); + EXPECT_CALL(*mock_callbacks_, onRefreshAccessTokenSuccess(_, _, _, _)); + Http::MockAsyncClientRequest request(&cm_.thread_local_cluster_.async_client_); + ASSERT_TRUE(popPendingCallback( + [&](auto* callback) { callback->onSuccess(request, std::move(mock_response)); })); +} + +TEST_F(OAuth2ClientTest, RequestErrorResponse) { + Http::ResponseHeaderMapPtr mock_response_headers{new Http::TestResponseHeaderMapImpl{ + {Http::Headers::get().Status.get(), "500"}, + {Http::Headers::get().ContentType.get(), "application/json"}, + }}; + Http::ResponseMessagePtr mock_response( + new Http::ResponseMessageImpl(std::move(mock_response_headers))); + + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly( + Invoke([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callbacks_.push_back(&cb); + return &request_; + })); + + client_->setCallbacks(*mock_callbacks_); + client_->asyncRefreshAccessToken("a", "b", "c"); + EXPECT_EQ(1, callbacks_.size()); + EXPECT_CALL(*mock_callbacks_, onRefreshAccessTokenFailure()); + Http::MockAsyncClientRequest request(&cm_.thread_local_cluster_.async_client_); + ASSERT_TRUE(popPendingCallback( + [&](auto* callback) { callback->onSuccess(request, std::move(mock_response)); })); +} + TEST_F(OAuth2ClientTest, NetworkError) { EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) .WillRepeatedly( @@ -211,6 +321,48 @@ TEST_F(OAuth2ClientTest, NetworkError) { })); } +TEST_F(OAuth2ClientTest, UpdateTokenNetworkError) { + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly( + Invoke([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callbacks_.push_back(&cb); + return &request_; + })); + + client_->setCallbacks(*mock_callbacks_); + client_->asyncRefreshAccessToken("a", "b", "c"); + EXPECT_EQ(1, callbacks_.size()); + + EXPECT_CALL(*mock_callbacks_, onRefreshAccessTokenFailure()); + Http::MockAsyncClientRequest request(&cm_.thread_local_cluster_.async_client_); + ASSERT_TRUE(popPendingCallback([&](auto* callback) { + callback->onFailure(request, Http::AsyncClient::FailureReason::Reset); + })); +} + +TEST_F(OAuth2ClientTest, NetworkErrorDoubleCallStateInvalid) { + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly( + Invoke([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callbacks_.push_back(&cb); + return &request_; + })); + + client_->setCallbacks(*mock_callbacks_); + client_->asyncRefreshAccessToken("a", "b", "c"); + EXPECT_EQ(1, callbacks_.size()); + + EXPECT_CALL(*mock_callbacks_, onRefreshAccessTokenFailure()); + Http::MockAsyncClientRequest request(&cm_.thread_local_cluster_.async_client_); + ASSERT_TRUE(popPendingCallback([&](auto* callback) { + callback->onFailure(request, Http::AsyncClient::FailureReason::Reset); + EXPECT_DEATH(callback->onFailure(request, Http::AsyncClient::FailureReason::Reset), + "Malformed oauth client state"); + })); +} + TEST_F(OAuth2ClientTest, NoCluster) { ON_CALL(cm_, getThreadLocalCluster("auth")).WillByDefault(Return(nullptr)); client_->setCallbacks(*mock_callbacks_); diff --git a/test/extensions/filters/udp/udp_proxy/BUILD b/test/extensions/filters/udp/udp_proxy/BUILD index 9b640bf83374..3491199c7f94 100644 --- a/test/extensions/filters/udp/udp_proxy/BUILD +++ b/test/extensions/filters/udp/udp_proxy/BUILD @@ -34,6 +34,7 @@ envoy_extension_cc_test( deps = [ ":mocks", "//source/common/common:hash_lib", + "//source/common/router:string_accessor_lib", "//source/common/stream_info:uint32_accessor_lib", "//source/extensions/access_loggers/file:config", "//source/extensions/filters/udp/udp_proxy:config", diff --git a/test/extensions/filters/udp/udp_proxy/hash_policy_impl_test.cc b/test/extensions/filters/udp/udp_proxy/hash_policy_impl_test.cc index 147b4aab2578..5453248fdcfd 100644 --- a/test/extensions/filters/udp/udp_proxy/hash_policy_impl_test.cc +++ b/test/extensions/filters/udp/udp_proxy/hash_policy_impl_test.cc @@ -6,6 +6,8 @@ #include "source/common/network/utility.h" #include "source/extensions/filters/udp/udp_proxy/hash_policy_impl.h" +#include "test/test_common/utility.h" + #include "gtest/gtest.h" namespace Envoy { diff --git a/test/extensions/filters/udp/udp_proxy/mocks.h b/test/extensions/filters/udp/udp_proxy/mocks.h index 832dfbe51d3f..ce23c9374e1a 100644 --- a/test/extensions/filters/udp/udp_proxy/mocks.h +++ b/test/extensions/filters/udp/udp_proxy/mocks.h @@ -54,6 +54,10 @@ class MockUdpTunnelingConfig : public UdpTunnelingConfig { MOCK_METHOD(bool, usePost, (), (const)); MOCK_METHOD(const std::string&, postPath, (), (const)); MOCK_METHOD(Http::HeaderEvaluator&, headerEvaluator, (), (const)); + MOCK_METHOD(uint32_t, maxConnectAttempts, (), (const)); + MOCK_METHOD(bool, bufferEnabled, (), (const)); + MOCK_METHOD(uint32_t, maxBufferedDatagrams, (), (const)); + MOCK_METHOD(uint64_t, maxBufferedBytes, (), (const)); std::string default_proxy_host_ = "default.host.com"; std::string default_target_host_ = "default.target.host"; diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/BUILD b/test/extensions/filters/udp/udp_proxy/session_filters/BUILD index 1419e3fe27c4..a2bb0d9f1527 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/BUILD +++ b/test/extensions/filters/udp/udp_proxy/session_filters/BUILD @@ -52,6 +52,7 @@ envoy_cc_test_library( deps = [ ":buffer_filter_proto_cc_proto", "//envoy/registry", + "//source/common/router:string_accessor_lib", "//source/extensions/filters/udp/udp_proxy/session_filters:factory_base_lib", "//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface", "//test/test_common:utility_lib", diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h b/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h index e34f431b4e16..8f32cb16b51c 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h +++ b/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h @@ -5,6 +5,7 @@ #include "envoy/registry/registry.h" #include "source/common/config/utility.h" +#include "source/common/router/string_accessor_impl.h" #include "source/extensions/filters/udp/udp_proxy/session_filters/factory_base.h" #include "source/extensions/filters/udp/udp_proxy/session_filters/filter.h" @@ -33,10 +34,18 @@ class BufferingSessionFilter : public Filter { void initializeReadFilterCallbacks(ReadFilterCallbacks& callbacks) override { read_callbacks_ = &callbacks; + // Verify that the filter is able to access the stream info. + callbacks.streamInfo().filterState()->setData( + "test.read", std::make_shared("val"), + Envoy::StreamInfo::FilterState::StateType::Mutable); } void initializeWriteFilterCallbacks(WriteFilterCallbacks& callbacks) override { write_callbacks_ = &callbacks; + // Verify that the filter is able to access the stream info. + callbacks.streamInfo().filterState()->setData( + "test.write", std::make_shared("val"), + Envoy::StreamInfo::FilterState::StateType::Mutable); } ReadFilterStatus onNewSession() override { return ReadFilterStatus::Continue; } 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 bc8228279252..6dc8ff10d6f7 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 @@ -7,6 +7,7 @@ #include "source/common/common/hash.h" #include "source/common/network/socket_impl.h" #include "source/common/network/socket_option_impl.h" +#include "source/common/router/string_accessor_impl.h" #include "source/common/stream_info/uint32_accessor_impl.h" #include "source/extensions/filters/udp/udp_proxy/config.h" #include "source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h" @@ -15,6 +16,7 @@ #include "test/mocks/api/mocks.h" #include "test/mocks/http/stream_encoder.h" #include "test/mocks/network/socket.h" +#include "test/mocks/server/factory_context.h" #include "test/mocks/server/listener_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/cluster_update_callbacks.h" @@ -966,6 +968,28 @@ use_per_packet_load_balancing: true "Only one of use_per_packet_load_balancing or session_filters can be used."); } +TEST_F(UdpProxyFilterTest, MutualExcludePerPacketLoadBalancingAndTunneling) { + auto config = R"EOF( +stat_prefix: foo +matcher: + on_no_match: + action: + name: route + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: fake_cluster +use_per_packet_load_balancing: true +tunneling_config: + proxy_host: host.com + target_host: host.com + default_target_port: 30 + )EOF"; + + EXPECT_THROW_WITH_MESSAGE( + setup(readConfig(config)), EnvoyException, + "Only one of use_per_packet_load_balancing or tunneling_config can be used."); +} + // Verify that on second data packet sent from the client, another upstream host is selected. TEST_F(UdpProxyFilterTest, PerPacketLoadBalancingBasicFlow) { InSequence s; @@ -1468,10 +1492,15 @@ class HttpUpstreamImplTest : public testing::Test { absl::optional opt_authority = absl::nullopt, absl::optional opt_path = absl::nullopt, absl::optional header_to_add = absl::nullopt) { + // In case connect-udp is used, Envoy expect the H2 headers to be normalized with H1, + // so expect that the request headers here match H1 headers, even though + // eventually H2 headers will be sent. When the headers are normalized to H1, the method + // is replaced with GET, a header with the 'upgrade' key is added with 'connect-udp' + // value and a header with the 'connection' key is added with 'Upgrade' value. std::string scheme = is_ssl ? "https" : "http"; std::string authority = opt_authority.has_value() ? opt_authority.value() : "default.host.com:10"; - std::string method = use_post ? "POST" : "CONNECT"; + std::string method = use_post ? "POST" : "GET"; std::string path = opt_path.has_value() ? opt_path.value() : "/.well-known/masque/udp/default.target.host/20/"; @@ -1480,7 +1509,8 @@ class HttpUpstreamImplTest : public testing::Test { if (!use_post) { headers.addCopy("capsule-protocol", "?1"); - headers.addCopy(":protocol", "connect-udp"); + headers.addCopy("upgrade", "connect-udp"); + headers.addCopy("connection", "Upgrade"); } if (header_to_add) { @@ -1626,7 +1656,7 @@ TEST_F(HttpUpstreamImplTest, FailureResponseHeadersEndStream) { EXPECT_CALL(creation_callbacks_, onStreamFailure()); Http::ResponseHeaderMapPtr response_headers( - new Http::TestResponseHeaderMapImpl{{":status", "200"}, {"Capsule-Protocol", "?1"}}); + new Http::TestResponseHeaderMapImpl{{":status", "101"}, {"upgrade", "connect-udp"}}); upstream_->responseDecoder().decodeHeaders(std::move(response_headers), /*end_stream=*/true); } @@ -1638,7 +1668,7 @@ TEST_F(HttpUpstreamImplTest, SuccessResponseHeaders) { EXPECT_CALL(creation_callbacks_, onStreamSuccess(_)); Http::ResponseHeaderMapPtr response_headers( - new Http::TestResponseHeaderMapImpl{{":status", "200"}, {"Capsule-Protocol", "?1"}}); + new Http::TestResponseHeaderMapImpl{{":status", "101"}, {"upgrade", "connect-udp"}}); upstream_->responseDecoder().decodeHeaders(std::move(response_headers), /*end_stream=*/false); } @@ -1665,7 +1695,7 @@ TEST_F(HttpUpstreamImplTest, DecodeDataAfterSuccessHeaders) { Buffer::OwnedImpl data; Http::ResponseHeaderMapPtr response_headers( - new Http::TestResponseHeaderMapImpl{{":status", "200"}, {"Capsule-Protocol", "?1"}}); + new Http::TestResponseHeaderMapImpl{{":status", "101"}, {"upgrade", "connect-udp"}}); EXPECT_CALL(creation_callbacks_, onStreamFailure()).Times(0); EXPECT_CALL(creation_callbacks_, onStreamSuccess(_)); @@ -1692,7 +1722,7 @@ TEST_F(HttpUpstreamImplTest, DecodeTrailersAfterSuccessHeaders) { setAndExpectRequestEncoder(expectedHeaders()); Http::ResponseHeaderMapPtr response_headers( - new Http::TestResponseHeaderMapImpl{{":status", "200"}, {"Capsule-Protocol", "?1"}}); + new Http::TestResponseHeaderMapImpl{{":status", "101"}, {"upgrade", "connect-udp"}}); EXPECT_CALL(creation_callbacks_, onStreamFailure()).Times(0); EXPECT_CALL(creation_callbacks_, onStreamSuccess(_)); @@ -1859,6 +1889,164 @@ TEST_F(TunnelingConnectionPoolImplTest, FactoryTest) { EXPECT_TRUE(invalid_pool == nullptr); } +TEST(TunnelingConfigImplTest, DefaultConfigs) { + NiceMock context; + TunnelingConfig proto_config; + proto_config.set_use_post(true); + TunnelingConfigImpl config(proto_config, context); + + EXPECT_EQ(1, config.maxConnectAttempts()); + EXPECT_EQ(1024, config.maxBufferedDatagrams()); + EXPECT_EQ(16384, config.maxBufferedBytes()); +} + +TEST(TunnelingConfigImplTest, DefaultPostPath) { + NiceMock context; + TunnelingConfig proto_config; + proto_config.set_use_post(true); + TunnelingConfigImpl config(proto_config, context); + + EXPECT_EQ("/", config.postPath()); +} + +TEST(TunnelingConfigImplTest, PostPathWithoutPostMethod) { + NiceMock context; + TunnelingConfig proto_config; + proto_config.set_post_path("/path"); + EXPECT_THROW_WITH_MESSAGE(TunnelingConfigImpl(proto_config, context), EnvoyException, + "Can't set a post path when POST method isn't used"); +} + +TEST(TunnelingConfigImplTest, PostWithInvalidPath) { + NiceMock context; + TunnelingConfig proto_config; + proto_config.set_use_post(true); + proto_config.set_post_path("path"); + EXPECT_THROW_WITH_MESSAGE(TunnelingConfigImpl(proto_config, context), EnvoyException, + "Path must start with '/'"); +} + +TEST(TunnelingConfigImplTest, ValidProxyPort) { + NiceMock context; + TunnelingConfig proto_config; + proto_config.mutable_proxy_port()->set_value(443); + TunnelingConfigImpl config(proto_config, context); + EXPECT_EQ(443, config.proxyPort()); +} + +TEST(TunnelingConfigImplTest, ProxyPortOutOfRange) { + NiceMock context; + + { + TunnelingConfig proto_config; + proto_config.mutable_proxy_port()->set_value(0); + EXPECT_THROW_WITH_MESSAGE(TunnelingConfigImpl(proto_config, context), EnvoyException, + "Port value not in range"); + } + { + TunnelingConfig proto_config; + proto_config.mutable_proxy_port()->set_value(65536); + EXPECT_THROW_WITH_MESSAGE(TunnelingConfigImpl(proto_config, context), EnvoyException, + "Port value not in range"); + } +} + +TEST(TunnelingConfigImplTest, InvalidHeadersToAdd) { + NiceMock context; + + { + TunnelingConfig proto_config; + auto* header_to_add = proto_config.add_headers_to_add(); + auto* header = header_to_add->mutable_header(); + // Can't add pseudo header. + header->set_key(":method"); + header->set_value("GET"); + EXPECT_THROW_WITH_MESSAGE(TunnelingConfigImpl(proto_config, context), EnvoyException, + ":-prefixed or host headers may not be modified"); + } + + { + TunnelingConfig proto_config; + auto* header_to_add = proto_config.add_headers_to_add(); + auto* header = header_to_add->mutable_header(); + // Can't modify host. + header->set_key("host"); + header->set_value("example.net:80"); + EXPECT_THROW_WITH_MESSAGE(TunnelingConfigImpl(proto_config, context), EnvoyException, + ":-prefixed or host headers may not be modified"); + } +} + +TEST(TunnelingConfigImplTest, HeadersToAdd) { + NiceMock context; + NiceMock stream_info; + + stream_info.filterState()->setData( + "test_key", std::make_shared("test_val"), + Envoy::StreamInfo::FilterState::StateType::Mutable); + + TunnelingConfig proto_config; + auto* header_to_add = proto_config.add_headers_to_add(); + auto* header = header_to_add->mutable_header(); + header->set_key("test_key"); + header->set_value("%FILTER_STATE(test_key:PLAIN)%"); + TunnelingConfigImpl config(proto_config, context); + + auto headers = Http::TestRequestHeaderMapImpl{{":scheme", "http"}, {":authority", "host.com"}}; + config.headerEvaluator().evaluateHeaders( + headers, *Http::StaticEmptyHeaders::get().request_headers, + *Http::StaticEmptyHeaders::get().response_headers, stream_info); + EXPECT_EQ("test_val", headers.getByKey("test_key")); +} + +TEST(TunnelingConfigImplTest, ProxyHostFromFilterState) { + NiceMock context; + NiceMock stream_info; + + stream_info.filterState()->setData( + "test-proxy-host", std::make_shared("test.host.com"), + Envoy::StreamInfo::FilterState::StateType::Mutable); + + TunnelingConfig proto_config; + proto_config.set_proxy_host("%FILTER_STATE(test-proxy-host:PLAIN)%"); + TunnelingConfigImpl config(proto_config, context); + + EXPECT_EQ("test.host.com", config.proxyHost(stream_info)); +} + +TEST(TunnelingConfigImplTest, TargetHostFromFilterState) { + NiceMock context; + NiceMock stream_info; + + stream_info.filterState()->setData( + "test-proxy-host", std::make_shared("test.host.com"), + Envoy::StreamInfo::FilterState::StateType::Mutable); + + TunnelingConfig proto_config; + proto_config.set_target_host("%FILTER_STATE(test-proxy-host:PLAIN)%"); + TunnelingConfigImpl config(proto_config, context); + + EXPECT_EQ("test.host.com", config.targetHost(stream_info)); +} + +TEST(TunnelingConfigImplTest, BufferingState) { + NiceMock context; + + { + TunnelingConfig proto_config; + TunnelingConfigImpl config(proto_config, context); + // Buffering should be disabled by default. + EXPECT_FALSE(config.bufferEnabled()); + } + + { + TunnelingConfig proto_config; + proto_config.mutable_buffer_options(); // Will set buffering. + TunnelingConfigImpl config(proto_config, context); + EXPECT_TRUE(config.bufferEnabled()); + } +} + } // namespace } // namespace UdpProxy } // namespace UdpFilters diff --git a/test/extensions/upstreams/tcp/generic/config_test.cc b/test/extensions/upstreams/tcp/generic/config_test.cc index 10dcc66b126e..f2c62b449174 100644 --- a/test/extensions/upstreams/tcp/generic/config_test.cc +++ b/test/extensions/upstreams/tcp/generic/config_test.cc @@ -113,6 +113,15 @@ TEST_F(TcpConnPoolTest, Http3Config) { &lb_context_, callbacks_, downstream_stream_info_)); } +TEST(DisableTunnelingObjectFactory, CreateFromBytes) { + auto* factory = Registry::FactoryRegistry::getFactory( + TcpProxy::DisableTunnelingFilterStateKey); + ASSERT_NE(nullptr, factory); + auto object = factory->createFromBytes("true"); + ASSERT_NE(nullptr, object); + EXPECT_EQ(true, dynamic_cast(object.get())->value()); +} + } // namespace Generic } // namespace Tcp } // namespace Upstreams diff --git a/test/integration/BUILD b/test/integration/BUILD index e8b4e8585299..0c127d36bbe9 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -738,6 +738,7 @@ envoy_cc_test( deps = [ ":http_integration_lib", ":http_timeout_integration_test_lib", + "//test/test_common:test_runtime_lib", ], ) @@ -1826,10 +1827,13 @@ envoy_cc_test( deps = [ ":http_integration_lib", ":http_protocol_integration_lib", + "//source/extensions/filters/udp/udp_proxy:config", + "//source/extensions/filters/udp/udp_proxy/session_filters/http_capsule:config", "//source/extensions/upstreams/http/udp:config", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/udp/udp_proxy/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/upstreams/http/udp/v3:pkg_cc_proto", ], ) diff --git a/test/integration/http_protocol_integration.h b/test/integration/http_protocol_integration.h index 72faeae639a7..5988e1ecc061 100644 --- a/test/integration/http_protocol_integration.h +++ b/test/integration/http_protocol_integration.h @@ -70,10 +70,11 @@ class HttpProtocolIntegrationTest : public testing::TestWithParam& p); HttpProtocolIntegrationTest() - : HttpIntegrationTest( - GetParam().downstream_protocol, GetParam().version, - ConfigHelper::httpProxyConfig(/*downstream_is_quic=*/GetParam().downstream_protocol == - Http::CodecType::HTTP3)), + : HttpProtocolIntegrationTest(ConfigHelper::httpProxyConfig( + /*downstream_is_quic=*/GetParam().downstream_protocol == Http::CodecType::HTTP3)) {} + + HttpProtocolIntegrationTest(const std::string config) + : HttpIntegrationTest(GetParam().downstream_protocol, GetParam().version, config), use_universal_header_validator_(GetParam().use_universal_header_validator) { setupHttp1ImplOverrides(GetParam().http1_implementation); setupHttp2ImplOverrides(GetParam().http2_implementation); diff --git a/test/integration/http_timeout_integration_test.cc b/test/integration/http_timeout_integration_test.cc index 50c6726395c3..74830d697d03 100644 --- a/test/integration/http_timeout_integration_test.cc +++ b/test/integration/http_timeout_integration_test.cc @@ -1,5 +1,7 @@ #include "test/integration/http_timeout_integration_test.h" +#include "test/test_common/test_runtime.h" + #include "gtest/gtest.h" namespace Envoy { @@ -16,6 +18,10 @@ TEST_P(HttpTimeoutIntegrationTest, GlobalTimeout) { config_helper_.addConfigModifier(configureProxyStatus()); initialize(); + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); auto encoder_decoder = codec_client_->startRequest( Http::TestRequestHeaderMapImpl{{":method", "POST"}, @@ -49,7 +55,7 @@ TEST_P(HttpTimeoutIntegrationTest, GlobalTimeout) { EXPECT_TRUE(response->complete()); EXPECT_EQ("504", response->headers().getStatusValue()); EXPECT_EQ(response->headers().getProxyStatusValue(), - "envoy; error=connection_timeout; details=\"response_timeout; UT\""); + "envoy; error=http_response_timeout; details=\"response_timeout; UT\""); } // Testing that `x-envoy-expected-timeout-ms` header, set by egress envoy, is respected by ingress diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 641bc37e7804..a8706b0cfe9e 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -716,6 +716,11 @@ TEST_P(QuicHttpIntegrationTest, PortMigration) { initialize(); uint32_t old_port = lookupPort("http"); codec_client_ = makeHttpConnection(old_port); + + // Verify the default path degrading timeout is set correctly. + EXPECT_EQ(4u, quic::test::QuicSentPacketManagerPeer::GetNumPtosForPathDegrading( + &quic_connection_->sent_packet_manager())); + auto encoder_decoder = codec_client_->startRequest(Http::TestRequestHeaderMapImpl{{":method", "POST"}, {":path", "/test/long/url"}, @@ -1176,6 +1181,8 @@ TEST_P(QuicHttpIntegrationTest, MultipleNetworkFilters) { } TEST_P(QuicHttpIntegrationTest, DeferredLogging) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_defer_logging_to_ack_listener", + "true"); useAccessLog( "%PROTOCOL%,%ROUNDTRIP_DURATION%,%REQUEST_DURATION%,%RESPONSE_DURATION%,%RESPONSE_" "CODE%,%BYTES_RECEIVED%,%ROUTE_NAME%,%VIRTUAL_CLUSTER_NAME%,%RESPONSE_CODE_DETAILS%,%" @@ -1243,6 +1250,8 @@ TEST_P(QuicHttpIntegrationTest, DeferredLoggingDisabled) { } TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithReset) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_defer_logging_to_ack_listener", + "true"); useAccessLog( "%PROTOCOL%,%ROUNDTRIP_DURATION%,%REQUEST_DURATION%,%RESPONSE_DURATION%,%RESPONSE_" "CODE%,%BYTES_RECEIVED%,%ROUTE_NAME%,%VIRTUAL_CLUSTER_NAME%,%RESPONSE_CODE_DETAILS%,%" @@ -1271,6 +1280,8 @@ TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithReset) { } TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithQuicReset) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_defer_logging_to_ack_listener", + "true"); useAccessLog( "%PROTOCOL%,%ROUNDTRIP_DURATION%,%REQUEST_DURATION%,%RESPONSE_DURATION%,%RESPONSE_" "CODE%,%BYTES_RECEIVED%,%ROUTE_NAME%,%VIRTUAL_CLUSTER_NAME%,%RESPONSE_CODE_DETAILS%,%" @@ -1340,6 +1351,8 @@ TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithEnvoyReset) { } TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithInternalRedirect) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_defer_logging_to_ack_listener", + "true"); useAccessLog( "%PROTOCOL%,%ROUNDTRIP_DURATION%,%REQUEST_DURATION%,%RESPONSE_DURATION%,%RESPONSE_" "CODE%,%BYTES_RECEIVED%,%ROUTE_NAME%,%VIRTUAL_CLUSTER_NAME%,%RESPONSE_CODE_DETAILS%,%" @@ -1418,6 +1431,8 @@ TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithInternalRedirect) { } TEST_P(QuicHttpIntegrationTest, DeferredLoggingWithRetransmission) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_defer_logging_to_ack_listener", + "true"); useAccessLog("%BYTES_RETRANSMITTED%,%PACKETS_RETRANSMITTED%"); initialize(); diff --git a/test/integration/udp_tunneling_integration_test.cc b/test/integration/udp_tunneling_integration_test.cc index e657f5fd065b..99249f6f5e3b 100644 --- a/test/integration/udp_tunneling_integration_test.cc +++ b/test/integration/udp_tunneling_integration_test.cc @@ -3,6 +3,7 @@ #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/config/core/v3/proxy_protocol.pb.h" #include "envoy/extensions/access_loggers/file/v3/file.pb.h" +#include "envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.pb.h" #include "envoy/extensions/upstreams/http/udp/v3/udp_connection_pool.pb.h" #include "test/integration/http_integration.h" @@ -329,5 +330,392 @@ TEST_P(ForwardingConnectUdpIntegrationTest, DoNotForwardNonConnectUdp) { cleanupUpstreamAndDownstream(); } +// Tunneling downstream UDP over an upstream HTTP CONNECT tunnel. +class UdpTunnelingIntegrationTest : public HttpProtocolIntegrationTest { +public: + UdpTunnelingIntegrationTest() + : HttpProtocolIntegrationTest(ConfigHelper::baseUdpListenerConfig()) {} + + struct BufferOptions { + uint32_t max_buffered_datagrams_; + uint32_t max_buffered_bytes_; + }; + + struct TestConfig { + std::string proxy_host_; + std::string target_host_; + uint32_t max_connect_attempts_; + uint32_t default_target_port_; + bool use_post_; + std::string post_path_; + absl::optional buffer_options_; + absl::optional idle_timeout_; + }; + + void setup(const TestConfig& config) { + config_ = config; + + std::string filter_config = + fmt::format(R"EOF( +name: udp_proxy +typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig + stat_prefix: foo + matcher: + on_no_match: + action: + name: route + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route + cluster: cluster_0 + session_filters: + - name: http_capsule + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.session.http_capsule.v3.FilterConfig + tunneling_config: + proxy_host: {} + target_host: {} + default_target_port: {} + retry_options: + max_connect_attempts: {} +)EOF", + config.proxy_host_, config.target_host_, config.default_target_port_, + config.max_connect_attempts_); + + if (config.buffer_options_.has_value()) { + filter_config += fmt::format(R"EOF( + buffer_options: + max_buffered_datagrams: {} + max_buffered_bytes: {} +)EOF", + config.buffer_options_.value().max_buffered_datagrams_, + config.buffer_options_.value().max_buffered_bytes_); + } + + if (config.use_post_) { + filter_config += fmt::format(R"EOF( + use_post: true + post_path: {} +)EOF", + config.post_path_); + } + + if (config.idle_timeout_.has_value()) { + filter_config += fmt::format(R"EOF( + idle_timeout: {} +)EOF", + config.idle_timeout_.value()); + } + + config_helper_.renameListener("udp_proxy"); + config_helper_.addConfigModifier( + [filter_config](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + auto* filter = listener->add_listener_filters(); + TestUtility::loadFromYaml(filter_config, *filter); + }); + + HttpIntegrationTest::initialize(); + + const uint32_t port = lookupPort("udp_proxy"); + listener_address_ = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + + client_ = std::make_unique(version_); + }; + + const std::string encapsulate(std::string datagram) { + uint8_t capsule_length = datagram.length() + 1; + return absl::HexStringToBytes(absl::StrCat("00", Hex::encode(&capsule_length, 1), "00")) + + datagram; + } + + const std::string expectedCapsules(std::vector raw_datagrams) { + std::string expected_capsules = ""; + for (std::string datagram : raw_datagrams) { + expected_capsules += encapsulate(datagram); + } + + return expected_capsules; + } + + void expectPostRequestHeaders(const Http::RequestHeaderMap& headers) { + EXPECT_EQ(headers.getMethodValue(), "POST"); + EXPECT_EQ(headers.getPathValue(), config_.post_path_); + } + + void expectConnectRequestHeaders(const Http::RequestHeaderMap& original_headers) { + Http::TestRequestHeaderMapImpl headers(original_headers); + + // For connect-udp case, the server codec will transform the H2 headers to H1. For test + // convenience, transforming the H1 headers back to H2. + Http::Utility::transformUpgradeRequestFromH1toH2(headers); + std::string expected_path = absl::StrCat("/.well-known/masque/udp/", config_.target_host_, "/", + config_.default_target_port_, "/"); + + EXPECT_EQ(headers.getMethodValue(), "CONNECT"); + EXPECT_EQ(headers.getPathValue(), expected_path); + } + + void expectRequestHeaders(const Http::RequestHeaderMap& headers) { + if (config_.use_post_) { + expectPostRequestHeaders(headers); + } else { + expectConnectRequestHeaders(headers); + } + } + + void establishConnection(const std::string initial_datagram, int tunnels_count = 1) { + // Initial datagram will create a session and a tunnel request. + client_->write(initial_datagram, *listener_address_); + + if (!fake_upstream_connection_) { + ASSERT_TRUE( + fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + } + + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + expectRequestHeaders(upstream_request_->headers()); + + // Send upgrade headers downstream, fully establishing the connection. + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, + {"capsule-protocol", "?1"}}; + upstream_request_->encodeHeaders(response_headers, false); + + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", tunnels_count); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + } + + void sendCapsuleDownstream(const std::string datagram, bool end_stream = false) { + // Send data from upstream to downstream. + upstream_request_->encodeData(encapsulate(datagram), end_stream); + Network::UdpRecvData response_datagram; + client_->recv(response_datagram); + EXPECT_EQ(datagram, response_datagram.buffer_->toString()); + } + + TestConfig config_; + Network::Address::InstanceConstSharedPtr listener_address_; + std::unique_ptr client_; +}; + +TEST_P(UdpTunnelingIntegrationTest, BasicFlowWithBuffering) { + TestConfig config{"host.com", "target.com", 1, 30, false, "", + BufferOptions{1, 30}, absl::nullopt}; + setup(config); + + const std::string datagram1 = "hello"; + establishConnection(datagram1); + // Wait for buffered datagram. + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram1}))); + + const std::string datagram2 = "hello2"; + client_->write(datagram2, *listener_address_); + ASSERT_TRUE( + upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram1, datagram2}))); + + sendCapsuleDownstream("response1", false); + sendCapsuleDownstream("response2", true); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, BasicFlowNoBuffering) { + TestConfig config{"host.com", "target.com", 1, 30, false, "", absl::nullopt, absl::nullopt}; + setup(config); + + establishConnection("hello"); + // Since there's no buffering, the first datagram is dropped. Send another datagram and expect + // that it's the only datagram received upstream. + const std::string datagram2 = "hello2"; + client_->write(datagram2, *listener_address_); + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram2}))); + + sendCapsuleDownstream("response", true); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, BasicFlowWithPost) { + TestConfig config{"host.com", "target.com", 1, 30, true, "/post/path", + BufferOptions{1, 30}, absl::nullopt}; + setup(config); + + const std::string datagram1 = "hello"; + establishConnection(datagram1); + // Wait for buffered datagram. + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram1}))); + sendCapsuleDownstream("response", true); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, TwoConsecutiveDownstreamSessions) { + TestConfig config{"host.com", "target.com", 1, 30, false, "", + BufferOptions{1, 30}, absl::nullopt}; + setup(config); + + establishConnection("hello1"); + sendCapsuleDownstream("response2", true); // Will end first session. + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + establishConnection("hello2", 2); // Will create another session. + sendCapsuleDownstream("response2", true); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, IdleTimeout) { + TestConfig config{"host.com", "target.com", 1, 30, false, "", BufferOptions{1, 30}, "0.5s"}; + setup(config); + + const std::string datagram = "hello"; + establishConnection(datagram); + // Wait for buffered datagram. + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram}))); + + sendCapsuleDownstream("response1", false); + test_server_->waitForCounterEq("udp.foo.idle_timeout", 1); + ASSERT_TRUE(upstream_request_->waitForReset()); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, BufferOverflowDueToCapacity) { + TestConfig config{"host.com", "target.com", 1, 30, false, "", + BufferOptions{1, 30}, absl::nullopt}; + setup(config); + + // Send two datagrams before the upstream is established. Since the buffer capacity is 1 datagram, + // we expect the second one to be dropped. + client_->write("hello1", *listener_address_); + client_->write("hello2", *listener_address_); + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", 1); + + // "hello3" will drop because it's sent before the tunnel is established, and the buffer is full. + establishConnection("hello3"); + // Wait for the buffered datagram. + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({"hello1"}))); + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", 2); + + sendCapsuleDownstream("response", true); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, BufferOverflowDueToSize) { + TestConfig config{"host.com", "target.com", 1, 30, false, "", BufferOptions{100, 15}, + absl::nullopt}; + setup(config); + + // Send two datagrams before the upstream is established. Since the buffer capacity is 6 bytes, + // we expect the second one to be dropped. + client_->write("hello1", *listener_address_); + client_->write("hello2", *listener_address_); + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", 1); + + // "hello3" will drop because it's sent before the tunnel is established, and the buffer is full. + establishConnection("hello3"); + // Wait for the buffered datagram. + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({"hello1"}))); + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", 2); + + sendCapsuleDownstream("response", true); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, ConnectionReuse) { + TestConfig config{"host.com", "target.com", 1, 30, false, "", BufferOptions{100, 300}, + absl::nullopt}; + setup(config); + + // Establish connection for first session. + establishConnection("hello_1"); + + // Establish connection for second session. + Network::Test::UdpSyncPeer client2(version_); + client2.write("hello_2", *listener_address_); + + FakeStreamPtr upstream_request2; + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request2)); + ASSERT_TRUE(upstream_request2->waitForHeadersComplete()); + expectRequestHeaders(upstream_request2->headers()); + + // Send upgrade headers downstream, fully establishing the connection. + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"capsule-protocol", "?1"}}; + upstream_request2->encodeHeaders(response_headers, false); + + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 2); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 2); + + // Wait for buffered datagram for each stream. + ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({"hello_1"}))); + ASSERT_TRUE(upstream_request2->waitForData(*dispatcher_, expectedCapsules({"hello_2"}))); + + // Send capsule from upstream over the first stream, and close it. + sendCapsuleDownstream("response_1", true); + // First stream is closed so we expect active sessions to decrease to 1. + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + + // Send capsule from upstream over the second stream, and close it. + upstream_request2->encodeData(encapsulate("response_2"), true); + Network::UdpRecvData response_datagram; + client2.recv(response_datagram); + EXPECT_EQ("response_2", response_datagram.buffer_->toString()); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, FailureOnBadResponseHeaders) { + TestConfig config{"host.com", "target.com", 1, 30, false, "", + BufferOptions{1, 30}, absl::nullopt}; + setup(config); + + // Initial datagram will create a session and a tunnel request. + client_->write("hello", *listener_address_); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + expectRequestHeaders(upstream_request_->headers()); + + Http::TestResponseHeaderMapImpl response_headers{{":status", "404"}}; + upstream_request_->encodeHeaders(response_headers, true); + + test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_connect_attempts_exceeded", 1); + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_failure", 1); + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 0); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +TEST_P(UdpTunnelingIntegrationTest, ConnectionAttemptRetry) { + TestConfig config{"host.com", "target.com", 2, 30, false, "", + BufferOptions{1, 30}, absl::nullopt}; + setup(config); + + // Initial datagram will create a session and a tunnel request. + client_->write("hello", *listener_address_); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + expectRequestHeaders(upstream_request_->headers()); + + Http::TestResponseHeaderMapImpl fail_response_headers{{":status", "404"}}; + upstream_request_->encodeHeaders(fail_response_headers, true); + + test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + + // The request is retried, expect new downstream headers + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + expectRequestHeaders(upstream_request_->headers()); + + // Send upgrade headers downstream, fully establishing the connection. + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"capsule-protocol", "?1"}}; + upstream_request_->encodeHeaders(response_headers, false); + + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 1); + sendCapsuleDownstream("response", true); + test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); +} + +INSTANTIATE_TEST_SUITE_P(IpAndHttpVersions, UdpTunnelingIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( + {Http::CodecType::HTTP2}, {Http::CodecType::HTTP2})), + HttpProtocolIntegrationTest::protocolTestParamsToString); + } // namespace } // namespace Envoy diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 7cd000c5ca6d..5c6e7c4fef70 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -11,15 +11,15 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/event:95.1" # Emulated edge events guards don't report LCOV "source/common/filesystem/posix:96.2" # FileReadToEndNotReadable fails in some env; createPath can't test all failure branches. "source/common/http/http2:95.2" -"source/common/json:94.1" +"source/common/json:94.6" "source/common/matcher:94.6" "source/common/network:94.4" # Flaky, `activateFileEvents`, `startSecureTransport` and `ioctl`, listener_socket do not always report LCOV "source/common/network/dns_resolver:91.4" # A few lines of MacOS code not tested in linux scripts. Tested in MacOS scripts "source/common/protobuf:96.5" "source/common/quic:93.3" -"source/common/secret:95.0" +"source/common/secret:95.1" "source/common/signal:87.2" # Death tests don't report LCOV -"source/common/tcp:94.1" +"source/common/tcp:94.5" "source/common/thread:0.0" # Death tests don't report LCOV "source/common/watchdog:58.6" # Death tests don't report LCOV "source/exe:90.7" @@ -31,12 +31,11 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/common/wasm/ext:92.0" "source/extensions/filters/common/fault:94.5" "source/extensions/filters/common/rbac:90.5" -"source/extensions/filters/http/cache:93.4" +"source/extensions/filters/http/cache:94.0" "source/extensions/filters/http/grpc_json_transcoder:93.7" # TODO(#28232) "source/extensions/filters/http/ip_tagging:88.0" "source/extensions/filters/http/kill_request:91.7" # Death tests don't report LCOV "source/extensions/filters/http/wasm:1.9" -"source/extensions/filters/listener/original_dst:87.9" "source/extensions/filters/listener/original_src:92.1" "source/extensions/filters/network/common:96.4" "source/extensions/filters/network/mongo_proxy:96.0" @@ -47,7 +46,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/rate_limit_descriptors/expr:95.0" "source/extensions/stat_sinks/graphite_statsd:78.6" # Death tests don't report LCOV "source/extensions/stat_sinks/statsd:80.8" # Death tests don't report LCOV -"source/extensions/tracers:95.8" +"source/extensions/tracers:95.9" "source/extensions/tracers/common:73.8" "source/extensions/tracers/common/ot:71.8" "source/extensions/tracers/opencensus:93.2" @@ -60,10 +59,10 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/wasm_runtime/wasmtime:0.0" # Not enabled in coverage build "source/extensions/wasm_runtime/wavm:0.0" # Not enabled in coverage build "source/extensions/watchdog:83.3" # Death tests within extensions -"source/extensions/listener_managers/validation_listener_manager:70.0" +"source/extensions/listener_managers/validation_listener_manager:70.5" "source/extensions/watchdog/profile_action:83.3" "source/server:90.7" # flaky: be careful adjusting. See https://github.com/envoyproxy/envoy/issues/15239 -"source/server/config_validation:88.4" +"source/server/config_validation:88.9" "source/extensions/health_checkers:96.0" "source/extensions/health_checkers/http:93.9" "source/extensions/health_checkers/grpc:92.0" diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 97bd81920ee3..2d7d58383012 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -11,7 +11,7 @@ cryptography>=41.0.1 dependatool>=0.2.2 envoy.base.utils>=0.4.12 envoy.code.check>=0.5.8 -envoy.dependency.check>=0.1.7 +envoy.dependency.check>=0.1.10 envoy.distribution.release>=0.0.9 envoy.distribution.repo>=0.0.8 envoy.distribution.verify>=0.0.11 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 135d4ce588e7..d0ad9860f286 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -436,9 +436,9 @@ docutils==0.19 \ # envoy-docs-sphinx-runner # sphinx # sphinx-rtd-theme -envoy-base-utils==0.4.12 \ - --hash=sha256:2bafcb6be3c1223968c9ee90b7a33d6d93aee16fe99bef37410ea9e4bc1a957f \ - --hash=sha256:b9b409abe83be6911aa03cfe635ed1e2e2b9e44e7c8595b2b7e5c7aae7bf70fc +envoy-base-utils==0.4.13 \ + --hash=sha256:58a35870e15ca00e921f9ab8266c6a1f83dc40f830bf0f1002e940aae2067a06 \ + --hash=sha256:a3a1f1289ad7fabb33766699912d06a81385f4e3619f6bec80d5bdaab5e606a8 # via # -r requirements.in # envoy-code-check @@ -454,9 +454,9 @@ envoy-code-check==0.5.8 \ --hash=sha256:03f32588cc9ed98ab6703cbca6f81df1527db71c3a0f962be6a6084ded40d528 \ --hash=sha256:2b12c51098c78d393823cf055a54e9308c37321d769041f01a2f35b04074d6f3 # via -r requirements.in -envoy-dependency-check==0.1.8 \ - --hash=sha256:ac9820e446bb44e05121e5c93c210f40ca37076580b0d082da2c63e7784c338a \ - --hash=sha256:e92272ca1f4d850d3eb3bde3c22cff39c103e7850fbda8d1686814bfc8c45338 +envoy-dependency-check==0.1.10 \ + --hash=sha256:4a637e0ed7184791b495041f9baf44567a95cbb979e1e5f26f6a8c33f724cf9e \ + --hash=sha256:e6ae41249f298c865a357edcd8e4850354f222ea4f0dd629c737706b23670c75 # via -r requirements.in envoy-distribution-distrotest==0.0.10 \ --hash=sha256:83e912c48da22eb3e514fc1142247d33eb7ed0d59e94eca2ffbd178a26fbf808 \ @@ -637,9 +637,9 @@ gitdb==4.0.10 \ --hash=sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a \ --hash=sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7 # via gitpython -gitpython==3.1.36 \ - --hash=sha256:4bb0c2a6995e85064140d31a33289aa5dce80133a23d36fcd372d716c54d3ebf \ - --hash=sha256:8d22b5cfefd17c79914226982bb7851d6ade47545b1735a9d010a2a4c26d8388 +gitpython==3.1.37 \ + --hash=sha256:5f4c4187de49616d710a77e98ddf17b4782060a1788df441846bddefbb89ab33 \ + --hash=sha256:f9b9ddc0761c125d5780eab2d64be4873fc6817c2899cbcb34b02344bdc7bc54 # via -r requirements.in google-api-core==2.11.1 \ --hash=sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a \ diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index ffc45fe5517f..87c66d7b1ea3 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -195,7 +195,7 @@ paths: protobuf: include: - api/test - - bazel/cc_proto_descriptor_library + - api/bazel/cc_proto_descriptor_library - ci/prebuilt - source/common/protobuf - source/extensions/filters/http/grpc_field_extraction @@ -246,7 +246,7 @@ paths: # Files in these paths can use MessageLite::SerializeAsString serialize_as_string: include: - - bazel/cc_proto_descriptor_library/file_descriptor_generator.cc + - api/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 diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index c41c4955937e..e8866cc61a90 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -38,6 +38,7 @@ DOM GiB IPTOS Repick +Reserializer SION TRA Websockets @@ -73,7 +74,9 @@ FIXME HEXDIG HEXDIGIT HPE +Kwasi LRU +Mensah NAK OWS Preconnecting @@ -1293,6 +1296,7 @@ subtypes subzone suf superclass +superroot superset svc symlink