text: #include "EXTERN.h"
  #include "perl.h"
  #include "XSUB.h"
  #define NEED_my_strlcpy
  #include "ppport.h"
  
  #include "util.h"
  
  #include <string.h>
  
  #include <grpc/grpc.h>
  #include <grpc/byte_buffer_reader.h>
  #if !defined(GRPC_VERSION_1_1)
  #include <grpc/support/slice.h>
  #else
  #include <grpc/slice.h>
  #endif
  #include <grpc/support/alloc.h>
  
  bool module_initialized = false;
  grpc_completion_queue *completion_queue;
  
  grpc_byte_buffer *string_to_byte_buffer(char *string, size_t length) {
    gpr_slice slice = gpr_slice_from_copied_buffer(string, length);
    grpc_byte_buffer *buffer = grpc_raw_byte_buffer_create(&slice, 1);
    gpr_slice_unref(slice);
    return buffer;
  }
  
  void byte_buffer_to_string(grpc_byte_buffer *buffer, char **out_string,
                             size_t *out_length) {
    if (buffer == NULL) {
      *out_string = NULL;
      *out_length = 0;
      return;
    }
    size_t length = grpc_byte_buffer_length(buffer);
    char *string = calloc(length + 1, sizeof(char));
    size_t offset = 0;
    grpc_byte_buffer_reader reader;
    grpc_byte_buffer_reader_init(&reader, buffer);
    gpr_slice next;
    while (grpc_byte_buffer_reader_next(&reader, &next) != 0) {
      memcpy(string + offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next));
      offset += GPR_SLICE_LENGTH(next);
      gpr_slice_unref(next);
    }
    *out_string = string;
    *out_length = length;
  }
  
  void grpc_perl_init() {
    if (module_initialized) {
      return;
    }
    module_initialized = true;
    grpc_init();
    grpc_perl_init_completion_queue();
  }
  
  void grpc_perl_destroy() {
    if (!module_initialized) {
      return;
    }
    grpc_perl_shutdown_completion_queue();
    grpc_shutdown();
    module_initialized = false;
  }
  
  void grpc_perl_init_completion_queue() {
  #if defined(GRPC_VERSION_1_4)
    grpc_completion_queue_attributes attr;
  
    attr.version = 1;
    attr.cq_completion_type = GRPC_CQ_PLUCK;
    attr.cq_polling_type = GRPC_CQ_DEFAULT_POLLING;
  
    completion_queue = grpc_completion_queue_create(grpc_completion_queue_factory_lookup(&attr), &attr, NULL);
  #else
    completion_queue = grpc_completion_queue_create(NULL);
  #endif
  }
  
  void grpc_perl_shutdown_completion_queue() {
    grpc_completion_queue_shutdown(completion_queue);
  #if defined(GRPC_VERSION_1_4)
    while (grpc_completion_queue_pluck(completion_queue, NULL,
                                       gpr_inf_future(GPR_CLOCK_REALTIME),
                                       NULL).type != GRPC_QUEUE_SHUTDOWN);
  #else
    while (grpc_completion_queue_next(completion_queue,
                                      gpr_inf_future(GPR_CLOCK_REALTIME),
                                      NULL).type != GRPC_QUEUE_SHUTDOWN);
  #endif
    grpc_completion_queue_destroy(completion_queue);
    completion_queue = NULL;
  }
  
  void perl_grpc_read_args_array(HV *hash, grpc_channel_args *args) {
    // handle hashes
    if (SvTYPE(hash)!=SVt_PVHV) {
      croak("Expected hash for perl_grpc_read_args_array() args");
    }
  
    char* key;
    I32 keylen;
    SV* value;
  
    // count items in hash
    args->num_args = 0;
    hv_iterinit(hash);
    while((value = hv_iternextsv(hash,&key,&keylen))!=NULL) {
      args->num_args += 1;
    }
  
    args->args = calloc(args->num_args, sizeof(grpc_arg));
  
    int args_index = 0;
    hv_iterinit(hash);
    while((value = hv_iternextsv(hash,&key,&keylen))!=NULL) {
      if (SvOK(value)) {
        args->args[args_index].key = key;
        if (SvIOK(value)) {
          args->args[args_index].type = GRPC_ARG_INTEGER;
          args->args[args_index].value.integer = SvIV(value);
          args->args[args_index].value.string = NULL;
        } else {
          args->args[args_index].type = GRPC_ARG_STRING;
          args->args[args_index].value.string = SvPV_nolen(value);
        }
      } else {
        croak("args values must be int or string");
      }
      args_index++;
    }
  }
  
  /* Creates and returns a perl hash object with the data in a
   * grpc_metadata_array. Returns NULL on failure */
  HV* grpc_parse_metadata_array(grpc_metadata_array *metadata_array) {
    HV* hash = newHV();
    grpc_metadata *elements = metadata_array->metadata;
    grpc_metadata *elem;
  
    // hash->{key} = [val]
    int i;
    int count = metadata_array->count;
    SV *inner_value;
  #if defined(GRPC_VERSION_1_2)
    SV *key;
    HE *temp_fetch;
  #else
    SV **temp_fetch;
  #endif
    for (i = 0; i < count; i++) {
      elem = &elements[i];
  #if defined(GRPC_VERSION_1_2)
      key = sv_2mortal(grpc_slice_to_sv(elem->key));
      temp_fetch = hv_fetch_ent(hash, key, 0, 0);
      inner_value = temp_fetch ? HeVAL(temp_fetch) : NULL;
  #else
      temp_fetch = hv_fetch(hash, elem->key, strlen(elem->key), 0);
      inner_value = temp_fetch ? *temp_fetch : NULL;
  #endif
      if (inner_value) {
        if(!SvROK(inner_value)) {
          croak("Metadata hash somehow contains wrong types.");
          return NULL;
        }
        av_push( (AV*)SvRV(inner_value), grpc_slice_or_buffer_length_to_sv(elem->value) );
      } else {
        AV* av = newAV();
        av_push( av, grpc_slice_or_buffer_length_to_sv(elem->value) );
  #if defined(GRPC_VERSION_1_2)
        hv_store_ent(hash,key,newRV_inc((SV*)av),0);
  #else
        hv_store(hash,elem->key,strlen(elem->key),newRV_inc((SV*)av),0);
  #endif
      }
    }
  
    return hash;
  }
  
  /* Populates a grpc_metadata_array with the data in a perl hash object.
     Returns TRUE on success and FALSE on failure.
     Shouldn't use croak, as it is used in grpc callbacks, and everything explosed with a segfault.
   */
  bool create_metadata_array(HV *hash, grpc_metadata_array *metadata) {
    // First thing to do is to make grpc_metadata_array_destroy() safe no matter what
    grpc_metadata_array_init(metadata);
    metadata->capacity = 0;
    metadata->metadata = NULL;
  
    // handle hashes
    if (SvTYPE(hash)!=SVt_PVHV) {
      warn("Expected hash for create_metadata_array() args");
      return FALSE;
    }
  
    int i;
    char* key;
    I32 keylen;
    SV* value;
  
    // count items in hash
    metadata->capacity = 0;
    hv_iterinit(hash);
    while((value = hv_iternextsv(hash,&key,&keylen))!=NULL) {
      if (!SvROK(value)) {
        warn("expected array ref in metadata value %s, ignoring...",key);
        continue;
      }
      value = SvRV(value);
      if (SvTYPE(value)!=SVt_PVAV) {
        warn("expected array ref in metadata value %s, ignoring...",key);
        continue;
      }
      metadata->capacity += av_len((AV*)value)+1;
    }
  
    if(metadata->capacity > 0) {
      metadata->metadata = gpr_malloc(metadata->capacity * sizeof(grpc_metadata));
    } else {
      metadata->metadata = NULL;
      return TRUE;
    }
  
    metadata->count = 0;
    hv_iterinit(hash);
    while((value = hv_iternextsv(hash,&key,&keylen))!=NULL) {
      if (!SvROK(value)) {
        //warn("expected array ref in metadata value %s, ignoring...",key);
        continue;
      }
      value = SvRV(value);
      if (SvTYPE(value)!=SVt_PVAV) {
        //warn("expected array ref in metadata value %s, ignoring...",key);
        continue;
      }
      for(i=0;i<av_len((AV*)value)+1;i++) {
        SV** inner_value = av_fetch((AV*)value,i,1);
        if (SvOK(*inner_value)) {
  #if defined(GRPC_VERSION_1_2)
          metadata->metadata[metadata->count].key = grpc_slice_from_copied_string(key);
          metadata->metadata[metadata->count].value =
              grpc_slice_from_sv(*inner_value);
  #else
          metadata->metadata[metadata->count].key = key;
          metadata->metadata[metadata->count].value =
              strdup(SvPV(*inner_value,metadata->metadata[metadata->count].value_length));
  #endif
          metadata->count += 1;
        } else {
          warn("args values must be int or string");
          return FALSE;
        }
      }
    }
  
    return TRUE;
  }
  
  /* Callback function for plugin creds API */
  #if defined(GRPC_VERSION_1_7)
  int plugin_get_metadata(void *ptr, grpc_auth_metadata_context context,
                          grpc_credentials_plugin_metadata_cb cb,
                          void *user_data,
                          grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
                          size_t *num_creds_md, grpc_status_code *status,
                          const char **error_details) {
  #else
  void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context,
                           grpc_credentials_plugin_metadata_cb cb,
                           void *user_data) {
  #endif
    static char error_details_buf[1024];
  
    SV* callback = (SV*)ptr;
    SV* err_tmp;
    int has_error = FALSE;
    char *error_details_out = NULL;
    grpc_metadata_array metadata;
  
    dSP;
    ENTER;
  
    HV* hash = newHV();
    hv_stores(hash,"service_url", newSVpv(context.service_url,0));
    hv_stores(hash,"method_name", newSVpv(context.method_name,0));
  
    SAVETMPS;
    PUSHMARK(sp);
    XPUSHs(sv_2mortal((SV*)newRV_noinc((SV*)hash)));
    PUTBACK;
    int count = perl_call_sv(callback, G_SCALAR|G_EVAL);
    SPAGAIN;
  
    err_tmp = ERRSV;
    if (SvTRUE(err_tmp)) {
      has_error = TRUE;
      my_strlcpy(error_details_buf, SvPV_nolen(err_tmp), sizeof(error_details_buf));
      error_details_out = error_details_buf;
      POPs;
    } else if (count!=1) {
      has_error = TRUE;
      error_details_out = "callback returned more/less than 1 value";
      POPs;
    } else {
      SV* retval = POPs;
  
      if (SvROK(retval)) { // create_metadata_array() segfaults without this check
        if (!create_metadata_array((HV*)SvRV(retval), &metadata)) {
          has_error = TRUE;
          error_details_out = "callback returned invalid metadata";
          grpc_metadata_array_destroy(&metadata);
        }
      } else {
        has_error = TRUE;
        error_details_out = "calback returned non-reference";
      }
    }
  
    PUTBACK;
    FREETMPS;
    LEAVE;
  
    // TODO Documentation says that for GRPC_VERSION_1_7-style API a
    // callback MUST be called from a different thread. This doesn't
    // crash right now, but definitely should be fixed.
    // GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX is 4, so we can return
    // up to that amount of metadata items synchronously.
    if ( has_error ) {
      grpc_status_code code = GRPC_STATUS_INVALID_ARGUMENT;
      cb(user_data, NULL, 0, code, error_details_out);
    } else {
      grpc_status_code code = GRPC_STATUS_OK;
      cb(user_data, metadata.metadata, metadata.count, code, NULL);
    }
  
  #if defined(GRPC_VERSION_1_7)
    return 0;
  #endif
  }
  
  /* Cleanup function for plugin creds API */
  void plugin_destroy_state(void *ptr) {
    SV *state = (SV *)ptr;
    SvREFCNT_dec(state);
  }
  
  #if defined(GRPC_VERSION_1_2)
  SV *grpc_slice_to_sv(grpc_slice slice) {
    char *slice_str = grpc_slice_to_c_string(slice);
    SV *sv = newSVpv(slice_str, GRPC_SLICE_LENGTH(slice));
    gpr_free(slice_str);
    return sv;
  }
  
  grpc_slice grpc_slice_from_sv(SV *sv) {
    STRLEN length;
    const char *buffer = SvPV(sv, length);
    return grpc_slice_from_copied_buffer(buffer, length);
  }
  #endif
  