Пример #1
0
static void hc_mutate_op(grpc_call_element *elem,
                         grpc_transport_stream_op *op) {
  /* grab pointers to our data from the call element */
  call_data *calld = elem->call_data;
  channel_data *channeld = elem->channel_data;
  if (op->send_initial_metadata != NULL) {
    grpc_metadata_batch_filter(op->send_initial_metadata, client_strip_filter,
                               elem);
    /* Send : prefixed headers, which have to be before any application
       layer headers. */
    grpc_metadata_batch_add_head(op->send_initial_metadata, &calld->method,
                                 GRPC_MDELEM_METHOD_POST);
    grpc_metadata_batch_add_head(op->send_initial_metadata, &calld->scheme,
                                 channeld->static_scheme);
    grpc_metadata_batch_add_tail(op->send_initial_metadata, &calld->te_trailers,
                                 GRPC_MDELEM_TE_TRAILERS);
    grpc_metadata_batch_add_tail(
        op->send_initial_metadata, &calld->content_type,
        GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC);
    grpc_metadata_batch_add_tail(op->send_initial_metadata, &calld->user_agent,
                                 GRPC_MDELEM_REF(channeld->user_agent));
  }

  if (op->recv_initial_metadata != NULL) {
    /* substitute our callback for the higher callback */
    calld->recv_initial_metadata = op->recv_initial_metadata;
    calld->on_done_recv = op->on_complete;
    op->on_complete = &calld->hc_on_recv;
  }
}
Пример #2
0
/** Filter initial metadata */
static void process_send_initial_metadata(
    grpc_call_element *elem, grpc_metadata_batch *initial_metadata) {
    call_data *calld = elem->call_data;
    channel_data *channeld = elem->channel_data;
    /* Parse incoming request for compression. If any, it'll be available
     * at calld->compression_algorithm */
    grpc_metadata_batch_filter(initial_metadata, compression_md_filter, elem);
    if (!calld->has_compression_algorithm) {
        /* If no algorithm was found in the metadata and we aren't
         * exceptionally skipping compression, fall back to the channel
         * default */
        calld->compression_algorithm = channeld->default_compression_algorithm;
        calld->has_compression_algorithm = 1; /* GPR_TRUE */
    }
    /* hint compression algorithm */
    grpc_metadata_batch_add_tail(
        initial_metadata, &calld->compression_algorithm_storage,
        grpc_compression_encoding_mdelem(calld->compression_algorithm));

    /* convey supported compression algorithms */
    grpc_metadata_batch_add_tail(initial_metadata,
                                 &calld->accept_encoding_storage,
                                 GRPC_MDELEM_ACCEPT_ENCODING_FOR_ALGORITHMS(
                                     channeld->supported_compression_algorithms));
}
Пример #3
0
static grpc_error *process_send_initial_metadata(
    grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
    grpc_metadata_batch *initial_metadata, bool *has_compression_algorithm) {
  call_data *calld = elem->call_data;
  channel_data *channeld = elem->channel_data;
  *has_compression_algorithm = false;
  /* Parse incoming request for compression. If any, it'll be available
   * at calld->compression_algorithm */
  if (initial_metadata->idx.named.grpc_internal_encoding_request != NULL) {
    grpc_mdelem md =
        initial_metadata->idx.named.grpc_internal_encoding_request->md;
    if (!grpc_compression_algorithm_parse(GRPC_MDVALUE(md),
                                          &calld->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->compression_algorithm = GRPC_COMPRESS_NONE;
    }
    if (!GPR_BITGET(channeld->enabled_algorithms_bitset,
                    calld->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->compression_algorithm = GRPC_COMPRESS_NONE;
    }
    *has_compression_algorithm = true;

    grpc_metadata_batch_remove(
        exec_ctx, initial_metadata,
        initial_metadata->idx.named.grpc_internal_encoding_request);
  } else {
    /* If no algorithm was found in the metadata and we aren't
     * exceptionally skipping compression, fall back to the channel
     * default */
    calld->compression_algorithm = channeld->default_compression_algorithm;
    *has_compression_algorithm = true;
  }

  grpc_error *error = GRPC_ERROR_NONE;
  /* hint compression algorithm */
  if (calld->compression_algorithm != GRPC_COMPRESS_NONE) {
    error = grpc_metadata_batch_add_tail(
        exec_ctx, initial_metadata, &calld->compression_algorithm_storage,
        grpc_compression_encoding_mdelem(calld->compression_algorithm));
  }

  if (error != GRPC_ERROR_NONE) return error;

  /* convey supported compression algorithms */
  error = grpc_metadata_batch_add_tail(
      exec_ctx, initial_metadata, &calld->accept_encoding_storage,
      GRPC_MDELEM_ACCEPT_ENCODING_FOR_ALGORITHMS(
          channeld->supported_compression_algorithms));

  return error;
}
Пример #4
0
static void on_credentials_metadata(grpc_exec_ctx *exec_ctx, void *user_data,
                                    grpc_credentials_md *md_elems,
                                    size_t num_md,
                                    grpc_credentials_status status) {
  grpc_call_element *elem = (grpc_call_element *)user_data;
  call_data *calld = elem->call_data;
  channel_data *chand = elem->channel_data;
  grpc_transport_stream_op *op = &calld->op;
  grpc_metadata_batch *mdb;
  size_t i;
  reset_service_url(calld);
  if (status != GRPC_CREDENTIALS_OK) {
    bubble_up_error(exec_ctx, elem, GRPC_STATUS_UNAUTHENTICATED,
                    "Credentials failed to get metadata.");
    return;
  }
  GPR_ASSERT(num_md <= MAX_CREDENTIALS_METADATA_COUNT);
  GPR_ASSERT(op->send_ops && op->send_ops->nops > calld->op_md_idx &&
             op->send_ops->ops[calld->op_md_idx].type == GRPC_OP_METADATA);
  mdb = &op->send_ops->ops[calld->op_md_idx].data.metadata;
  for (i = 0; i < num_md; i++) {
    grpc_metadata_batch_add_tail(
        mdb, &calld->md_links[i],
        grpc_mdelem_from_slices(chand->md_ctx, gpr_slice_ref(md_elems[i].key),
                                gpr_slice_ref(md_elems[i].value)));
  }
  grpc_call_next_op(exec_ctx, elem, op);
}
static void on_credentials_metadata(grpc_exec_ctx *exec_ctx, void *user_data,
                                    grpc_credentials_md *md_elems,
                                    size_t num_md,
                                    grpc_credentials_status status,
                                    const char *error_details) {
  grpc_call_element *elem = (grpc_call_element *)user_data;
  call_data *calld = elem->call_data;
  grpc_transport_stream_op *op = &calld->op;
  grpc_metadata_batch *mdb;
  size_t i;
  reset_auth_metadata_context(&calld->auth_md_context);
  if (status != GRPC_CREDENTIALS_OK) {
    bubble_up_error(exec_ctx, elem, GRPC_STATUS_UNAUTHENTICATED,
                    (error_details != NULL && strlen(error_details) > 0)
                        ? error_details
                        : "Credentials failed to get metadata.");
    return;
  }
  GPR_ASSERT(num_md <= MAX_CREDENTIALS_METADATA_COUNT);
  GPR_ASSERT(op->send_initial_metadata != NULL);
  mdb = op->send_initial_metadata;
  for (i = 0; i < num_md; i++) {
    grpc_metadata_batch_add_tail(
        mdb, &calld->md_links[i],
        grpc_mdelem_from_slices(gpr_slice_ref(md_elems[i].key),
                                gpr_slice_ref(md_elems[i].value)));
  }
  grpc_call_next_op(exec_ctx, elem, op);
}
static void hs_mutate_op(grpc_call_element *elem,
                         grpc_transport_stream_op *op) {
  /* grab pointers to our data from the call element */
  call_data *calld = elem->call_data;
  channel_data *channeld = elem->channel_data;
  size_t i;

  if (op->send_ops && !calld->sent_status) {
    size_t nops = op->send_ops->nops;
    grpc_stream_op *ops = op->send_ops->ops;
    for (i = 0; i < nops; i++) {
      grpc_stream_op *op = &ops[i];
      if (op->type != GRPC_OP_METADATA) continue;
      calld->sent_status = 1;
      grpc_metadata_batch_add_head(&op->data.metadata, &calld->status,
                                   GRPC_MDELEM_REF(channeld->status_ok));
      grpc_metadata_batch_add_tail(&op->data.metadata, &calld->content_type,
                                   GRPC_MDELEM_REF(channeld->content_type));
      break;
    }
  }

  if (op->recv_ops && !calld->got_initial_metadata) {
    /* substitute our callback for the higher callback */
    calld->recv_ops = op->recv_ops;
    calld->on_done_recv = op->on_done_recv;
    op->on_done_recv = &calld->hs_on_recv;
  }
}
Пример #7
0
static void on_credentials_metadata(grpc_exec_ctx *exec_ctx, void *arg,
                                    grpc_error *input_error) {
  grpc_transport_stream_op_batch *batch = (grpc_transport_stream_op_batch *)arg;
  grpc_call_element *elem = batch->handler_private.extra_arg;
  call_data *calld = elem->call_data;
  reset_auth_metadata_context(&calld->auth_md_context);
  grpc_error *error = GRPC_ERROR_REF(input_error);
  if (error == GRPC_ERROR_NONE) {
    GPR_ASSERT(calld->md_array.size <= MAX_CREDENTIALS_METADATA_COUNT);
    GPR_ASSERT(batch->send_initial_metadata);
    grpc_metadata_batch *mdb =
        batch->payload->send_initial_metadata.send_initial_metadata;
    for (size_t i = 0; i < calld->md_array.size; ++i) {
      add_error(&error, grpc_metadata_batch_add_tail(
                            exec_ctx, mdb, &calld->md_links[i],
                            GRPC_MDELEM_REF(calld->md_array.md[i])));
    }
  }
  if (error == GRPC_ERROR_NONE) {
    grpc_call_next_op(exec_ctx, elem, batch);
  } else {
    error = grpc_error_set_int(error, GRPC_ERROR_INT_GRPC_STATUS,
                               GRPC_STATUS_UNAUTHENTICATED);
    grpc_transport_stream_op_batch_finish_with_failure(exec_ctx, batch, error);
  }
}
Пример #8
0
/* add lb_token of selected subchannel (address) to the call's initial
 * metadata */
static void initial_metadata_add_lb_token(
    grpc_metadata_batch *initial_metadata,
    grpc_linked_mdelem *lb_token_mdelem_storage, grpc_mdelem *lb_token) {
  GPR_ASSERT(lb_token_mdelem_storage != NULL);
  GPR_ASSERT(lb_token != NULL);
  grpc_metadata_batch_add_tail(initial_metadata, lb_token_mdelem_storage,
                               lb_token);
}
Пример #9
0
static void hc_mutate_op(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
                         grpc_transport_stream_op *op) {
  /* grab pointers to our data from the call element */
  call_data *calld = elem->call_data;
  channel_data *channeld = elem->channel_data;

  if (op->send_initial_metadata != NULL) {
    /* Decide which HTTP VERB to use. We use GET if the request is marked
    cacheable, and the operation contains both initial metadata and send
    message, and the payload is below the size threshold, and all the data
    for this request is immediately available. */
    grpc_mdelem *method = GRPC_MDELEM_METHOD_POST;
    if ((op->send_initial_metadata_flags &
         GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) &&
        op->send_message != NULL &&
        op->send_message->length < channeld->max_payload_size_for_get) {
      method = GRPC_MDELEM_METHOD_GET;
      /* The following write to calld->send_message_blocked isn't racy with
      reads in hc_start_transport_op (which deals with SEND_MESSAGE ops) because
      being here means ops->send_message is not NULL, which is primarily
      guarding the read there. */
      calld->send_message_blocked = true;
    } else if (op->send_initial_metadata_flags &
               GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) {
      method = GRPC_MDELEM_METHOD_PUT;
    }

    /* Attempt to read the data from send_message and create a header field. */
    if (method == GRPC_MDELEM_METHOD_GET) {
      /* allocate memory to hold the entire payload */
      calld->payload_bytes = gpr_malloc(op->send_message->length);

      /* read slices of send_message and copy into payload_bytes */
      calld->send_op = *op;
      calld->send_length = op->send_message->length;
      calld->send_flags = op->send_message->flags;
      continue_send_message(exec_ctx, elem);

      if (calld->send_message_blocked == false) {
        /* when all the send_message data is available, then create a MDELEM and
        append to headers */
        grpc_mdelem *payload_bin = grpc_mdelem_from_metadata_strings(
            GRPC_MDSTR_GRPC_PAYLOAD_BIN,
            grpc_mdstr_from_buffer(calld->payload_bytes,
                                   op->send_message->length));
        grpc_metadata_batch_add_tail(op->send_initial_metadata,
                                     &calld->payload_bin, payload_bin);
        calld->on_complete = op->on_complete;
        op->on_complete = &calld->hc_on_complete;
        op->send_message = NULL;
      } else {
        /* Not all data is available. Fall back to POST. */
        gpr_log(GPR_DEBUG,
                "Request is marked Cacheable but not all data is available.\
                            Falling back to POST");
        method = GRPC_MDELEM_METHOD_POST;
      }
    }
Пример #10
0
static void hc_mutate_op(grpc_call_element *elem,
                         grpc_transport_stream_op *op) {
  /* grab pointers to our data from the call element */
  call_data *calld = elem->call_data;
  channel_data *channeld = elem->channel_data;
  size_t i;
  if (op->send_ops && !calld->sent_initial_metadata) {
    size_t nops = op->send_ops->nops;
    grpc_stream_op *ops = op->send_ops->ops;
    for (i = 0; i < nops; i++) {
      grpc_stream_op *op = &ops[i];
      if (op->type != GRPC_OP_METADATA) continue;
      calld->sent_initial_metadata = 1;
      grpc_metadata_batch_filter(&op->data.metadata, client_strip_filter, elem);
      /* Send : prefixed headers, which have to be before any application
         layer headers. */
      grpc_metadata_batch_add_head(&op->data.metadata, &calld->method,
                                   GRPC_MDELEM_REF(channeld->method));
      grpc_metadata_batch_add_head(&op->data.metadata, &calld->scheme,
                                   GRPC_MDELEM_REF(channeld->scheme));
      grpc_metadata_batch_add_tail(&op->data.metadata, &calld->te_trailers,
                                   GRPC_MDELEM_REF(channeld->te_trailers));
      grpc_metadata_batch_add_tail(&op->data.metadata, &calld->content_type,
                                   GRPC_MDELEM_REF(channeld->content_type));
      grpc_metadata_batch_add_tail(&op->data.metadata, &calld->user_agent,
                                   GRPC_MDELEM_REF(channeld->user_agent));
      break;
    }
  }

  if (op->recv_ops && !calld->got_initial_metadata) {
    /* substitute our callback for the higher callback */
    calld->recv_ops = op->recv_ops;
    calld->on_done_recv = op->on_done_recv;
    op->on_done_recv = &calld->hc_on_recv;
  }
}
Пример #11
0
static void hs_mutate_op(grpc_call_element *elem,
                         grpc_transport_stream_op *op) {
  /* grab pointers to our data from the call element */
  call_data *calld = elem->call_data;

  if (op->send_initial_metadata != NULL && !calld->sent_status) {
    calld->sent_status = 1;
    grpc_metadata_batch_add_head(op->send_initial_metadata, &calld->status,
                                 GRPC_MDELEM_STATUS_200);
    grpc_metadata_batch_add_tail(
        op->send_initial_metadata, &calld->content_type,
        GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC);
  }

  if (op->recv_initial_metadata) {
    /* substitute our callback for the higher callback */
    GPR_ASSERT(op->recv_idempotent_request != NULL);
    calld->recv_initial_metadata = op->recv_initial_metadata;
    calld->recv_idempotent_request = op->recv_idempotent_request;
    calld->on_done_recv = op->recv_initial_metadata_ready;
    op->recv_initial_metadata_ready = &calld->hs_on_recv;
  }
}
Пример #12
0
static void on_credentials_metadata(grpc_exec_ctx *exec_ctx, void *user_data,
                                    grpc_credentials_md *md_elems,
                                    size_t num_md,
                                    grpc_credentials_status status,
                                    const char *error_details) {
  grpc_call_element *elem = (grpc_call_element *)user_data;
  call_data *calld = elem->call_data;
  grpc_transport_stream_op *op = &calld->op;
  grpc_metadata_batch *mdb;
  size_t i;
  reset_auth_metadata_context(&calld->auth_md_context);
  grpc_error *error = GRPC_ERROR_NONE;
  if (status != GRPC_CREDENTIALS_OK) {
    error = grpc_error_set_int(
        GRPC_ERROR_CREATE(error_details != NULL && strlen(error_details) > 0
                              ? error_details
                              : "Credentials failed to get metadata."),
        GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAUTHENTICATED);
  } else {
    GPR_ASSERT(num_md <= MAX_CREDENTIALS_METADATA_COUNT);
    GPR_ASSERT(op->send_initial_metadata != NULL);
    mdb = op->send_initial_metadata;
    for (i = 0; i < num_md; i++) {
      add_error(&error,
                grpc_metadata_batch_add_tail(
                    exec_ctx, mdb, &calld->md_links[i],
                    grpc_mdelem_from_slices(
                        exec_ctx, grpc_slice_ref_internal(md_elems[i].key),
                        grpc_slice_ref_internal(md_elems[i].value))));
    }
  }
  if (error == GRPC_ERROR_NONE) {
    grpc_call_next_op(exec_ctx, elem, op);
  } else {
    grpc_transport_stream_op_finish_with_failure(exec_ctx, op, error);
  }
}
Пример #13
0
/** Filter's "main" function, called for any incoming grpc_transport_stream_op
 * instance that holds a non-zero number of send operations, accesible to this
 * function in \a send_ops.  */
static void process_send_ops(grpc_call_element *elem,
                             grpc_stream_op_buffer *send_ops) {
  call_data *calld = elem->call_data;
  channel_data *channeld = elem->channel_data;
  size_t i;
  int did_compress = 0;

  /* In streaming calls, we need to reset the previously accumulated slices */
  gpr_slice_buffer_reset_and_unref(&calld->slices);
  for (i = 0; i < send_ops->nops; ++i) {
    grpc_stream_op *sop = &send_ops->ops[i];
    switch (sop->type) {
      case GRPC_OP_BEGIN_MESSAGE:
        /* buffer up slices until we've processed all the expected ones (as
         * given by GRPC_OP_BEGIN_MESSAGE) */
        calld->remaining_slice_bytes = sop->data.begin_message.length;
        if (sop->data.begin_message.flags & GRPC_WRITE_NO_COMPRESS) {
          calld->has_compression_algorithm = 1; /* GPR_TRUE */
          calld->compression_algorithm = GRPC_COMPRESS_NONE;
        }
        break;
      case GRPC_OP_METADATA:
        if (!calld->written_initial_metadata) {
          /* Parse incoming request for compression. If any, it'll be available
           * at calld->compression_algorithm */
          grpc_metadata_batch_filter(&(sop->data.metadata),
                                     compression_md_filter, elem);
          if (!calld->has_compression_algorithm) {
            /* If no algorithm was found in the metadata and we aren't
             * exceptionally skipping compression, fall back to the channel
             * default */
            calld->compression_algorithm =
                channeld->default_compression_algorithm;
            calld->has_compression_algorithm = 1; /* GPR_TRUE */
          }
          /* hint compression algorithm */
          grpc_metadata_batch_add_tail(
              &(sop->data.metadata), &calld->compression_algorithm_storage,
              GRPC_MDELEM_REF(channeld->mdelem_compression_algorithms
                                  [calld->compression_algorithm]));

          /* convey supported compression algorithms */
          grpc_metadata_batch_add_tail(
              &(sop->data.metadata), &calld->accept_encoding_storage,
              GRPC_MDELEM_REF(channeld->mdelem_accept_encoding));

          calld->written_initial_metadata = 1; /* GPR_TRUE */
        }
        break;
      case GRPC_OP_SLICE:
        if (skip_compression(channeld, calld)) continue;
        GPR_ASSERT(calld->remaining_slice_bytes > 0);
        /* Increase input ref count, gpr_slice_buffer_add takes ownership.  */
        gpr_slice_buffer_add(&calld->slices, gpr_slice_ref(sop->data.slice));
        GPR_ASSERT(GPR_SLICE_LENGTH(sop->data.slice) >=
                   calld->remaining_slice_bytes);
        calld->remaining_slice_bytes -=
            (gpr_uint32)GPR_SLICE_LENGTH(sop->data.slice);
        if (calld->remaining_slice_bytes == 0) {
          did_compress =
              compress_send_sb(calld->compression_algorithm, &calld->slices);
        }
        break;
      case GRPC_NO_OP:
        break;
    }
  }

  /* Modify the send_ops stream_op_buffer depending on whether compression was
   * carried out */
  if (did_compress) {
    finish_compressed_sopb(send_ops, elem);
  }
}
Пример #14
0
static int fill_send_ops(grpc_call *call, grpc_transport_op *op) {
  grpc_ioreq_data data;
  grpc_metadata_batch mdb;
  size_t i;
  char status_str[GPR_LTOA_MIN_BUFSIZE];
  GPR_ASSERT(op->send_ops == NULL);

  switch (call->write_state) {
    case WRITE_STATE_INITIAL:
      if (!is_op_live(call, GRPC_IOREQ_SEND_INITIAL_METADATA)) {
        break;
      }
      data = call->request_data[GRPC_IOREQ_SEND_INITIAL_METADATA];
      mdb.list = chain_metadata_from_app(call, data.send_metadata.count,
                                         data.send_metadata.metadata);
      mdb.garbage.head = mdb.garbage.tail = NULL;
      mdb.deadline = call->send_deadline;
      for (i = 0; i < call->send_initial_metadata_count; i++) {
        grpc_metadata_batch_link_head(&mdb, &call->send_initial_metadata[i]);
      }
      grpc_sopb_add_metadata(&call->send_ops, mdb);
      op->send_ops = &call->send_ops;
      op->bind_pollset = grpc_cq_pollset(call->cq);
      call->last_send_contains |= 1 << GRPC_IOREQ_SEND_INITIAL_METADATA;
      call->write_state = WRITE_STATE_STARTED;
      call->send_initial_metadata_count = 0;
    /* fall through intended */
    case WRITE_STATE_STARTED:
      if (is_op_live(call, GRPC_IOREQ_SEND_MESSAGE)) {
        data = call->request_data[GRPC_IOREQ_SEND_MESSAGE];
        grpc_sopb_add_begin_message(
            &call->send_ops, grpc_byte_buffer_length(data.send_message), 0);
        copy_byte_buffer_to_stream_ops(data.send_message, &call->send_ops);
        op->send_ops = &call->send_ops;
        call->last_send_contains |= 1 << GRPC_IOREQ_SEND_MESSAGE;
      }
      if (is_op_live(call, GRPC_IOREQ_SEND_CLOSE)) {
        op->is_last_send = 1;
        op->send_ops = &call->send_ops;
        call->last_send_contains |= 1 << GRPC_IOREQ_SEND_CLOSE;
        call->write_state = WRITE_STATE_WRITE_CLOSED;
        if (!call->is_client) {
          /* send trailing metadata */
          data = call->request_data[GRPC_IOREQ_SEND_TRAILING_METADATA];
          mdb.list = chain_metadata_from_app(call, data.send_metadata.count,
                                             data.send_metadata.metadata);
          mdb.garbage.head = mdb.garbage.tail = NULL;
          mdb.deadline = gpr_inf_future;
          /* send status */
          /* TODO(ctiller): cache common status values */
          data = call->request_data[GRPC_IOREQ_SEND_STATUS];
          gpr_ltoa(data.send_status.code, status_str);
          grpc_metadata_batch_add_tail(
              &mdb, &call->status_link,
              grpc_mdelem_from_metadata_strings(
                  call->metadata_context,
                  grpc_mdstr_ref(grpc_channel_get_status_string(call->channel)),
                  grpc_mdstr_from_string(call->metadata_context, status_str)));
          if (data.send_status.details) {
            grpc_metadata_batch_add_tail(
                &mdb, &call->details_link,
                grpc_mdelem_from_metadata_strings(
                    call->metadata_context,
                    grpc_mdstr_ref(
                        grpc_channel_get_message_string(call->channel)),
                    grpc_mdstr_from_string(call->metadata_context,
                                           data.send_status.details)));
          }
          grpc_sopb_add_metadata(&call->send_ops, mdb);
        }
      }
      break;
    case WRITE_STATE_WRITE_CLOSED:
      break;
  }
  if (op->send_ops) {
    op->on_done_send = call_on_done_send;
    op->send_user_data = call;
  }
  return op->send_ops != NULL;
}