text: /*
   *
   * Copyright 2015 gRPC 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.
   *
   */
  
  #include <grpc/support/port_platform.h>
  
  #include <assert.h>
  #include <string.h>
  
  #include <grpc/compression.h>
  #include <grpc/slice_buffer.h>
  #include <grpc/support/alloc.h>
  #include <grpc/support/log.h>
  
  #include "src/core/ext/filters/http/message_compress/message_compress_filter.h"
  #include "src/core/lib/channel/channel_args.h"
  #include "src/core/lib/compression/algorithm_metadata.h"
  #include "src/core/lib/compression/compression_args.h"
  #include "src/core/lib/compression/compression_internal.h"
  #include "src/core/lib/compression/message_compress.h"
  #include "src/core/lib/gpr/string.h"
  #include "src/core/lib/gprpp/manual_constructor.h"
  #include "src/core/lib/profiling/timers.h"
  #include "src/core/lib/slice/slice_internal.h"
  #include "src/core/lib/slice/slice_string_helpers.h"
  #include "src/core/lib/surface/call.h"
  #include "src/core/lib/transport/static_metadata.h"
  
  static void start_send_message_batch(void* arg, grpc_error* unused);
  static void send_message_on_complete(void* arg, grpc_error* error);
  static void on_send_message_next_done(void* arg, grpc_error* error);
  
  namespace {
  enum initial_metadata_state {
    // Initial metadata not yet seen.
    INITIAL_METADATA_UNSEEN = 0,
    // Initial metadata seen; compression algorithm set.
    HAS_COMPRESSION_ALGORITHM,
    // Initial metadata seen; no compression algorithm set.
    NO_COMPRESSION_ALGORITHM,
  };
  
  struct call_data {
    call_data(grpc_call_element* elem, const grpc_call_element_args& args)
        : call_combiner(args.call_combiner) {
      GRPC_CLOSURE_INIT(&start_send_message_batch_in_call_combiner,
                        start_send_message_batch, elem,
                        grpc_schedule_on_exec_ctx);
      grpc_slice_buffer_init(&slices);
      GRPC_CLOSURE_INIT(&send_message_on_complete, ::send_message_on_complete,
                        elem, grpc_schedule_on_exec_ctx);
      GRPC_CLOSURE_INIT(&on_send_message_next_done, ::on_send_message_next_done,
                        elem, grpc_schedule_on_exec_ctx);
    }
  
    ~call_data() {
      grpc_slice_buffer_destroy_internal(&slices);
      GRPC_ERROR_UNREF(cancel_error);
    }
  
    grpc_core::CallCombiner* call_combiner;
    grpc_linked_mdelem compression_algorithm_storage;
    grpc_linked_mdelem stream_compression_algorithm_storage;
    grpc_linked_mdelem accept_encoding_storage;
    grpc_linked_mdelem accept_stream_encoding_storage;
    /** Compression algorithm we'll try to use. It may be given by incoming
     * metadata, or by the channel's default compression settings. */
    grpc_message_compression_algorithm message_compression_algorithm =
        GRPC_MESSAGE_COMPRESS_NONE;
    initial_metadata_state send_initial_metadata_state = INITIAL_METADATA_UNSEEN;
    grpc_error* cancel_error = GRPC_ERROR_NONE;
    grpc_closure start_send_message_batch_in_call_combiner;
    grpc_transport_stream_op_batch* send_message_batch = nullptr;
    grpc_slice_buffer slices; /**< Buffers up input slices to be compressed */
    grpc_core::ManualConstructor<grpc_core::SliceBufferByteStream>
        replacement_stream;
    grpc_closure* original_send_message_on_complete;
    grpc_closure send_message_on_complete;
    grpc_closure on_send_message_next_done;
  };
  
  struct channel_data {
    /** The default, channel-level, compression algorithm */
    grpc_compression_algorithm default_compression_algorithm;
    /** Bitset of enabled compression algorithms */
    uint32_t enabled_algorithms_bitset;
    /** Supported compression algorithms */
    uint32_t supported_message_compression_algorithms;
    /** Supported stream compression algorithms */
    uint32_t supported_stream_compression_algorithms;
  };
  }  // namespace
  
  static bool skip_compression(grpc_call_element* elem, uint32_t flags,
                               bool has_compression_algorithm) {
    call_data* calld = static_cast<call_data*>(elem->call_data);
    channel_data* channeld = static_cast<channel_data*>(elem->channel_data);
  
    if (flags & (GRPC_WRITE_NO_COMPRESS | GRPC_WRITE_INTERNAL_COMPRESS)) {
      return true;
    }
    if (has_compression_algorithm) {
      if (calld->message_compression_algorithm == GRPC_MESSAGE_COMPRESS_NONE) {
        return true;
      }
      return false; /* we have an actual call-specific algorithm */
    }
    /* no per-call compression override */
    return channeld->default_compression_algorithm == GRPC_COMPRESS_NONE;
  }
  
  /** Filter initial metadata */
  static grpc_error* process_send_initial_metadata(
      grpc_call_element* elem, grpc_metadata_batch* initial_metadata,
      bool* has_compression_algorithm) GRPC_MUST_USE_RESULT;
  static grpc_error* process_send_initial_metadata(
      grpc_call_element* elem, grpc_metadata_batch* initial_metadata,
      bool* has_compression_algorithm) {
    call_data* calld = static_cast<call_data*>(elem->call_data);
    channel_data* channeld = static_cast<channel_data*>(elem->channel_data);
    *has_compression_algorithm = false;
    grpc_compression_algorithm compression_algorithm;
    grpc_stream_compression_algorithm stream_compression_algorithm =
        GRPC_STREAM_COMPRESS_NONE;
    if (initial_metadata->idx.named.grpc_internal_encoding_request != nullptr) {
      grpc_mdelem md =
          initial_metadata->idx.named.grpc_internal_encoding_request->md;
      if (GPR_UNLIKELY(!grpc_compression_algorithm_parse(
              GRPC_MDVALUE(md), &compression_algorithm))) {
        char* val = grpc_slice_to_c_string(GRPC_MDVALUE(md));
        gpr_log(GPR_ERROR,
                "Invalid compression algorithm: '%s' (unknown). Ignoring.", val);
        gpr_free(val);
        calld->message_compression_algorithm = GRPC_MESSAGE_COMPRESS_NONE;
        stream_compression_algorithm = GRPC_STREAM_COMPRESS_NONE;
      }
      if (GPR_UNLIKELY(!GPR_BITGET(channeld->enabled_algorithms_bitset,
                                   compression_algorithm))) {
        char* val = grpc_slice_to_c_string(GRPC_MDVALUE(md));
        gpr_log(GPR_ERROR,
                "Invalid compression algorithm: '%s' (previously disabled). "
                "Ignoring.",
                val);
        gpr_free(val);
        calld->message_compression_algorithm = GRPC_MESSAGE_COMPRESS_NONE;
        stream_compression_algorithm = GRPC_STREAM_COMPRESS_NONE;
      }
      *has_compression_algorithm = true;
      grpc_metadata_batch_remove(
          initial_metadata,
          initial_metadata->idx.named.grpc_internal_encoding_request);
      calld->message_compression_algorithm =
          grpc_compression_algorithm_to_message_compression_algorithm(
              compression_algorithm);
      stream_compression_algorithm =
          grpc_compression_algorithm_to_stream_compression_algorithm(
              compression_algorithm);
    } else {
      /* If no algorithm was found in the metadata and we aren't
       * exceptionally skipping compression, fall back to the channel
       * default */
      if (channeld->default_compression_algorithm != GRPC_COMPRESS_NONE) {
        calld->message_compression_algorithm =
            grpc_compression_algorithm_to_message_compression_algorithm(
                channeld->default_compression_algorithm);
        stream_compression_algorithm =
            grpc_compression_algorithm_to_stream_compression_algorithm(
                channeld->default_compression_algorithm);
      }
      *has_compression_algorithm = true;
    }
  
    grpc_error* error = GRPC_ERROR_NONE;
    /* hint compression algorithm */
    if (stream_compression_algorithm != GRPC_STREAM_COMPRESS_NONE) {
      error = grpc_metadata_batch_add_tail(
          initial_metadata, &calld->stream_compression_algorithm_storage,
          grpc_stream_compression_encoding_mdelem(stream_compression_algorithm));
    } else if (calld->message_compression_algorithm !=
               GRPC_MESSAGE_COMPRESS_NONE) {
      error = grpc_metadata_batch_add_tail(
          initial_metadata, &calld->compression_algorithm_storage,
          grpc_message_compression_encoding_mdelem(
              calld->message_compression_algorithm));
    }
  
    if (error != GRPC_ERROR_NONE) return error;
  
    /* convey supported compression algorithms */
    error = grpc_metadata_batch_add_tail(
        initial_metadata, &calld->accept_encoding_storage,
        GRPC_MDELEM_ACCEPT_ENCODING_FOR_ALGORITHMS(
            channeld->supported_message_compression_algorithms));
  
    if (error != GRPC_ERROR_NONE) return error;
  
    /* Do not overwrite accept-encoding header if it already presents (e.g. added
     * by some proxy). */
    if (!initial_metadata->idx.named.accept_encoding) {
      error = grpc_metadata_batch_add_tail(
          initial_metadata, &calld->accept_stream_encoding_storage,
          GRPC_MDELEM_ACCEPT_STREAM_ENCODING_FOR_ALGORITHMS(
              channeld->supported_stream_compression_algorithms));
    }
  
    return error;
  }
  
  static void send_message_on_complete(void* arg, grpc_error* error) {
    grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
    call_data* calld = static_cast<call_data*>(elem->call_data);
    grpc_slice_buffer_reset_and_unref_internal(&calld->slices);
    GRPC_CLOSURE_RUN(calld->original_send_message_on_complete,
                     GRPC_ERROR_REF(error));
  }
  
  static void send_message_batch_continue(grpc_call_element* elem) {
    call_data* calld = static_cast<call_data*>(elem->call_data);
    // Note: The call to grpc_call_next_op() results in yielding the
    // call combiner, so we need to clear calld->send_message_batch
    // before we do that.
    grpc_transport_stream_op_batch* send_message_batch =
        calld->send_message_batch;
    calld->send_message_batch = nullptr;
    grpc_call_next_op(elem, send_message_batch);
  }
  
  static void finish_send_message(grpc_call_element* elem) {
    call_data* calld = static_cast<call_data*>(elem->call_data);
    // Compress the data if appropriate.
    grpc_slice_buffer tmp;
    grpc_slice_buffer_init(&tmp);
    uint32_t send_flags =
        calld->send_message_batch->payload->send_message.send_message->flags();
    bool did_compress = grpc_msg_compress(calld->message_compression_algorithm,
                                          &calld->slices, &tmp);
    if (did_compress) {
      if (GRPC_TRACE_FLAG_ENABLED(grpc_compression_trace)) {
        const char* algo_name;
        const size_t before_size = calld->slices.length;
        const size_t after_size = tmp.length;
        const float savings_ratio = 1.0f - static_cast<float>(after_size) /
                                               static_cast<float>(before_size);
        GPR_ASSERT(grpc_message_compression_algorithm_name(
            calld->message_compression_algorithm, &algo_name));
        gpr_log(GPR_INFO,
                "Compressed[%s] %" PRIuPTR " bytes vs. %" PRIuPTR
                " bytes (%.2f%% savings)",
                algo_name, before_size, after_size, 100 * savings_ratio);
      }
      grpc_slice_buffer_swap(&calld->slices, &tmp);
      send_flags |= GRPC_WRITE_INTERNAL_COMPRESS;
    } else {
      if (GRPC_TRACE_FLAG_ENABLED(grpc_compression_trace)) {
        const char* algo_name;
        GPR_ASSERT(grpc_message_compression_algorithm_name(
            calld->message_compression_algorithm, &algo_name));
        gpr_log(GPR_INFO,
                "Algorithm '%s' enabled but decided not to compress. Input size: "
                "%" PRIuPTR,
                algo_name, calld->slices.length);
      }
    }
    grpc_slice_buffer_destroy_internal(&tmp);
    // Swap out the original byte stream with our new one and send the
    // batch down.
    calld->replacement_stream.Init(&calld->slices, send_flags);
    calld->send_message_batch->payload->send_message.send_message.reset(
        calld->replacement_stream.get());
    calld->original_send_message_on_complete =
        calld->send_message_batch->on_complete;
    calld->send_message_batch->on_complete = &calld->send_message_on_complete;
    send_message_batch_continue(elem);
  }
  
  static void fail_send_message_batch_in_call_combiner(void* arg,
                                                       grpc_error* error) {
    call_data* calld = static_cast<call_data*>(arg);
    if (calld->send_message_batch != nullptr) {
      grpc_transport_stream_op_batch_finish_with_failure(
          calld->send_message_batch, GRPC_ERROR_REF(error), calld->call_combiner);
      calld->send_message_batch = nullptr;
    }
  }
  
  // Pulls a slice from the send_message byte stream and adds it to calld->slices.
  static grpc_error* pull_slice_from_send_message(call_data* calld) {
    grpc_slice incoming_slice;
    grpc_error* error =
        calld->send_message_batch->payload->send_message.send_message->Pull(
            &incoming_slice);
    if (error == GRPC_ERROR_NONE) {
      grpc_slice_buffer_add(&calld->slices, incoming_slice);
    }
    return error;
  }
  
  // Reads as many slices as possible from the send_message byte stream.
  // If all data has been read, invokes finish_send_message().  Otherwise,
  // an async call to ByteStream::Next() has been started, which will
  // eventually result in calling on_send_message_next_done().
  static void continue_reading_send_message(grpc_call_element* elem) {
    call_data* calld = static_cast<call_data*>(elem->call_data);
    while (calld->send_message_batch->payload->send_message.send_message->Next(
        ~static_cast<size_t>(0), &calld->on_send_message_next_done)) {
      grpc_error* error = pull_slice_from_send_message(calld);
      if (error != GRPC_ERROR_NONE) {
        // Closure callback; does not take ownership of error.
        fail_send_message_batch_in_call_combiner(calld, error);
        GRPC_ERROR_UNREF(error);
        return;
      }
      if (calld->slices.length == calld->send_message_batch->payload->send_message
                                      .send_message->length()) {
        finish_send_message(elem);
        break;
      }
    }
  }
  
  // Async callback for ByteStream::Next().
  static void on_send_message_next_done(void* arg, grpc_error* error) {
    grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
    call_data* calld = static_cast<call_data*>(elem->call_data);
    if (error != GRPC_ERROR_NONE) {
      // Closure callback; does not take ownership of error.
      fail_send_message_batch_in_call_combiner(calld, error);
      return;
    }
    error = pull_slice_from_send_message(calld);
    if (error != GRPC_ERROR_NONE) {
      // Closure callback; does not take ownership of error.
      fail_send_message_batch_in_call_combiner(calld, error);
      GRPC_ERROR_UNREF(error);
      return;
    }
    if (calld->slices.length ==
        calld->send_message_batch->payload->send_message.send_message->length()) {
      finish_send_message(elem);
    } else {
      continue_reading_send_message(elem);
    }
  }
  
  static void start_send_message_batch(void* arg, grpc_error* unused) {
    grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
    call_data* calld = static_cast<call_data*>(elem->call_data);
    if (skip_compression(
            elem,
            calld->send_message_batch->payload->send_message.send_message
                ->flags(),
            calld->send_initial_metadata_state == HAS_COMPRESSION_ALGORITHM)) {
      send_message_batch_continue(elem);
    } else {
      continue_reading_send_message(elem);
    }
  }
  
  static void compress_start_transport_stream_op_batch(
      grpc_call_element* elem, grpc_transport_stream_op_batch* batch) {
    GPR_TIMER_SCOPE("compress_start_transport_stream_op_batch", 0);
    call_data* calld = static_cast<call_data*>(elem->call_data);
    // Handle cancel_stream.
    if (batch->cancel_stream) {
      GRPC_ERROR_UNREF(calld->cancel_error);
      calld->cancel_error =
          GRPC_ERROR_REF(batch->payload->cancel_stream.cancel_error);
      if (calld->send_message_batch != nullptr) {
        if (calld->send_initial_metadata_state == INITIAL_METADATA_UNSEEN) {
          GRPC_CALL_COMBINER_START(
              calld->call_combiner,
              GRPC_CLOSURE_CREATE(fail_send_message_batch_in_call_combiner, calld,
                                  grpc_schedule_on_exec_ctx),
              GRPC_ERROR_REF(calld->cancel_error), "failing send_message op");
        } else {
          calld->send_message_batch->payload->send_message.send_message->Shutdown(
              GRPC_ERROR_REF(calld->cancel_error));
        }
      }
    } else if (calld->cancel_error != GRPC_ERROR_NONE) {
      grpc_transport_stream_op_batch_finish_with_failure(
          batch, GRPC_ERROR_REF(calld->cancel_error), calld->call_combiner);
      return;
    }
    // Handle send_initial_metadata.
    if (batch->send_initial_metadata) {
      GPR_ASSERT(calld->send_initial_metadata_state == INITIAL_METADATA_UNSEEN);
      bool has_compression_algorithm;
      grpc_error* error = process_send_initial_metadata(
          elem, batch->payload->send_initial_metadata.send_initial_metadata,
          &has_compression_algorithm);
      if (error != GRPC_ERROR_NONE) {
        grpc_transport_stream_op_batch_finish_with_failure(batch, error,
                                                           calld->call_combiner);
        return;
      }
      calld->send_initial_metadata_state = has_compression_algorithm
                                               ? HAS_COMPRESSION_ALGORITHM
                                               : NO_COMPRESSION_ALGORITHM;
      // If we had previously received a batch containing a send_message op,
      // handle it now.  Note that we need to re-enter the call combiner
      // for this, since we can't send two batches down while holding the
      // call combiner, since the connected_channel filter (at the bottom of
      // the call stack) will release the call combiner for each batch it sees.
      if (calld->send_message_batch != nullptr) {
        GRPC_CALL_COMBINER_START(
            calld->call_combiner,
            &calld->start_send_message_batch_in_call_combiner, GRPC_ERROR_NONE,
            "starting send_message after send_initial_metadata");
      }
    }
    // Handle send_message.
    if (batch->send_message) {
      GPR_ASSERT(calld->send_message_batch == nullptr);
      calld->send_message_batch = batch;
      // If we have not yet seen send_initial_metadata, then we have to
      // wait.  We save the batch in calld and then drop the call
      // combiner, which we'll have to pick up again later when we get
      // send_initial_metadata.
      if (calld->send_initial_metadata_state == INITIAL_METADATA_UNSEEN) {
        GRPC_CALL_COMBINER_STOP(
            calld->call_combiner,
            "send_message batch pending send_initial_metadata");
        return;
      }
      start_send_message_batch(elem, GRPC_ERROR_NONE);
    } else {
      // Pass control down the stack.
      grpc_call_next_op(elem, batch);
    }
  }
  
  /* Constructor for call_data */
  static grpc_error* init_call_elem(grpc_call_element* elem,
                                    const grpc_call_element_args* args) {
    new (elem->call_data) call_data(elem, *args);
    return GRPC_ERROR_NONE;
  }
  
  /* Destructor for call_data */
  static void destroy_call_elem(grpc_call_element* elem,
                                const grpc_call_final_info* final_info,
                                grpc_closure* ignored) {
    call_data* calld = static_cast<call_data*>(elem->call_data);
    calld->~call_data();
  }
  
  /* Constructor for channel_data */
  static grpc_error* init_channel_elem(grpc_channel_element* elem,
                                       grpc_channel_element_args* args) {
    channel_data* channeld = static_cast<channel_data*>(elem->channel_data);
  
    channeld->enabled_algorithms_bitset =
        grpc_channel_args_compression_algorithm_get_states(args->channel_args);
    channeld->default_compression_algorithm =
        grpc_channel_args_get_compression_algorithm(args->channel_args);
  
    /* Make sure the default isn't disabled. */
    if (!GPR_BITGET(channeld->enabled_algorithms_bitset,
                    channeld->default_compression_algorithm)) {
      gpr_log(GPR_DEBUG,
              "compression algorithm %d not enabled: switching to none",
              channeld->default_compression_algorithm);
      channeld->default_compression_algorithm = GRPC_COMPRESS_NONE;
    }
  
    uint32_t supported_compression_algorithms =
        (((1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1) &
         channeld->enabled_algorithms_bitset) |
        1u;
  
    channeld->supported_message_compression_algorithms =
        grpc_compression_bitset_to_message_bitset(
            supported_compression_algorithms);
  
    channeld->supported_stream_compression_algorithms =
        grpc_compression_bitset_to_stream_bitset(
            supported_compression_algorithms);
  
    GPR_ASSERT(!args->is_last);
    return GRPC_ERROR_NONE;
  }
  
  /* Destructor for channel data */
  static void destroy_channel_elem(grpc_channel_element* elem) {}
  
  const grpc_channel_filter grpc_message_compress_filter = {
      compress_start_transport_stream_op_batch,
      grpc_channel_next_op,
      sizeof(call_data),
      init_call_elem,
      grpc_call_stack_ignore_set_pollset_or_pollset_set,
      destroy_call_elem,
      sizeof(channel_data),
      init_channel_elem,
      destroy_channel_elem,
      grpc_channel_next_get_info,
      "message_compress"};
  