コード例 #1
0
ファイル: security_handshaker.c プロジェクト: izouxv/grpc
static grpc_error *send_handshake_bytes_to_peer_locked(grpc_exec_ctx *exec_ctx,
                                                       security_handshaker *h) {
  // Get data to send.
  tsi_result result = TSI_OK;
  size_t offset = 0;
  do {
    size_t to_send_size = h->handshake_buffer_size - offset;
    result = tsi_handshaker_get_bytes_to_send_to_peer(
        h->handshaker, h->handshake_buffer + offset, &to_send_size);
    offset += to_send_size;
    if (result == TSI_INCOMPLETE_DATA) {
      h->handshake_buffer_size *= 2;
      h->handshake_buffer =
          gpr_realloc(h->handshake_buffer, h->handshake_buffer_size);
    }
  } while (result == TSI_INCOMPLETE_DATA);
  if (result != TSI_OK) {
    return grpc_set_tsi_error_result(GRPC_ERROR_CREATE("Handshake failed"),
                                     result);
  }
  // Send data.
  grpc_slice to_send =
      grpc_slice_from_copied_buffer((const char *)h->handshake_buffer, offset);
  grpc_slice_buffer_reset_and_unref(&h->outgoing);
  grpc_slice_buffer_add(&h->outgoing, to_send);
  grpc_endpoint_write(exec_ctx, h->args->endpoint, &h->outgoing,
                      &h->on_handshake_data_sent_to_peer);
  return GRPC_ERROR_NONE;
}
コード例 #2
0
ファイル: handshake.c プロジェクト: nerdrew/grpc
static void send_handshake_bytes_to_peer(grpc_exec_ctx *exec_ctx,
        grpc_security_handshake *h) {
    size_t offset = 0;
    tsi_result result = TSI_OK;
    gpr_slice to_send;

    do {
        size_t to_send_size = h->handshake_buffer_size - offset;
        result = tsi_handshaker_get_bytes_to_send_to_peer(
                     h->handshaker, h->handshake_buffer + offset, &to_send_size);
        offset += to_send_size;
        if (result == TSI_INCOMPLETE_DATA) {
            h->handshake_buffer_size *= 2;
            h->handshake_buffer =
                gpr_realloc(h->handshake_buffer, h->handshake_buffer_size);
        }
    } while (result == TSI_INCOMPLETE_DATA);

    if (result != TSI_OK) {
        security_handshake_done(exec_ctx, h,
                                grpc_set_tsi_error_result(
                                    GRPC_ERROR_CREATE("Handshake failed"), result));
        return;
    }

    to_send =
        gpr_slice_from_copied_buffer((const char *)h->handshake_buffer, offset);
    gpr_slice_buffer_reset_and_unref(&h->outgoing);
    gpr_slice_buffer_add(&h->outgoing, to_send);
    /* TODO(klempner,jboeuf): This should probably use the client setup
       deadline */
    grpc_endpoint_write(exec_ctx, h->wrapped_endpoint, &h->outgoing,
                        &h->on_handshake_data_sent_to_peer);
}
コード例 #3
0
ファイル: handshake.c プロジェクト: nerdrew/grpc
static void on_peer_checked(grpc_exec_ctx *exec_ctx, void *user_data,
                            grpc_security_status status,
                            grpc_auth_context *auth_context) {
    grpc_security_handshake *h = user_data;
    tsi_frame_protector *protector;
    tsi_result result;
    if (status != GRPC_SECURITY_OK) {
        security_handshake_done(
            exec_ctx, h,
            grpc_error_set_int(GRPC_ERROR_CREATE("Error checking peer."),
                               GRPC_ERROR_INT_SECURITY_STATUS, status));
        return;
    }
    h->auth_context = GRPC_AUTH_CONTEXT_REF(auth_context, "handshake");
    result =
        tsi_handshaker_create_frame_protector(h->handshaker, NULL, &protector);
    if (result != TSI_OK) {
        security_handshake_done(
            exec_ctx, h,
            grpc_set_tsi_error_result(
                GRPC_ERROR_CREATE("Frame protector creation failed"), result));
        return;
    }
    h->secure_endpoint =
        grpc_secure_endpoint_create(protector, h->wrapped_endpoint,
                                    h->left_overs.slices, h->left_overs.count);
    h->left_overs.count = 0;
    h->left_overs.length = 0;
    security_handshake_done(exec_ctx, h, GRPC_ERROR_NONE);
    return;
}
コード例 #4
0
ファイル: security_handshaker.c プロジェクト: aaronjheng/grpc
static void on_peer_checked(grpc_exec_ctx *exec_ctx, void *arg,
                            grpc_error *error) {
  security_handshaker *h = arg;
  gpr_mu_lock(&h->mu);
  if (error != GRPC_ERROR_NONE || h->shutdown) {
    security_handshake_failed_locked(exec_ctx, h, GRPC_ERROR_REF(error));
    goto done;
  }
  // Create frame protector.
  tsi_frame_protector *protector;
  tsi_result result = tsi_handshaker_result_create_frame_protector(
      h->handshaker_result, NULL, &protector);
  if (result != TSI_OK) {
    error = grpc_set_tsi_error_result(
        GRPC_ERROR_CREATE_FROM_STATIC_STRING("Frame protector creation failed"),
        result);
    security_handshake_failed_locked(exec_ctx, h, error);
    goto done;
  }
  // Get unused bytes.
  unsigned char *unused_bytes = NULL;
  size_t unused_bytes_size = 0;
  result = tsi_handshaker_result_get_unused_bytes(
      h->handshaker_result, &unused_bytes, &unused_bytes_size);
  // Create secure endpoint.
  if (unused_bytes_size > 0) {
    grpc_slice slice =
        grpc_slice_from_copied_buffer((char *)unused_bytes, unused_bytes_size);
    h->args->endpoint =
        grpc_secure_endpoint_create(protector, h->args->endpoint, &slice, 1);
    grpc_slice_unref_internal(exec_ctx, slice);
  } else {
    h->args->endpoint =
        grpc_secure_endpoint_create(protector, h->args->endpoint, NULL, 0);
  }
  tsi_handshaker_result_destroy(h->handshaker_result);
  h->handshaker_result = NULL;
  // Clear out the read buffer before it gets passed to the transport.
  grpc_slice_buffer_reset_and_unref_internal(exec_ctx, h->args->read_buffer);
  // Add auth context to channel args.
  grpc_arg auth_context_arg = grpc_auth_context_to_arg(h->auth_context);
  grpc_channel_args *tmp_args = h->args->args;
  h->args->args =
      grpc_channel_args_copy_and_add(tmp_args, &auth_context_arg, 1);
  grpc_channel_args_destroy(exec_ctx, tmp_args);
  // Invoke callback.
  GRPC_CLOSURE_SCHED(exec_ctx, h->on_handshake_done, GRPC_ERROR_NONE);
  // Set shutdown to true so that subsequent calls to
  // security_handshaker_shutdown() do nothing.
  h->shutdown = true;
done:
  gpr_mu_unlock(&h->mu);
  security_handshaker_unref(exec_ctx, h);
}
コード例 #5
0
ファイル: security_handshaker.c プロジェクト: izouxv/grpc
static grpc_error *check_peer_locked(grpc_exec_ctx *exec_ctx,
                                     security_handshaker *h) {
  tsi_peer peer;
  tsi_result result = tsi_handshaker_extract_peer(h->handshaker, &peer);
  if (result != TSI_OK) {
    return grpc_set_tsi_error_result(
        GRPC_ERROR_CREATE("Peer extraction failed"), result);
  }
  grpc_security_connector_check_peer(exec_ctx, h->connector, peer,
                                     &h->auth_context, &h->on_peer_checked);
  return GRPC_ERROR_NONE;
}
コード例 #6
0
ファイル: handshake.c プロジェクト: nerdrew/grpc
static void check_peer(grpc_exec_ctx *exec_ctx, grpc_security_handshake *h) {
    tsi_peer peer;
    tsi_result result = tsi_handshaker_extract_peer(h->handshaker, &peer);

    if (result != TSI_OK) {
        security_handshake_done(
            exec_ctx, h, grpc_set_tsi_error_result(
                GRPC_ERROR_CREATE("Peer extraction failed"), result));
        return;
    }
    grpc_security_connector_check_peer(exec_ctx, h->connector, peer,
                                       on_peer_checked, h);
}
コード例 #7
0
ファイル: security_handshaker.c プロジェクト: izouxv/grpc
static void on_peer_checked(grpc_exec_ctx *exec_ctx, void *arg,
                            grpc_error *error) {
  security_handshaker *h = arg;
  gpr_mu_lock(&h->mu);
  if (error != GRPC_ERROR_NONE || h->shutdown) {
    security_handshake_failed_locked(exec_ctx, h, GRPC_ERROR_REF(error));
    goto done;
  }
  // Get frame protector.
  tsi_frame_protector *protector;
  tsi_result result =
      tsi_handshaker_create_frame_protector(h->handshaker, NULL, &protector);
  if (result != TSI_OK) {
    error = grpc_set_tsi_error_result(
        GRPC_ERROR_CREATE("Frame protector creation failed"), result);
    security_handshake_failed_locked(exec_ctx, h, error);
    goto done;
  }
  // Success.
  // Create secure endpoint.
  h->args->endpoint = grpc_secure_endpoint_create(
      protector, h->args->endpoint, h->left_overs.slices, h->left_overs.count);
  h->left_overs.count = 0;
  h->left_overs.length = 0;
  // Clear out the read buffer before it gets passed to the transport,
  // since any excess bytes were already copied to h->left_overs.
  grpc_slice_buffer_reset_and_unref(h->args->read_buffer);
  // Add auth context to channel args.
  grpc_arg auth_context_arg = grpc_auth_context_to_arg(h->auth_context);
  grpc_channel_args *tmp_args = h->args->args;
  h->args->args =
      grpc_channel_args_copy_and_add(tmp_args, &auth_context_arg, 1);
  grpc_channel_args_destroy(tmp_args);
  // Invoke callback.
  grpc_exec_ctx_sched(exec_ctx, h->on_handshake_done, GRPC_ERROR_NONE, NULL);
  // Set shutdown to true so that subsequent calls to
  // security_handshaker_shutdown() do nothing.
  h->shutdown = true;
done:
  gpr_mu_unlock(&h->mu);
  security_handshaker_unref(exec_ctx, h);
}
コード例 #8
0
ファイル: security_handshaker.c プロジェクト: aaronjheng/grpc
static grpc_error *on_handshake_next_done_locked(
    grpc_exec_ctx *exec_ctx, security_handshaker *h, tsi_result result,
    const unsigned char *bytes_to_send, size_t bytes_to_send_size,
    tsi_handshaker_result *handshaker_result) {
  grpc_error *error = GRPC_ERROR_NONE;
  // Read more if we need to.
  if (result == TSI_INCOMPLETE_DATA) {
    GPR_ASSERT(bytes_to_send_size == 0);
    grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
                       &h->on_handshake_data_received_from_peer);
    return error;
  }
  if (result != TSI_OK) {
    return grpc_set_tsi_error_result(
        GRPC_ERROR_CREATE_FROM_STATIC_STRING("Handshake failed"), result);
  }
  // Update handshaker result.
  if (handshaker_result != NULL) {
    GPR_ASSERT(h->handshaker_result == NULL);
    h->handshaker_result = handshaker_result;
  }
  if (bytes_to_send_size > 0) {
    // Send data to peer, if needed.
    grpc_slice to_send = grpc_slice_from_copied_buffer(
        (const char *)bytes_to_send, bytes_to_send_size);
    grpc_slice_buffer_reset_and_unref_internal(exec_ctx, &h->outgoing);
    grpc_slice_buffer_add(&h->outgoing, to_send);
    grpc_endpoint_write(exec_ctx, h->args->endpoint, &h->outgoing,
                        &h->on_handshake_data_sent_to_peer);
  } else if (handshaker_result == NULL) {
    // There is nothing to send, but need to read from peer.
    grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
                       &h->on_handshake_data_received_from_peer);
  } else {
    // Handshake has finished, check peer and so on.
    error = check_peer_locked(exec_ctx, h);
  }
  return error;
}
コード例 #9
0
ファイル: handshake.c プロジェクト: nerdrew/grpc
static void on_handshake_data_received_from_peer(grpc_exec_ctx *exec_ctx,
        void *handshake,
        grpc_error *error) {
    grpc_security_handshake *h = handshake;
    size_t consumed_slice_size = 0;
    tsi_result result = TSI_OK;
    size_t i;
    size_t num_left_overs;
    int has_left_overs_in_current_slice = 0;

    if (error != GRPC_ERROR_NONE) {
        security_handshake_done(
            exec_ctx, h,
            GRPC_ERROR_CREATE_REFERENCING("Handshake read failed", &error, 1));
        return;
    }

    for (i = 0; i < h->incoming.count; i++) {
        consumed_slice_size = GPR_SLICE_LENGTH(h->incoming.slices[i]);
        result = tsi_handshaker_process_bytes_from_peer(
                     h->handshaker, GPR_SLICE_START_PTR(h->incoming.slices[i]),
                     &consumed_slice_size);
        if (!tsi_handshaker_is_in_progress(h->handshaker)) break;
    }

    if (tsi_handshaker_is_in_progress(h->handshaker)) {
        /* We may need more data. */
        if (result == TSI_INCOMPLETE_DATA) {
            grpc_endpoint_read(exec_ctx, h->wrapped_endpoint, &h->incoming,
                               &h->on_handshake_data_received_from_peer);
            return;
        } else {
            send_handshake_bytes_to_peer(exec_ctx, h);
            return;
        }
    }

    if (result != TSI_OK) {
        security_handshake_done(exec_ctx, h,
                                grpc_set_tsi_error_result(
                                    GRPC_ERROR_CREATE("Handshake failed"), result));
        return;
    }

    /* Handshake is done and successful this point. */
    has_left_overs_in_current_slice =
        (consumed_slice_size < GPR_SLICE_LENGTH(h->incoming.slices[i]));
    num_left_overs =
        (has_left_overs_in_current_slice ? 1 : 0) + h->incoming.count - i - 1;
    if (num_left_overs == 0) {
        check_peer(exec_ctx, h);
        return;
    }

    /* Put the leftovers in our buffer (ownership transfered). */
    if (has_left_overs_in_current_slice) {
        gpr_slice_buffer_add(
            &h->left_overs,
            gpr_slice_split_tail(&h->incoming.slices[i], consumed_slice_size));
        gpr_slice_unref(
            h->incoming.slices[i]); /* split_tail above increments refcount. */
    }
    gpr_slice_buffer_addn(
        &h->left_overs, &h->incoming.slices[i + 1],
        num_left_overs - (size_t)has_left_overs_in_current_slice);
    check_peer(exec_ctx, h);
}
コード例 #10
0
ファイル: secure_endpoint.c プロジェクト: Alex-duzhichao/grpc
static void endpoint_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *secure_ep,
                           gpr_slice_buffer *slices, grpc_closure *cb) {
  unsigned i;
  tsi_result result = TSI_OK;
  secure_endpoint *ep = (secure_endpoint *)secure_ep;
  uint8_t *cur = GPR_SLICE_START_PTR(ep->write_staging_buffer);
  uint8_t *end = GPR_SLICE_END_PTR(ep->write_staging_buffer);

  gpr_slice_buffer_reset_and_unref(&ep->output_buffer);

  if (false && grpc_trace_secure_endpoint) {
    for (i = 0; i < slices->count; i++) {
      char *data =
          gpr_dump_slice(slices->slices[i], GPR_DUMP_HEX | GPR_DUMP_ASCII);
      gpr_log(GPR_DEBUG, "WRITE %p: %s", ep, data);
      gpr_free(data);
    }
  }

  for (i = 0; i < slices->count; i++) {
    gpr_slice plain = slices->slices[i];
    uint8_t *message_bytes = GPR_SLICE_START_PTR(plain);
    size_t message_size = GPR_SLICE_LENGTH(plain);
    while (message_size > 0) {
      size_t protected_buffer_size_to_send = (size_t)(end - cur);
      size_t processed_message_size = message_size;
      gpr_mu_lock(&ep->protector_mu);
      result = tsi_frame_protector_protect(ep->protector, message_bytes,
                                           &processed_message_size, cur,
                                           &protected_buffer_size_to_send);
      gpr_mu_unlock(&ep->protector_mu);
      if (result != TSI_OK) {
        gpr_log(GPR_ERROR, "Encryption error: %s",
                tsi_result_to_string(result));
        break;
      }
      message_bytes += processed_message_size;
      message_size -= processed_message_size;
      cur += protected_buffer_size_to_send;

      if (cur == end) {
        flush_write_staging_buffer(ep, &cur, &end);
      }
    }
    if (result != TSI_OK) break;
  }
  if (result == TSI_OK) {
    size_t still_pending_size;
    do {
      size_t protected_buffer_size_to_send = (size_t)(end - cur);
      gpr_mu_lock(&ep->protector_mu);
      result = tsi_frame_protector_protect_flush(ep->protector, cur,
                                                 &protected_buffer_size_to_send,
                                                 &still_pending_size);
      gpr_mu_unlock(&ep->protector_mu);
      if (result != TSI_OK) break;
      cur += protected_buffer_size_to_send;
      if (cur == end) {
        flush_write_staging_buffer(ep, &cur, &end);
      }
    } while (still_pending_size > 0);
    if (cur != GPR_SLICE_START_PTR(ep->write_staging_buffer)) {
      gpr_slice_buffer_add(
          &ep->output_buffer,
          gpr_slice_split_head(
              &ep->write_staging_buffer,
              (size_t)(cur - GPR_SLICE_START_PTR(ep->write_staging_buffer))));
    }
  }

  if (result != TSI_OK) {
    /* TODO(yangg) do different things according to the error type? */
    gpr_slice_buffer_reset_and_unref(&ep->output_buffer);
    grpc_exec_ctx_sched(
        exec_ctx, cb,
        grpc_set_tsi_error_result(GRPC_ERROR_CREATE("Wrap failed"), result),
        NULL);
    return;
  }

  grpc_endpoint_write(exec_ctx, ep->wrapped_ep, &ep->output_buffer, cb);
}
コード例 #11
0
ファイル: secure_endpoint.c プロジェクト: Alex-duzhichao/grpc
static void on_read(grpc_exec_ctx *exec_ctx, void *user_data,
                    grpc_error *error) {
  unsigned i;
  uint8_t keep_looping = 0;
  tsi_result result = TSI_OK;
  secure_endpoint *ep = (secure_endpoint *)user_data;
  uint8_t *cur = GPR_SLICE_START_PTR(ep->read_staging_buffer);
  uint8_t *end = GPR_SLICE_END_PTR(ep->read_staging_buffer);

  if (error != GRPC_ERROR_NONE) {
    gpr_slice_buffer_reset_and_unref(ep->read_buffer);
    call_read_cb(exec_ctx, ep, GRPC_ERROR_CREATE_REFERENCING(
                                   "Secure read failed", &error, 1));
    return;
  }

  /* TODO(yangg) check error, maybe bail out early */
  for (i = 0; i < ep->source_buffer.count; i++) {
    gpr_slice encrypted = ep->source_buffer.slices[i];
    uint8_t *message_bytes = GPR_SLICE_START_PTR(encrypted);
    size_t message_size = GPR_SLICE_LENGTH(encrypted);

    while (message_size > 0 || keep_looping) {
      size_t unprotected_buffer_size_written = (size_t)(end - cur);
      size_t processed_message_size = message_size;
      gpr_mu_lock(&ep->protector_mu);
      result = tsi_frame_protector_unprotect(ep->protector, message_bytes,
                                             &processed_message_size, cur,
                                             &unprotected_buffer_size_written);
      gpr_mu_unlock(&ep->protector_mu);
      if (result != TSI_OK) {
        gpr_log(GPR_ERROR, "Decryption error: %s",
                tsi_result_to_string(result));
        break;
      }
      message_bytes += processed_message_size;
      message_size -= processed_message_size;
      cur += unprotected_buffer_size_written;

      if (cur == end) {
        flush_read_staging_buffer(ep, &cur, &end);
        /* Force to enter the loop again to extract buffered bytes in protector.
           The bytes could be buffered because of running out of staging_buffer.
           If this happens at the end of all slices, doing another unprotect
           avoids leaving data in the protector. */
        keep_looping = 1;
      } else if (unprotected_buffer_size_written > 0) {
        keep_looping = 1;
      } else {
        keep_looping = 0;
      }
    }
    if (result != TSI_OK) break;
  }

  if (cur != GPR_SLICE_START_PTR(ep->read_staging_buffer)) {
    gpr_slice_buffer_add(
        ep->read_buffer,
        gpr_slice_split_head(
            &ep->read_staging_buffer,
            (size_t)(cur - GPR_SLICE_START_PTR(ep->read_staging_buffer))));
  }

  /* TODO(yangg) experiment with moving this block after read_cb to see if it
     helps latency */
  gpr_slice_buffer_reset_and_unref(&ep->source_buffer);

  if (result != TSI_OK) {
    gpr_slice_buffer_reset_and_unref(ep->read_buffer);
    call_read_cb(exec_ctx, ep, grpc_set_tsi_error_result(
                                   GRPC_ERROR_CREATE("Unwrap failed"), result));
    return;
  }

  call_read_cb(exec_ctx, ep, GRPC_ERROR_NONE);
}
コード例 #12
0
ファイル: security_handshaker.c プロジェクト: izouxv/grpc
static void on_handshake_data_received_from_peer(grpc_exec_ctx *exec_ctx,
                                                 void *arg, grpc_error *error) {
  security_handshaker *h = arg;
  gpr_mu_lock(&h->mu);
  if (error != GRPC_ERROR_NONE || h->shutdown) {
    security_handshake_failed_locked(
        exec_ctx, h,
        GRPC_ERROR_CREATE_REFERENCING("Handshake read failed", &error, 1));
    gpr_mu_unlock(&h->mu);
    security_handshaker_unref(exec_ctx, h);
    return;
  }
  // Process received data.
  tsi_result result = TSI_OK;
  size_t consumed_slice_size = 0;
  size_t i;
  for (i = 0; i < h->args->read_buffer->count; i++) {
    consumed_slice_size = GRPC_SLICE_LENGTH(h->args->read_buffer->slices[i]);
    result = tsi_handshaker_process_bytes_from_peer(
        h->handshaker, GRPC_SLICE_START_PTR(h->args->read_buffer->slices[i]),
        &consumed_slice_size);
    if (!tsi_handshaker_is_in_progress(h->handshaker)) break;
  }
  if (tsi_handshaker_is_in_progress(h->handshaker)) {
    /* We may need more data. */
    if (result == TSI_INCOMPLETE_DATA) {
      grpc_endpoint_read(exec_ctx, h->args->endpoint, h->args->read_buffer,
                         &h->on_handshake_data_received_from_peer);
      goto done;
    } else {
      error = send_handshake_bytes_to_peer_locked(exec_ctx, h);
      if (error != GRPC_ERROR_NONE) {
        security_handshake_failed_locked(exec_ctx, h, error);
        gpr_mu_unlock(&h->mu);
        security_handshaker_unref(exec_ctx, h);
        return;
      }
      goto done;
    }
  }
  if (result != TSI_OK) {
    security_handshake_failed_locked(
        exec_ctx, h, grpc_set_tsi_error_result(
                         GRPC_ERROR_CREATE("Handshake failed"), result));
    gpr_mu_unlock(&h->mu);
    security_handshaker_unref(exec_ctx, h);
    return;
  }
  /* Handshake is done and successful this point. */
  bool has_left_overs_in_current_slice =
      (consumed_slice_size <
       GRPC_SLICE_LENGTH(h->args->read_buffer->slices[i]));
  size_t num_left_overs = (has_left_overs_in_current_slice ? 1 : 0) +
                          h->args->read_buffer->count - i - 1;
  if (num_left_overs > 0) {
    /* Put the leftovers in our buffer (ownership transfered). */
    if (has_left_overs_in_current_slice) {
      grpc_slice_buffer_add(
          &h->left_overs,
          grpc_slice_split_tail(&h->args->read_buffer->slices[i],
                                consumed_slice_size));
      /* split_tail above increments refcount. */
      grpc_slice_unref(h->args->read_buffer->slices[i]);
    }
    grpc_slice_buffer_addn(
        &h->left_overs, &h->args->read_buffer->slices[i + 1],
        num_left_overs - (size_t)has_left_overs_in_current_slice);
  }
  // Check peer.
  error = check_peer_locked(exec_ctx, h);
  if (error != GRPC_ERROR_NONE) {
    security_handshake_failed_locked(exec_ctx, h, error);
    gpr_mu_unlock(&h->mu);
    security_handshaker_unref(exec_ctx, h);
    return;
  }
done:
  gpr_mu_unlock(&h->mu);
}