// Copyright 2025 Envoy Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package envoy.config.tap.v3;

import "envoy/config/common/matcher/v3/matcher.proto";
import "envoy/config/core/v3/base.proto";
import "envoy/config/core/v3/extension.proto";
import "envoy/config/core/v3/grpc_service.proto";
import "envoy/config/route/v3/route_components.proto";

import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";

import "envoy/annotations/deprecation.proto";
import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.config.tap.v3";
option java_outer_classname = "CommonProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/config/tap/v3;tapv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Tap common configuration]

// Tap configuration.
message TapConfig {
  // [#comment:TODO(mattklein123): Rate limiting]

  option (udpa.annotations.versioning).previous_message_type =
      "envoy.service.tap.v2alpha.TapConfig";

  // The match configuration. If the configuration matches the data source being tapped, a tap will
  // occur, with the result written to the configured output.
  // Exactly one of :ref:`match <envoy_v3_api_field_config.tap.v3.TapConfig.match>` and
  // :ref:`match_config <envoy_v3_api_field_config.tap.v3.TapConfig.match_config>` must be set. If both
  // are set, the :ref:`match <envoy_v3_api_field_config.tap.v3.TapConfig.match>` will be used.
  MatchPredicate match_config = 1
      [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];

  // The match configuration. If the configuration matches the data source being tapped, a tap will
  // occur, with the result written to the configured output.
  // Exactly one of :ref:`match <envoy_v3_api_field_config.tap.v3.TapConfig.match>` and
  // :ref:`match_config <envoy_v3_api_field_config.tap.v3.TapConfig.match_config>` must be set. If both
  // are set, the :ref:`match <envoy_v3_api_field_config.tap.v3.TapConfig.match>` will be used.
  common.matcher.v3.MatchPredicate match = 4;

  // The tap output configuration. If a match configuration matches a data source being tapped,
  // a tap will occur and the data will be written to the configured output.
  OutputConfig output_config = 2 [(validate.rules).message = {required: true}];

  // [#not-implemented-hide:] Specify if Tap matching is enabled. The % of requests\connections for
  // which the tap matching is enabled. When not enabled, the request\connection will not be
  // recorded.
  //
  // .. note::
  //
  //   This field defaults to 100/:ref:`HUNDRED
  //   <envoy_v3_api_enum_type.v3.FractionalPercent.DenominatorType>`.
  core.v3.RuntimeFractionalPercent tap_enabled = 3;
}

// Tap match configuration. This is a recursive structure which allows complex nested match
// configurations to be built using various logical operators.
// [#next-free-field: 11]
message MatchPredicate {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.service.tap.v2alpha.MatchPredicate";

  // A set of match configurations used for logical operations.
  message MatchSet {
    option (udpa.annotations.versioning).previous_message_type =
        "envoy.service.tap.v2alpha.MatchPredicate.MatchSet";

    // The list of rules that make up the set.
    repeated MatchPredicate rules = 1 [(validate.rules).repeated = {min_items: 2}];
  }

  oneof rule {
    option (validate.required) = true;

    // A set that describes a logical OR. If any member of the set matches, the match configuration
    // matches.
    MatchSet or_match = 1;

    // A set that describes a logical AND. If all members of the set match, the match configuration
    // matches.
    MatchSet and_match = 2;

    // A negation match. The match configuration will match if the negated match condition matches.
    MatchPredicate not_match = 3;

    // The match configuration will always match.
    bool any_match = 4 [(validate.rules).bool = {const: true}];

    // HTTP request headers match configuration.
    HttpHeadersMatch http_request_headers_match = 5;

    // HTTP request trailers match configuration.
    HttpHeadersMatch http_request_trailers_match = 6;

    // HTTP response headers match configuration.
    HttpHeadersMatch http_response_headers_match = 7;

    // HTTP response trailers match configuration.
    HttpHeadersMatch http_response_trailers_match = 8;

    // HTTP request generic body match configuration.
    HttpGenericBodyMatch http_request_generic_body_match = 9;

    // HTTP response generic body match configuration.
    HttpGenericBodyMatch http_response_generic_body_match = 10;
  }
}

// HTTP headers match configuration.
message HttpHeadersMatch {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.service.tap.v2alpha.HttpHeadersMatch";

  // HTTP headers to match.
  repeated route.v3.HeaderMatcher headers = 1;
}

// HTTP generic body match configuration.
// List of text strings and hex strings to be located in HTTP body.
// All specified strings must be found in the HTTP body for positive match.
// The search may be limited to specified number of bytes from the body start.
//
// .. attention::
//
//   Searching for patterns in HTTP body is potentially cpu intensive. For each specified pattern, http body is scanned byte by byte to find a match.
//   If multiple patterns are specified, the process is repeated for each pattern. If location of a pattern is known, ``bytes_limit`` should be specified
//   to scan only part of the http body.
message HttpGenericBodyMatch {
  message GenericTextMatch {
    oneof rule {
      option (validate.required) = true;

      // Text string to be located in HTTP body.
      string string_match = 1 [(validate.rules).string = {min_len: 1}];

      // Sequence of bytes to be located in HTTP body.
      bytes binary_match = 2 [(validate.rules).bytes = {min_len: 1}];
    }
  }

  // Limits search to specified number of bytes - default zero (no limit - match entire captured buffer).
  uint32 bytes_limit = 1;

  // List of patterns to match.
  repeated GenericTextMatch patterns = 2 [(validate.rules).repeated = {min_items: 1}];
}

// Tap output configuration.
// [#next-free-field: 6]
message OutputConfig {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.service.tap.v2alpha.OutputConfig";

  // Output sinks for tap data. Currently a single sink is allowed in the list. Once multiple
  // sink types are supported this constraint will be relaxed.
  repeated OutputSink sinks = 1 [(validate.rules).repeated = {min_items: 1 max_items: 1}];

  // For buffered tapping, the maximum amount of received body that will be buffered prior to
  // truncation. If truncation occurs, the :ref:`truncated
  // <envoy_v3_api_field_data.tap.v3.Body.truncated>` field will be set. If not specified, the
  // default is 1KiB.
  google.protobuf.UInt32Value max_buffered_rx_bytes = 2;

  // For buffered tapping, the maximum amount of transmitted body that will be buffered prior to
  // truncation. If truncation occurs, the :ref:`truncated
  // <envoy_v3_api_field_data.tap.v3.Body.truncated>` field will be set. If not specified, the
  // default is 1KiB.
  google.protobuf.UInt32Value max_buffered_tx_bytes = 3;

  // Indicates whether taps produce a single buffered message per tap, or multiple streamed
  // messages per tap in the emitted :ref:`TraceWrapper
  // <envoy_v3_api_msg_data.tap.v3.TraceWrapper>` messages. Note that streamed tapping does not
  // mean that no buffering takes place. Buffering may be required if data is processed before a
  // match can be determined. See the HTTP tap filter :ref:`streaming
  // <config_http_filters_tap_streaming>` documentation for more information.
  bool streaming = 4;

  // Tapped messages will be sent on each read/write event for streamed tapping by default.
  // But this behavior could be controlled by setting this field.
  // If set then the tapped messages will be send once the threshold is reached.
  // This could be used to avoid high frequent sending.
  google.protobuf.UInt32Value min_streamed_sent_bytes = 5;
}

// Tap output sink configuration.
// [#next-free-field: 7]
message OutputSink {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.service.tap.v2alpha.OutputSink";

  // Output format. All output is in the form of one or more :ref:`TraceWrapper
  // <envoy_v3_api_msg_data.tap.v3.TraceWrapper>` messages. This enumeration indicates
  // how those messages are written. Note that not all sinks support all output formats. See
  // individual sink documentation for more information.
  enum Format {
    // Each message will be written as JSON. Any :ref:`body <envoy_v3_api_msg_data.tap.v3.Body>`
    // data will be present in the :ref:`as_bytes
    // <envoy_v3_api_field_data.tap.v3.Body.as_bytes>` field. This means that body data will be
    // base64 encoded as per the `proto3 JSON mappings
    // <https://developers.google.com/protocol-buffers/docs/proto3#json>`_.
    JSON_BODY_AS_BYTES = 0;

    // Each message will be written as JSON. Any :ref:`body <envoy_v3_api_msg_data.tap.v3.Body>`
    // data will be present in the :ref:`as_string
    // <envoy_v3_api_field_data.tap.v3.Body.as_string>` field. This means that body data will be
    // string encoded as per the `proto3 JSON mappings
    // <https://developers.google.com/protocol-buffers/docs/proto3#json>`_. This format type is
    // useful when it is known that that body is human readable (e.g., JSON over HTTP) and the
    // user wishes to view it directly without being forced to base64 decode the body.
    JSON_BODY_AS_STRING = 1;

    // Binary proto format. Note that binary proto is not self-delimiting. If a sink writes
    // multiple binary messages without any length information the data stream will not be
    // useful. However, for certain sinks that are self-delimiting (e.g., one message per file)
    // this output format makes consumption simpler.
    PROTO_BINARY = 2;

    // Messages are written as a sequence tuples, where each tuple is the message length encoded
    // as a `protobuf 32-bit varint
    // <https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.io.coded_stream>`_
    // followed by the binary message. The messages can be read back using the language specific
    // protobuf coded stream implementation to obtain the message length and the message.
    PROTO_BINARY_LENGTH_DELIMITED = 3;

    // Text proto format.
    PROTO_TEXT = 4;
  }

  // Sink output format.
  Format format = 1 [(validate.rules).enum = {defined_only: true}];

  oneof output_sink_type {
    option (validate.required) = true;

    // Tap output will be streamed out the :http:post:`/tap` admin endpoint.
    //
    // .. attention::
    //
    //   It is only allowed to specify the streaming admin output sink if the tap is being
    //   configured from the :http:post:`/tap` admin endpoint. Thus, if an extension has
    //   been configured to receive tap configuration from some other source (e.g., static
    //   file, XDS, etc.) configuring the streaming admin output type will fail.
    StreamingAdminSink streaming_admin = 2;

    // Tap output will be written to a file per tap sink.
    FilePerTapSink file_per_tap = 3;

    // [#not-implemented-hide:]
    // GrpcService to stream data to. The format argument must be PROTO_BINARY.
    // [#comment: TODO(samflattery): remove cleanup in uber_per_filter.cc once implemented]
    StreamingGrpcSink streaming_grpc = 4;

    // Tap output will be buffered in a single block before flushing to the :http:post:`/tap` admin endpoint
    //
    // .. attention::
    //
    //   It is only allowed to specify the buffered admin output sink if the tap is being
    //   configured from the :http:post:`/tap` admin endpoint. Thus, if an extension has
    //   been configured to receive tap configuration from some other source (e.g., static
    //   file, XDS, etc.) configuring the buffered admin output type will fail.
    BufferedAdminSink buffered_admin = 5;

    // Tap output filter will be defined by an extension type
    core.v3.TypedExtensionConfig custom_sink = 6;
  }
}

// Streaming admin sink configuration.
message StreamingAdminSink {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.service.tap.v2alpha.StreamingAdminSink";
}

// BufferedAdminSink configures a tap output to collect traces without returning them until
// one of multiple criteria are satisfied.
// Similar to StreamingAdminSink, it is only allowed to specify the buffered admin output
// sink if the tap is being configured from the ``/tap`` admin endpoint.
message BufferedAdminSink {
  // Stop collecting traces when the specified number are collected.
  // If other criteria for ending collection are reached first, this value will not be used.
  uint64 max_traces = 1 [(validate.rules).uint64 = {gt: 0}];

  // Acts as a fallback to prevent the client from waiting for long periods of time.
  // After timeout has occurred, a buffer flush will be triggered, returning the traces buffered so far.
  // This may result in returning fewer traces than were requested, and in the case that no traces are
  // buffered during this time, no traces will be returned.
  // Specifying 0 for the timeout value (or not specifying a value at all) indicates an infinite timeout.
  google.protobuf.Duration timeout = 2;
}

// The file per tap sink outputs a discrete file for every tapped stream.
message FilePerTapSink {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.service.tap.v2alpha.FilePerTapSink";

  // Path prefix. The output file will be of the form <path_prefix>_<id>.pb, where <id> is an
  // identifier distinguishing the recorded trace for stream instances (the Envoy
  // connection ID, HTTP stream ID, etc.).
  string path_prefix = 1 [(validate.rules).string = {min_len: 1}];
}

// [#not-implemented-hide:] Streaming gRPC sink configuration sends the taps to an external gRPC
// server.
message StreamingGrpcSink {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.service.tap.v2alpha.StreamingGrpcSink";

  // Opaque identifier, that will be sent back to the streaming grpc server.
  string tap_id = 1;

  // The gRPC server that hosts the Tap Sink Service.
  core.v3.GrpcService grpc_service = 2 [(validate.rules).message = {required: true}];
}
