/*
 * nghttp2 - HTTP/2 C Library
 *
 * Copyright (c) 2013 Tatsuhiro Tsujikawa
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "nghttp2_hd.h"

#include <string.h>
#include <assert.h>
#include <stdio.h>

#include "nghttp2_helper.h"
#include "nghttp2_int.h"

/* Make scalar initialization form of nghttp2_hd_entry */
#define MAKE_STATIC_ENT(N, V, T, H)                                            \
  {                                                                            \
    { (uint8_t *)(N), (uint8_t *)(V), sizeof((N)) - 1, sizeof((V)) - 1, 0 }    \
    , NULL, 0, (H), (T), 1, NGHTTP2_HD_FLAG_NONE                               \
  }

/* Generated by mkstatictbl.py */
/* 3rd parameter is nghttp2_token value for header field name.  We use
   first enum value if same header names are repeated (e.g.,
   :status). */
static nghttp2_hd_entry static_table[] = {
    MAKE_STATIC_ENT(":authority", "", 0, 3153725150u),
    MAKE_STATIC_ENT(":method", "GET", 1, 695666056u),
    MAKE_STATIC_ENT(":method", "POST", 1, 695666056u),
    MAKE_STATIC_ENT(":path", "/", 3, 3292848686u),
    MAKE_STATIC_ENT(":path", "/index.html", 3, 3292848686u),
    MAKE_STATIC_ENT(":scheme", "http", 5, 2510477674u),
    MAKE_STATIC_ENT(":scheme", "https", 5, 2510477674u),
    MAKE_STATIC_ENT(":status", "200", 7, 4000288983u),
    MAKE_STATIC_ENT(":status", "204", 7, 4000288983u),
    MAKE_STATIC_ENT(":status", "206", 7, 4000288983u),
    MAKE_STATIC_ENT(":status", "304", 7, 4000288983u),
    MAKE_STATIC_ENT(":status", "400", 7, 4000288983u),
    MAKE_STATIC_ENT(":status", "404", 7, 4000288983u),
    MAKE_STATIC_ENT(":status", "500", 7, 4000288983u),
    MAKE_STATIC_ENT("accept-charset", "", 14, 3664010344u),
    MAKE_STATIC_ENT("accept-encoding", "gzip, deflate", 15, 3379649177u),
    MAKE_STATIC_ENT("accept-language", "", 16, 1979086614u),
    MAKE_STATIC_ENT("accept-ranges", "", 17, 1713753958u),
    MAKE_STATIC_ENT("accept", "", 18, 136609321u),
    MAKE_STATIC_ENT("access-control-allow-origin", "", 19, 2710797292u),
    MAKE_STATIC_ENT("age", "", 20, 742476188u),
    MAKE_STATIC_ENT("allow", "", 21, 2930878514u),
    MAKE_STATIC_ENT("authorization", "", 22, 2436257726u),
    MAKE_STATIC_ENT("cache-control", "", 23, 1355326669u),
    MAKE_STATIC_ENT("content-disposition", "", 24, 3889184348u),
    MAKE_STATIC_ENT("content-encoding", "", 25, 65203592u),
    MAKE_STATIC_ENT("content-language", "", 26, 24973587u),
    MAKE_STATIC_ENT("content-length", "", 27, 1308181789u),
    MAKE_STATIC_ENT("content-location", "", 28, 2302364718u),
    MAKE_STATIC_ENT("content-range", "", 29, 3555523146u),
    MAKE_STATIC_ENT("content-type", "", 30, 4244048277u),
    MAKE_STATIC_ENT("cookie", "", 31, 2007449791u),
    MAKE_STATIC_ENT("date", "", 32, 3564297305u),
    MAKE_STATIC_ENT("etag", "", 33, 113792960u),
    MAKE_STATIC_ENT("expect", "", 34, 2530896728u),
    MAKE_STATIC_ENT("expires", "", 35, 1049544579u),
    MAKE_STATIC_ENT("from", "", 36, 2513272949u),
    MAKE_STATIC_ENT("host", "", 37, 2952701295u),
    MAKE_STATIC_ENT("if-match", "", 38, 3597694698u),
    MAKE_STATIC_ENT("if-modified-since", "", 39, 2213050793u),
    MAKE_STATIC_ENT("if-none-match", "", 40, 2536202615u),
    MAKE_STATIC_ENT("if-range", "", 41, 2340978238u),
    MAKE_STATIC_ENT("if-unmodified-since", "", 42, 3794814858u),
    MAKE_STATIC_ENT("last-modified", "", 43, 3226950251u),
    MAKE_STATIC_ENT("link", "", 44, 232457833u),
    MAKE_STATIC_ENT("location", "", 45, 200649126u),
    MAKE_STATIC_ENT("max-forwards", "", 46, 1826162134u),
    MAKE_STATIC_ENT("proxy-authenticate", "", 47, 2709445359u),
    MAKE_STATIC_ENT("proxy-authorization", "", 48, 2686392507u),
    MAKE_STATIC_ENT("range", "", 49, 4208725202u),
    MAKE_STATIC_ENT("referer", "", 50, 3969579366u),
    MAKE_STATIC_ENT("refresh", "", 51, 3572655668u),
    MAKE_STATIC_ENT("retry-after", "", 52, 3336180598u),
    MAKE_STATIC_ENT("server", "", 53, 1085029842u),
    MAKE_STATIC_ENT("set-cookie", "", 54, 1848371000u),
    MAKE_STATIC_ENT("strict-transport-security", "", 55, 4138147361u),
    MAKE_STATIC_ENT("transfer-encoding", "", 56, 3719590988u),
    MAKE_STATIC_ENT("user-agent", "", 57, 606444526u),
    MAKE_STATIC_ENT("vary", "", 58, 1085005381u),
    MAKE_STATIC_ENT("via", "", 59, 1762798611u),
    MAKE_STATIC_ENT("www-authenticate", "", 60, 779865858u),
};

static int memeq(const void *s1, const void *s2, size_t n) {
  return memcmp(s1, s2, n) == 0;
}

/*
 * This function was generated by genlibtokenlookup.py.  Inspired by
 * h2o header lookup.  https://github.com/h2o/h2o
 */
static int lookup_token(const uint8_t *name, size_t namelen) {
  switch (namelen) {
  case 2:
    switch (name[1]) {
    case 'e':
      if (lstreq("t", name, 1)) {
        return NGHTTP2_TOKEN_TE;
      }
      break;
    }
    break;
  case 3:
    switch (name[2]) {
    case 'a':
      if (lstreq("vi", name, 2)) {
        return NGHTTP2_TOKEN_VIA;
      }
      break;
    case 'e':
      if (lstreq("ag", name, 2)) {
        return NGHTTP2_TOKEN_AGE;
      }
      break;
    }
    break;
  case 4:
    switch (name[3]) {
    case 'e':
      if (lstreq("dat", name, 3)) {
        return NGHTTP2_TOKEN_DATE;
      }
      break;
    case 'g':
      if (lstreq("eta", name, 3)) {
        return NGHTTP2_TOKEN_ETAG;
      }
      break;
    case 'k':
      if (lstreq("lin", name, 3)) {
        return NGHTTP2_TOKEN_LINK;
      }
      break;
    case 'm':
      if (lstreq("fro", name, 3)) {
        return NGHTTP2_TOKEN_FROM;
      }
      break;
    case 't':
      if (lstreq("hos", name, 3)) {
        return NGHTTP2_TOKEN_HOST;
      }
      break;
    case 'y':
      if (lstreq("var", name, 3)) {
        return NGHTTP2_TOKEN_VARY;
      }
      break;
    }
    break;
  case 5:
    switch (name[4]) {
    case 'e':
      if (lstreq("rang", name, 4)) {
        return NGHTTP2_TOKEN_RANGE;
      }
      break;
    case 'h':
      if (lstreq(":pat", name, 4)) {
        return NGHTTP2_TOKEN__PATH;
      }
      if (lstreq(":pat", name, 4)) {
        return NGHTTP2_TOKEN__PATH;
      }
      break;
    case 'w':
      if (lstreq("allo", name, 4)) {
        return NGHTTP2_TOKEN_ALLOW;
      }
      break;
    }
    break;
  case 6:
    switch (name[5]) {
    case 'e':
      if (lstreq("cooki", name, 5)) {
        return NGHTTP2_TOKEN_COOKIE;
      }
      break;
    case 'r':
      if (lstreq("serve", name, 5)) {
        return NGHTTP2_TOKEN_SERVER;
      }
      break;
    case 't':
      if (lstreq("accep", name, 5)) {
        return NGHTTP2_TOKEN_ACCEPT;
      }
      if (lstreq("expec", name, 5)) {
        return NGHTTP2_TOKEN_EXPECT;
      }
      break;
    }
    break;
  case 7:
    switch (name[6]) {
    case 'd':
      if (lstreq(":metho", name, 6)) {
        return NGHTTP2_TOKEN__METHOD;
      }
      if (lstreq(":metho", name, 6)) {
        return NGHTTP2_TOKEN__METHOD;
      }
      break;
    case 'e':
      if (lstreq(":schem", name, 6)) {
        return NGHTTP2_TOKEN__SCHEME;
      }
      if (lstreq(":schem", name, 6)) {
        return NGHTTP2_TOKEN__SCHEME;
      }
      if (lstreq("upgrad", name, 6)) {
        return NGHTTP2_TOKEN_UPGRADE;
      }
      break;
    case 'h':
      if (lstreq("refres", name, 6)) {
        return NGHTTP2_TOKEN_REFRESH;
      }
      break;
    case 'r':
      if (lstreq("refere", name, 6)) {
        return NGHTTP2_TOKEN_REFERER;
      }
      break;
    case 's':
      if (lstreq(":statu", name, 6)) {
        return NGHTTP2_TOKEN__STATUS;
      }
      if (lstreq(":statu", name, 6)) {
        return NGHTTP2_TOKEN__STATUS;
      }
      if (lstreq(":statu", name, 6)) {
        return NGHTTP2_TOKEN__STATUS;
      }
      if (lstreq(":statu", name, 6)) {
        return NGHTTP2_TOKEN__STATUS;
      }
      if (lstreq(":statu", name, 6)) {
        return NGHTTP2_TOKEN__STATUS;
      }
      if (lstreq(":statu", name, 6)) {
        return NGHTTP2_TOKEN__STATUS;
      }
      if (lstreq(":statu", name, 6)) {
        return NGHTTP2_TOKEN__STATUS;
      }
      if (lstreq("expire", name, 6)) {
        return NGHTTP2_TOKEN_EXPIRES;
      }
      break;
    }
    break;
  case 8:
    switch (name[7]) {
    case 'e':
      if (lstreq("if-rang", name, 7)) {
        return NGHTTP2_TOKEN_IF_RANGE;
      }
      break;
    case 'h':
      if (lstreq("if-matc", name, 7)) {
        return NGHTTP2_TOKEN_IF_MATCH;
      }
      break;
    case 'n':
      if (lstreq("locatio", name, 7)) {
        return NGHTTP2_TOKEN_LOCATION;
      }
      break;
    }
    break;
  case 10:
    switch (name[9]) {
    case 'e':
      if (lstreq("keep-aliv", name, 9)) {
        return NGHTTP2_TOKEN_KEEP_ALIVE;
      }
      if (lstreq("set-cooki", name, 9)) {
        return NGHTTP2_TOKEN_SET_COOKIE;
      }
      break;
    case 'n':
      if (lstreq("connectio", name, 9)) {
        return NGHTTP2_TOKEN_CONNECTION;
      }
      break;
    case 't':
      if (lstreq("user-agen", name, 9)) {
        return NGHTTP2_TOKEN_USER_AGENT;
      }
      break;
    case 'y':
      if (lstreq(":authorit", name, 9)) {
        return NGHTTP2_TOKEN__AUTHORITY;
      }
      break;
    }
    break;
  case 11:
    switch (name[10]) {
    case 'r':
      if (lstreq("retry-afte", name, 10)) {
        return NGHTTP2_TOKEN_RETRY_AFTER;
      }
      break;
    }
    break;
  case 12:
    switch (name[11]) {
    case 'e':
      if (lstreq("content-typ", name, 11)) {
        return NGHTTP2_TOKEN_CONTENT_TYPE;
      }
      break;
    case 's':
      if (lstreq("max-forward", name, 11)) {
        return NGHTTP2_TOKEN_MAX_FORWARDS;
      }
      break;
    }
    break;
  case 13:
    switch (name[12]) {
    case 'd':
      if (lstreq("last-modifie", name, 12)) {
        return NGHTTP2_TOKEN_LAST_MODIFIED;
      }
      break;
    case 'e':
      if (lstreq("content-rang", name, 12)) {
        return NGHTTP2_TOKEN_CONTENT_RANGE;
      }
      break;
    case 'h':
      if (lstreq("if-none-matc", name, 12)) {
        return NGHTTP2_TOKEN_IF_NONE_MATCH;
      }
      break;
    case 'l':
      if (lstreq("cache-contro", name, 12)) {
        return NGHTTP2_TOKEN_CACHE_CONTROL;
      }
      break;
    case 'n':
      if (lstreq("authorizatio", name, 12)) {
        return NGHTTP2_TOKEN_AUTHORIZATION;
      }
      break;
    case 's':
      if (lstreq("accept-range", name, 12)) {
        return NGHTTP2_TOKEN_ACCEPT_RANGES;
      }
      break;
    }
    break;
  case 14:
    switch (name[13]) {
    case 'h':
      if (lstreq("content-lengt", name, 13)) {
        return NGHTTP2_TOKEN_CONTENT_LENGTH;
      }
      break;
    case 't':
      if (lstreq("accept-charse", name, 13)) {
        return NGHTTP2_TOKEN_ACCEPT_CHARSET;
      }
      break;
    }
    break;
  case 15:
    switch (name[14]) {
    case 'e':
      if (lstreq("accept-languag", name, 14)) {
        return NGHTTP2_TOKEN_ACCEPT_LANGUAGE;
      }
      break;
    case 'g':
      if (lstreq("accept-encodin", name, 14)) {
        return NGHTTP2_TOKEN_ACCEPT_ENCODING;
      }
      break;
    }
    break;
  case 16:
    switch (name[15]) {
    case 'e':
      if (lstreq("content-languag", name, 15)) {
        return NGHTTP2_TOKEN_CONTENT_LANGUAGE;
      }
      if (lstreq("www-authenticat", name, 15)) {
        return NGHTTP2_TOKEN_WWW_AUTHENTICATE;
      }
      break;
    case 'g':
      if (lstreq("content-encodin", name, 15)) {
        return NGHTTP2_TOKEN_CONTENT_ENCODING;
      }
      break;
    case 'n':
      if (lstreq("content-locatio", name, 15)) {
        return NGHTTP2_TOKEN_CONTENT_LOCATION;
      }
      if (lstreq("proxy-connectio", name, 15)) {
        return NGHTTP2_TOKEN_PROXY_CONNECTION;
      }
      break;
    }
    break;
  case 17:
    switch (name[16]) {
    case 'e':
      if (lstreq("if-modified-sinc", name, 16)) {
        return NGHTTP2_TOKEN_IF_MODIFIED_SINCE;
      }
      break;
    case 'g':
      if (lstreq("transfer-encodin", name, 16)) {
        return NGHTTP2_TOKEN_TRANSFER_ENCODING;
      }
      break;
    }
    break;
  case 18:
    switch (name[17]) {
    case 'e':
      if (lstreq("proxy-authenticat", name, 17)) {
        return NGHTTP2_TOKEN_PROXY_AUTHENTICATE;
      }
      break;
    }
    break;
  case 19:
    switch (name[18]) {
    case 'e':
      if (lstreq("if-unmodified-sinc", name, 18)) {
        return NGHTTP2_TOKEN_IF_UNMODIFIED_SINCE;
      }
      break;
    case 'n':
      if (lstreq("content-dispositio", name, 18)) {
        return NGHTTP2_TOKEN_CONTENT_DISPOSITION;
      }
      if (lstreq("proxy-authorizatio", name, 18)) {
        return NGHTTP2_TOKEN_PROXY_AUTHORIZATION;
      }
      break;
    }
    break;
  case 25:
    switch (name[24]) {
    case 'y':
      if (lstreq("strict-transport-securit", name, 24)) {
        return NGHTTP2_TOKEN_STRICT_TRANSPORT_SECURITY;
      }
      break;
    }
    break;
  case 27:
    switch (name[26]) {
    case 'n':
      if (lstreq("access-control-allow-origi", name, 26)) {
        return NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN;
      }
      break;
    }
    break;
  }
  return -1;
}

int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t flags, uint8_t *name,
                          size_t namelen, uint8_t *value, size_t valuelen,
                          int token, nghttp2_mem *mem) {
  int rv = 0;

  /* Since nghttp2_hd_entry is used for indexing, ent->nv.flags always
     NGHTTP2_NV_FLAG_NONE */
  ent->nv.flags = NGHTTP2_NV_FLAG_NONE;

  if ((flags & NGHTTP2_HD_FLAG_NAME_ALLOC) &&
      (flags & NGHTTP2_HD_FLAG_NAME_GIFT) == 0) {
    if (namelen == 0) {
      flags = (uint8_t)(flags & ~NGHTTP2_HD_FLAG_NAME_ALLOC);
      ent->nv.name = (uint8_t *)"";
    } else {
      /* name may not be NULL terminated on compression. */
      ent->nv.name = nghttp2_mem_malloc(mem, namelen + 1);
      if (ent->nv.name == NULL) {
        rv = NGHTTP2_ERR_NOMEM;
        goto fail;
      }
      memcpy(ent->nv.name, name, namelen);
      ent->nv.name[namelen] = '\0';
    }
  } else {
    ent->nv.name = name;
  }
  if ((flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) &&
      (flags & NGHTTP2_HD_FLAG_VALUE_GIFT) == 0) {
    if (valuelen == 0) {
      flags = (uint8_t)(flags & ~NGHTTP2_HD_FLAG_VALUE_ALLOC);
      ent->nv.value = (uint8_t *)"";
    } else {
      /* value may not be NULL terminated on compression. */
      ent->nv.value = nghttp2_mem_malloc(mem, valuelen + 1);
      if (ent->nv.value == NULL) {
        rv = NGHTTP2_ERR_NOMEM;
        goto fail2;
      }
      memcpy(ent->nv.value, value, valuelen);
      ent->nv.value[valuelen] = '\0';
    }
  } else {
    ent->nv.value = value;
  }
  ent->nv.namelen = namelen;
  ent->nv.valuelen = valuelen;
  ent->token = token;
  ent->ref = 1;
  ent->flags = flags;
  ent->next = NULL;
  ent->hash = 0;

  return 0;

fail2:
  if ((flags & NGHTTP2_HD_FLAG_NAME_ALLOC) &&
      (flags & NGHTTP2_HD_FLAG_NAME_GIFT) == 0) {
    nghttp2_mem_free(mem, ent->nv.name);
  }
fail:
  return rv;
}

void nghttp2_hd_entry_free(nghttp2_hd_entry *ent, nghttp2_mem *mem) {
  assert(ent->ref == 0);
  if (ent->flags & NGHTTP2_HD_FLAG_NAME_ALLOC) {
    nghttp2_mem_free(mem, ent->nv.name);
  }
  if (ent->flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) {
    nghttp2_mem_free(mem, ent->nv.value);
  }
}

static int name_eq(const nghttp2_nv *a, const nghttp2_nv *b) {
  return a->namelen == b->namelen && memeq(a->name, b->name, a->namelen);
}

static int value_eq(const nghttp2_nv *a, const nghttp2_nv *b) {
  return a->valuelen == b->valuelen && memeq(a->value, b->value, a->valuelen);
}

static uint32_t name_hash(const nghttp2_nv *nv) {
  /* 32 bit FNV-1a: http://isthe.com/chongo/tech/comp/fnv/ */
  uint32_t h = 2166136261u;
  size_t i;

  for (i = 0; i < nv->namelen; ++i) {
    h ^= nv->name[i];
    h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
  }

  return h;
}

static void hd_map_init(nghttp2_hd_map *map) {
  memset(map, 0, sizeof(nghttp2_hd_map));
}

static void hd_map_insert(nghttp2_hd_map *map, nghttp2_hd_entry *ent) {
  nghttp2_hd_entry **bucket;

  bucket = &map->table[ent->hash & (HD_MAP_SIZE - 1)];

  if (*bucket == NULL) {
    *bucket = ent;
    return;
  }

  /* lower index is linked near the root */
  ent->next = *bucket;
  *bucket = ent;
}

static nghttp2_hd_entry *hd_map_find(nghttp2_hd_map *map, int *exact_match,
                                     const nghttp2_nv *nv, int token,
                                     uint32_t hash) {
  nghttp2_hd_entry *p;
  nghttp2_hd_entry *res = NULL;

  *exact_match = 0;

  for (p = map->table[hash & (HD_MAP_SIZE - 1)]; p; p = p->next) {
    if (hash != p->hash || token != p->token ||
        (token == -1 && !name_eq(&p->nv, nv))) {
      continue;
    }
    if (!res) {
      res = p;
    }
    if (value_eq(&p->nv, nv)) {
      res = p;
      *exact_match = 1;
      break;
    }
  }

  return res;
}

static void hd_map_remove(nghttp2_hd_map *map, nghttp2_hd_entry *ent) {
  nghttp2_hd_entry **bucket;
  nghttp2_hd_entry *p;

  bucket = &map->table[ent->hash & (HD_MAP_SIZE - 1)];

  if (*bucket == NULL) {
    return;
  }

  if (*bucket == ent) {
    *bucket = ent->next;
    ent->next = NULL;
    return;
  }

  for (p = *bucket; p; p = p->next) {
    if (p->next == ent) {
      p->next = ent->next;
      ent->next = NULL;
      return;
    }
  }
}

static int hd_ringbuf_init(nghttp2_hd_ringbuf *ringbuf, size_t bufsize,
                           nghttp2_mem *mem) {
  size_t size;
  for (size = 1; size < bufsize; size <<= 1)
    ;
  ringbuf->buffer = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry *) * size);
  if (ringbuf->buffer == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }
  ringbuf->mask = size - 1;
  ringbuf->first = 0;
  ringbuf->len = 0;
  return 0;
}

static nghttp2_hd_entry *hd_ringbuf_get(nghttp2_hd_ringbuf *ringbuf,
                                        size_t idx) {
  assert(idx < ringbuf->len);
  return ringbuf->buffer[(ringbuf->first + idx) & ringbuf->mask];
}

static int hd_ringbuf_reserve(nghttp2_hd_ringbuf *ringbuf, size_t bufsize,
                              nghttp2_mem *mem) {
  size_t i;
  size_t size;
  nghttp2_hd_entry **buffer;

  if (ringbuf->mask + 1 >= bufsize) {
    return 0;
  }
  for (size = 1; size < bufsize; size <<= 1)
    ;
  buffer = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry *) * size);
  if (buffer == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }
  for (i = 0; i < ringbuf->len; ++i) {
    buffer[i] = hd_ringbuf_get(ringbuf, i);
  }
  nghttp2_mem_free(mem, ringbuf->buffer);
  ringbuf->buffer = buffer;
  ringbuf->mask = size - 1;
  ringbuf->first = 0;
  return 0;
}

static void hd_ringbuf_free(nghttp2_hd_ringbuf *ringbuf, nghttp2_mem *mem) {
  size_t i;
  if (ringbuf == NULL) {
    return;
  }
  for (i = 0; i < ringbuf->len; ++i) {
    nghttp2_hd_entry *ent = hd_ringbuf_get(ringbuf, i);
    --ent->ref;
    nghttp2_hd_entry_free(ent, mem);
    nghttp2_mem_free(mem, ent);
  }
  nghttp2_mem_free(mem, ringbuf->buffer);
}

static int hd_ringbuf_push_front(nghttp2_hd_ringbuf *ringbuf,
                                 nghttp2_hd_entry *ent, nghttp2_mem *mem) {
  int rv;

  rv = hd_ringbuf_reserve(ringbuf, ringbuf->len + 1, mem);

  if (rv != 0) {
    return rv;
  }

  ringbuf->buffer[--ringbuf->first & ringbuf->mask] = ent;
  ++ringbuf->len;

  return 0;
}

static void hd_ringbuf_pop_back(nghttp2_hd_ringbuf *ringbuf) {
  assert(ringbuf->len > 0);
  --ringbuf->len;
}

static int hd_context_init(nghttp2_hd_context *context, nghttp2_mem *mem) {
  int rv;
  context->mem = mem;
  context->bad = 0;
  context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
  rv = hd_ringbuf_init(&context->hd_table, context->hd_table_bufsize_max /
                                               NGHTTP2_HD_ENTRY_OVERHEAD,
                       mem);
  if (rv != 0) {
    return rv;
  }

  context->hd_table_bufsize = 0;
  context->next_seq = 0;

  return 0;
}

static void hd_context_free(nghttp2_hd_context *context) {
  hd_ringbuf_free(&context->hd_table, context->mem);
}

int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem) {
  return nghttp2_hd_deflate_init2(
      deflater, NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE, mem);
}

int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater,
                             size_t deflate_hd_table_bufsize_max,
                             nghttp2_mem *mem) {
  int rv;
  rv = hd_context_init(&deflater->ctx, mem);
  if (rv != 0) {
    return rv;
  }

  hd_map_init(&deflater->map);

  if (deflate_hd_table_bufsize_max < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) {
    deflater->notify_table_size_change = 1;
    deflater->ctx.hd_table_bufsize_max = deflate_hd_table_bufsize_max;
  } else {
    deflater->notify_table_size_change = 0;
  }

  deflater->deflate_hd_table_bufsize_max = deflate_hd_table_bufsize_max;
  deflater->min_hd_table_bufsize_max = UINT32_MAX;

  return 0;
}

int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem) {
  int rv;

  rv = hd_context_init(&inflater->ctx, mem);
  if (rv != 0) {
    goto fail;
  }

  inflater->settings_hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
  inflater->min_hd_table_bufsize_max = UINT32_MAX;

  inflater->ent_keep = NULL;
  inflater->nv_keep = NULL;

  inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
  inflater->state = NGHTTP2_HD_STATE_INFLATE_START;

  rv = nghttp2_bufs_init3(&inflater->nvbufs, NGHTTP2_HD_MAX_NV / 8, 8, 1, 0,
                          mem);

  if (rv != 0) {
    goto nvbufs_fail;
  }

  inflater->huffman_encoded = 0;
  inflater->index = 0;
  inflater->left = 0;
  inflater->shift = 0;
  inflater->newnamelen = 0;
  inflater->index_required = 0;
  inflater->no_index = 0;

  return 0;

nvbufs_fail:
  hd_context_free(&inflater->ctx);
fail:
  return rv;
}

static void hd_inflate_keep_free(nghttp2_hd_inflater *inflater) {
  nghttp2_mem *mem;

  mem = inflater->ctx.mem;
  if (inflater->ent_keep) {
    if (inflater->ent_keep->ref == 0) {
      nghttp2_hd_entry_free(inflater->ent_keep, mem);
      nghttp2_mem_free(mem, inflater->ent_keep);
    }
    inflater->ent_keep = NULL;
  }

  nghttp2_mem_free(mem, inflater->nv_keep);
  inflater->nv_keep = NULL;
}

void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater) {
  hd_context_free(&deflater->ctx);
}

void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater) {
  hd_inflate_keep_free(inflater);
  nghttp2_bufs_free(&inflater->nvbufs);
  hd_context_free(&inflater->ctx);
}

static size_t entry_room(size_t namelen, size_t valuelen) {
  return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen;
}

static int emit_indexed_header(nghttp2_nv *nv_out, int *token_out,
                               nghttp2_hd_entry *ent) {
  DEBUGF(fprintf(stderr, "inflatehd: header emission: %s: %s\n", ent->nv.name,
                 ent->nv.value));
  /* ent->ref may be 0. This happens if the encoder emits literal
     block larger than header table capacity with indexing. */
  *nv_out = ent->nv;
  *token_out = ent->token;
  return 0;
}

static int emit_literal_header(nghttp2_nv *nv_out, int *token_out,
                               nghttp2_nv *nv) {
  DEBUGF(fprintf(stderr, "inflatehd: header emission: %s: %s\n", nv->name,
                 nv->value));
  *nv_out = *nv;
  *token_out = lookup_token(nv->name, nv->namelen);
  return 0;
}

static size_t count_encoded_length(size_t n, size_t prefix) {
  size_t k = (size_t)((1 << prefix) - 1);
  size_t len = 0;

  if (n < k) {
    return 1;
  }

  n -= k;
  ++len;

  for (; n >= 128; n >>= 7, ++len)
    ;

  return len + 1;
}

static size_t encode_length(uint8_t *buf, size_t n, size_t prefix) {
  size_t k = (size_t)((1 << prefix) - 1);
  uint8_t *begin = buf;

  *buf = (uint8_t)(*buf & ~k);

  if (n < k) {
    *buf = (uint8_t)(*buf | n);
    return 1;
  }

  *buf = (uint8_t)(*buf | k);
  ++buf;

  n -= k;

  for (; n >= 128; n >>= 7) {
    *buf++ = (uint8_t)((1 << 7) | (n & 0x7f));
  }

  *buf++ = (uint8_t)n;

  return (size_t)(buf - begin);
}

/*
 * Decodes |prefix| prefixed integer stored from |in|.  The |last|
 * represents the 1 beyond the last of the valid contiguous memory
 * region from |in|.  The decoded integer must be less than or equal
 * to UINT32_MAX.
 *
 * If the |initial| is nonzero, it is used as a initial value, this
 * function assumes the |in| starts with intermediate data.
 *
 * An entire integer is decoded successfully, decoded, the |*final| is
 * set to nonzero.
 *
 * This function stores the decoded integer in |*res| if it succeed,
 * including partial decoding (in this case, number of shift to make
 * in the next call will be stored in |*shift_ptr|) and returns number
 * of bytes processed, or returns -1, indicating decoding error.
 */
static ssize_t decode_length(uint32_t *res, size_t *shift_ptr, int *final,
                             uint32_t initial, size_t shift, uint8_t *in,
                             uint8_t *last, size_t prefix) {
  uint32_t k = (uint8_t)((1 << prefix) - 1);
  uint32_t n = initial;
  uint8_t *start = in;

  *shift_ptr = 0;
  *final = 0;

  if (n == 0) {
    if ((*in & k) != k) {
      *res = (*in) & k;
      *final = 1;
      return 1;
    }

    n = k;

    if (++in == last) {
      *res = n;
      return (ssize_t)(in - start);
    }
  }

  for (; in != last; ++in, shift += 7) {
    uint32_t add = *in & 0x7f;

    if ((UINT32_MAX >> shift) < add) {
      DEBUGF(fprintf(stderr, "inflate: integer overflow on shift\n"));
      return -1;
    }

    add <<= shift;

    if (UINT32_MAX - add < n) {
      DEBUGF(fprintf(stderr, "inflate: integer overflow on addition\n"));
      return -1;
    }

    n += add;

    if ((*in & (1 << 7)) == 0) {
      break;
    }
  }

  *shift_ptr = shift;

  if (in == last) {
    *res = n;
    return (ssize_t)(in - start);
  }

  *res = n;
  *final = 1;
  return (ssize_t)(in + 1 - start);
}

static int emit_table_size(nghttp2_bufs *bufs, size_t table_size) {
  int rv;
  uint8_t *bufp;
  size_t blocklen;
  uint8_t sb[16];

  DEBUGF(fprintf(stderr, "deflatehd: emit table_size=%zu\n", table_size));

  blocklen = count_encoded_length(table_size, 5);

  if (sizeof(sb) < blocklen) {
    return NGHTTP2_ERR_HEADER_COMP;
  }

  bufp = sb;

  *bufp = 0x20u;

  encode_length(bufp, table_size, 5);

  rv = nghttp2_bufs_add(bufs, sb, blocklen);
  if (rv != 0) {
    return rv;
  }

  return 0;
}

static int emit_indexed_block(nghttp2_bufs *bufs, size_t idx) {
  int rv;
  size_t blocklen;
  uint8_t sb[16];
  uint8_t *bufp;

  blocklen = count_encoded_length(idx + 1, 7);

  DEBUGF(fprintf(stderr, "deflatehd: emit indexed index=%zu, %zu bytes\n", idx,
                 blocklen));

  if (sizeof(sb) < blocklen) {
    return NGHTTP2_ERR_HEADER_COMP;
  }

  bufp = sb;
  *bufp = 0x80u;
  encode_length(bufp, idx + 1, 7);

  rv = nghttp2_bufs_add(bufs, sb, blocklen);
  if (rv != 0) {
    return rv;
  }

  return 0;
}

static int emit_string(nghttp2_bufs *bufs, const uint8_t *str, size_t len) {
  int rv;
  uint8_t sb[16];
  uint8_t *bufp;
  size_t blocklen;
  size_t enclen;
  int huffman = 0;

  enclen = nghttp2_hd_huff_encode_count(str, len);

  if (enclen < len) {
    huffman = 1;
  } else {
    enclen = len;
  }

  blocklen = count_encoded_length(enclen, 7);

  DEBUGF(fprintf(stderr, "deflatehd: emit string str="));
  DEBUGF(fwrite(str, len, 1, stderr));
  DEBUGF(fprintf(stderr, ", length=%zu, huffman=%d, encoded_length=%zu\n", len,
                 huffman, enclen));

  if (sizeof(sb) < blocklen) {
    return NGHTTP2_ERR_HEADER_COMP;
  }

  bufp = sb;
  *bufp = huffman ? 1 << 7 : 0;
  encode_length(bufp, enclen, 7);

  rv = nghttp2_bufs_add(bufs, sb, blocklen);
  if (rv != 0) {
    return rv;
  }

  if (huffman) {
    rv = nghttp2_hd_huff_encode(bufs, str, len);
  } else {
    assert(enclen == len);
    rv = nghttp2_bufs_add(bufs, str, len);
  }

  return rv;
}

static uint8_t pack_first_byte(int indexing_mode) {
  switch (indexing_mode) {
  case NGHTTP2_HD_WITH_INDEXING:
    return 0x40u;
  case NGHTTP2_HD_WITHOUT_INDEXING:
    return 0;
  case NGHTTP2_HD_NEVER_INDEXING:
    return 0x10u;
  default:
    assert(0);
  }
  /* This is required to compile with android NDK r10d +
     --enable-werror */
  return 0;
}

static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
                              const nghttp2_nv *nv, int indexing_mode) {
  int rv;
  uint8_t *bufp;
  size_t blocklen;
  uint8_t sb[16];
  size_t prefixlen;

  if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) {
    prefixlen = 6;
  } else {
    prefixlen = 4;
  }

  DEBUGF(fprintf(stderr, "deflatehd: emit indname index=%zu, valuelen=%zu, "
                         "indexing_mode=%d\n",
                 idx, nv->valuelen, indexing_mode));

  blocklen = count_encoded_length(idx + 1, prefixlen);

  if (sizeof(sb) < blocklen) {
    return NGHTTP2_ERR_HEADER_COMP;
  }

  bufp = sb;

  *bufp = pack_first_byte(indexing_mode);

  encode_length(bufp, idx + 1, prefixlen);

  rv = nghttp2_bufs_add(bufs, sb, blocklen);
  if (rv != 0) {
    return rv;
  }

  rv = emit_string(bufs, nv->value, nv->valuelen);
  if (rv != 0) {
    return rv;
  }

  return 0;
}

static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv,
                              int indexing_mode) {
  int rv;

  DEBUGF(fprintf(stderr, "deflatehd: emit newname namelen=%zu, valuelen=%zu, "
                         "indexing_mode=%d\n",
                 nv->namelen, nv->valuelen, indexing_mode));

  rv = nghttp2_bufs_addb(bufs, pack_first_byte(indexing_mode));
  if (rv != 0) {
    return rv;
  }

  rv = emit_string(bufs, nv->name, nv->namelen);
  if (rv != 0) {
    return rv;
  }

  rv = emit_string(bufs, nv->value, nv->valuelen);
  if (rv != 0) {
    return rv;
  }

  return 0;
}

static nghttp2_hd_entry *
add_hd_table_incremental(nghttp2_hd_context *context, const nghttp2_nv *nv,
                         int token, uint8_t entry_flags, nghttp2_hd_map *map,
                         uint32_t hash) {
  int rv;
  nghttp2_hd_entry *new_ent;
  size_t room;
  nghttp2_mem *mem;

  mem = context->mem;
  room = entry_room(nv->namelen, nv->valuelen);

  while (context->hd_table_bufsize + room > context->hd_table_bufsize_max &&
         context->hd_table.len > 0) {

    size_t idx = context->hd_table.len - 1;
    nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx);

    context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen);

    DEBUGF(fprintf(stderr, "hpack: remove item from header table: %s: %s\n",
                   ent->nv.name, ent->nv.value));

    hd_ringbuf_pop_back(&context->hd_table);
    if (map) {
      hd_map_remove(map, ent);
    }
    if (--ent->ref == 0) {
      nghttp2_hd_entry_free(ent, mem);
      nghttp2_mem_free(mem, ent);
    }
  }

  new_ent = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry));
  if (new_ent == NULL) {
    return NULL;
  }

  rv = nghttp2_hd_entry_init(new_ent, entry_flags, nv->name, nv->namelen,
                             nv->value, nv->valuelen, token, mem);
  if (rv != 0) {
    nghttp2_mem_free(mem, new_ent);
    return NULL;
  }

  if (room > context->hd_table_bufsize_max) {
    /* The entry taking more than NGHTTP2_HD_MAX_BUFFER_SIZE is
       immediately evicted. */
    --new_ent->ref;
  } else {
    rv = hd_ringbuf_push_front(&context->hd_table, new_ent, mem);

    if (rv != 0) {
      --new_ent->ref;

      if ((entry_flags & NGHTTP2_HD_FLAG_NAME_ALLOC) &&
          (entry_flags & NGHTTP2_HD_FLAG_NAME_GIFT)) {
        /* nv->name are managed by caller. */
        new_ent->nv.name = NULL;
        new_ent->nv.namelen = 0;
      }
      if ((entry_flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) &&
          (entry_flags & NGHTTP2_HD_FLAG_VALUE_GIFT)) {
        /* nv->value are managed by caller. */
        new_ent->nv.value = NULL;
        new_ent->nv.valuelen = 0;
      }

      nghttp2_hd_entry_free(new_ent, mem);
      nghttp2_mem_free(mem, new_ent);

      return NULL;
    }

    new_ent->seq = context->next_seq++;
    new_ent->hash = hash;

    if (map) {
      hd_map_insert(map, new_ent);
    }

    context->hd_table_bufsize += room;
  }
  return new_ent;
}

typedef struct {
  ssize_t index;
  /* Nonzero if both name and value are matched. */
  uint8_t name_value_match;
} search_result;

static search_result search_static_table(const nghttp2_nv *nv, int token,
                                         int indexing_mode) {
  search_result res = {token, 0};
  int i;

  if (indexing_mode == NGHTTP2_HD_NEVER_INDEXING) {
    return res;
  }

  for (i = token;
       i <= NGHTTP2_TOKEN_WWW_AUTHENTICATE && static_table[i].token == token;
       ++i) {
    if (value_eq(&static_table[i].nv, nv)) {
      res.index = i;
      res.name_value_match = 1;
      return res;
    }
  }
  return res;
}

static search_result search_hd_table(nghttp2_hd_context *context,
                                     const nghttp2_nv *nv, int token,
                                     int indexing_mode, nghttp2_hd_map *map,
                                     uint32_t hash) {
  search_result res = {-1, 0};
  nghttp2_hd_entry *ent;
  int exact_match;

  if (token >= 0 && token <= NGHTTP2_TOKEN_WWW_AUTHENTICATE) {
    res = search_static_table(nv, token, indexing_mode);
    if (res.name_value_match) {
      return res;
    }
  }

  exact_match = 0;
  ent = hd_map_find(map, &exact_match, nv, token, hash);
  if (ent == NULL) {
    return res;
  }

  if (res.index != -1 && !exact_match) {
    return res;
  }

  res.index =
      (ssize_t)(context->next_seq - 1 - ent->seq + NGHTTP2_STATIC_TABLE_LENGTH);

  if (exact_match) {
    res.name_value_match = 1;
  }

  return res;
}

static void hd_context_shrink_table_size(nghttp2_hd_context *context,
                                         nghttp2_hd_map *map) {
  nghttp2_mem *mem;

  mem = context->mem;

  while (context->hd_table_bufsize > context->hd_table_bufsize_max &&
         context->hd_table.len > 0) {
    size_t idx = context->hd_table.len - 1;
    nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx);
    context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen);
    hd_ringbuf_pop_back(&context->hd_table);
    if (map) {
      hd_map_remove(map, ent);
    }
    if (--ent->ref == 0) {
      nghttp2_hd_entry_free(ent, mem);
      nghttp2_mem_free(mem, ent);
    }
  }
}

int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
                                         size_t settings_hd_table_bufsize_max) {
  size_t next_bufsize = nghttp2_min(settings_hd_table_bufsize_max,
                                    deflater->deflate_hd_table_bufsize_max);

  deflater->ctx.hd_table_bufsize_max = next_bufsize;

  deflater->min_hd_table_bufsize_max =
      nghttp2_min(deflater->min_hd_table_bufsize_max, next_bufsize);

  deflater->notify_table_size_change = 1;

  hd_context_shrink_table_size(&deflater->ctx, &deflater->map);
  return 0;
}

int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,
                                         size_t settings_hd_table_bufsize_max) {
  switch (inflater->state) {
  case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE:
  case NGHTTP2_HD_STATE_INFLATE_START:
    break;
  default:
    return NGHTTP2_ERR_INVALID_STATE;
  }

  /* It seems that encoder is not required to send dynamic table size
     update if the table size is not changed after applying
     SETTINGS_HEADER_TABLE_SIZE.  RFC 7541 is ambiguous here, but this
     is the intention of the editor.  If new maximum table size is
     strictly smaller than the current negotiated maximum size,
     encoder must send dynamic table size update.  In other cases, we
     cannot expect it to do so. */
  if (inflater->ctx.hd_table_bufsize_max > settings_hd_table_bufsize_max) {
    inflater->state = NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE;
    /* Remember minimum value, and validate that encoder sends the
       value less than or equal to this. */
    inflater->min_hd_table_bufsize_max = settings_hd_table_bufsize_max;
  }

  inflater->settings_hd_table_bufsize_max = settings_hd_table_bufsize_max;

  inflater->ctx.hd_table_bufsize_max = settings_hd_table_bufsize_max;

  hd_context_shrink_table_size(&inflater->ctx, NULL);
  return 0;
}

#define INDEX_RANGE_VALID(context, idx)                                        \
  ((idx) < (context)->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH)

static size_t get_max_index(nghttp2_hd_context *context) {
  return context->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH - 1;
}

nghttp2_hd_entry *nghttp2_hd_table_get(nghttp2_hd_context *context,
                                       size_t idx) {
  assert(INDEX_RANGE_VALID(context, idx));
  if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) {
    return hd_ringbuf_get(&context->hd_table,
                          idx - NGHTTP2_STATIC_TABLE_LENGTH);
  } else {
    return &static_table[idx];
  }
}

static int hd_deflate_decide_indexing(nghttp2_hd_deflater *deflater,
                                      const nghttp2_nv *nv, int token) {
  if (token == NGHTTP2_TOKEN__PATH || token == NGHTTP2_TOKEN_AGE ||
      token == NGHTTP2_TOKEN_CONTENT_LENGTH || token == NGHTTP2_TOKEN_ETAG ||
      token == NGHTTP2_TOKEN_IF_MODIFIED_SINCE ||
      token == NGHTTP2_TOKEN_IF_NONE_MATCH || token == NGHTTP2_TOKEN_LOCATION ||
      token == NGHTTP2_TOKEN_SET_COOKIE ||
      entry_room(nv->namelen, nv->valuelen) >
          deflater->ctx.hd_table_bufsize_max * 3 / 4) {
    return NGHTTP2_HD_WITHOUT_INDEXING;
  }

  return NGHTTP2_HD_WITH_INDEXING;
}

static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs,
                      const nghttp2_nv *nv) {
  int rv;
  search_result res;
  ssize_t idx;
  int indexing_mode;
  int token;
  nghttp2_mem *mem;
  uint32_t hash;

  DEBUGF(fprintf(stderr, "deflatehd: deflating %.*s: %.*s\n", (int)nv->namelen,
                 nv->name, (int)nv->valuelen, nv->value));

  mem = deflater->ctx.mem;

  token = lookup_token(nv->name, nv->namelen);
  if (token == -1 || token > NGHTTP2_TOKEN_WWW_AUTHENTICATE) {
    hash = name_hash(nv);
  } else {
    hash = static_table[token].hash;
  }

  /* Don't index authorization header field since it may contain low
     entropy secret data (e.g., id/password).  Also cookie header
     field with less than 20 bytes value is also never indexed.  This
     is the same criteria used in Firefox codebase. */
  indexing_mode =
      token == NGHTTP2_TOKEN_AUTHORIZATION ||
              (token == NGHTTP2_TOKEN_COOKIE && nv->valuelen < 20) ||
              (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX)
          ? NGHTTP2_HD_NEVER_INDEXING
          : hd_deflate_decide_indexing(deflater, nv, token);

  res = search_hd_table(&deflater->ctx, nv, token, indexing_mode,
                        &deflater->map, hash);

  idx = res.index;

  if (res.name_value_match) {

    DEBUGF(fprintf(stderr, "deflatehd: name/value match index=%zd\n", idx));

    rv = emit_indexed_block(bufs, (size_t)idx);
    if (rv != 0) {
      return rv;
    }

    return 0;
  }

  if (res.index != -1) {
    DEBUGF(fprintf(stderr, "deflatehd: name match index=%zd\n", res.index));
  }

  if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) {
    nghttp2_hd_entry *new_ent;
    if (idx != -1 && idx < (ssize_t)NGHTTP2_STATIC_TABLE_LENGTH) {
      nghttp2_nv nv_indname;
      nv_indname = *nv;
      nv_indname.name =
          nghttp2_hd_table_get(&deflater->ctx, (size_t)idx)->nv.name;
      new_ent = add_hd_table_incremental(&deflater->ctx, &nv_indname, token,
                                         NGHTTP2_HD_FLAG_VALUE_ALLOC,
                                         &deflater->map, hash);
    } else {
      new_ent = add_hd_table_incremental(&deflater->ctx, nv, token,
                                         NGHTTP2_HD_FLAG_NAME_ALLOC |
                                             NGHTTP2_HD_FLAG_VALUE_ALLOC,
                                         &deflater->map, hash);
    }
    if (!new_ent) {
      return NGHTTP2_ERR_HEADER_COMP;
    }
    if (new_ent->ref == 0) {
      nghttp2_hd_entry_free(new_ent, mem);
      nghttp2_mem_free(mem, new_ent);
    }
  }
  if (idx == -1) {
    rv = emit_newname_block(bufs, nv, indexing_mode);
  } else {
    rv = emit_indname_block(bufs, (size_t)idx, nv, indexing_mode);
  }
  if (rv != 0) {
    return rv;
  }

  return 0;
}

int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater,
                               nghttp2_bufs *bufs, const nghttp2_nv *nv,
                               size_t nvlen) {
  size_t i;
  int rv = 0;

  if (deflater->ctx.bad) {
    return NGHTTP2_ERR_HEADER_COMP;
  }

  if (deflater->notify_table_size_change) {
    size_t min_hd_table_bufsize_max;

    min_hd_table_bufsize_max = deflater->min_hd_table_bufsize_max;

    deflater->notify_table_size_change = 0;
    deflater->min_hd_table_bufsize_max = UINT32_MAX;

    if (deflater->ctx.hd_table_bufsize_max > min_hd_table_bufsize_max) {

      rv = emit_table_size(bufs, min_hd_table_bufsize_max);

      if (rv != 0) {
        goto fail;
      }
    }

    rv = emit_table_size(bufs, deflater->ctx.hd_table_bufsize_max);

    if (rv != 0) {
      goto fail;
    }
  }

  for (i = 0; i < nvlen; ++i) {
    rv = deflate_nv(deflater, bufs, &nv[i]);
    if (rv != 0) {
      goto fail;
    }
  }

  DEBUGF(
      fprintf(stderr, "deflatehd: all input name/value pairs were deflated\n"));

  return 0;
fail:
  DEBUGF(fprintf(stderr, "deflatehd: error return %d\n", rv));

  deflater->ctx.bad = 1;
  return rv;
}

ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf,
                              size_t buflen, const nghttp2_nv *nv,
                              size_t nvlen) {
  nghttp2_bufs bufs;
  int rv;
  nghttp2_mem *mem;

  mem = deflater->ctx.mem;

  rv = nghttp2_bufs_wrap_init(&bufs, buf, buflen, mem);

  if (rv != 0) {
    return rv;
  }

  rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nv, nvlen);

  buflen = nghttp2_bufs_len(&bufs);

  nghttp2_bufs_wrap_free(&bufs);

  if (rv == NGHTTP2_ERR_BUFFER_ERROR) {
    return NGHTTP2_ERR_INSUFF_BUFSIZE;
  }

  if (rv != 0) {
    return rv;
  }

  return (ssize_t)buflen;
}

size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater _U_,
                                const nghttp2_nv *nva, size_t nvlen) {
  size_t n = 0;
  size_t i;

  /* Possible Maximum Header Table Size Change.  Encoding (1u << 31) -
     1 using 4 bit prefix requires 6 bytes.  We may emit this at most
     twice. */
  n += 12;

  /* Use Literal Header Field without indexing - New Name, since it is
     most space consuming format.  Also we choose the less one between
     non-huffman and huffman, so using literal byte count is
     sufficient for upper bound.

     Encoding (1u << 31) - 1 using 7 bit prefix requires 6 bytes.  We
     need 2 of this for |nvlen| header fields. */
  n += 6 * 2 * nvlen;

  for (i = 0; i < nvlen; ++i) {
    n += nva[i].namelen + nva[i].valuelen;
  }

  return n;
}

int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
                           size_t deflate_hd_table_bufsize_max) {
  return nghttp2_hd_deflate_new2(deflater_ptr, deflate_hd_table_bufsize_max,
                                 NULL);
}

int nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr,
                            size_t deflate_hd_table_bufsize_max,
                            nghttp2_mem *mem) {
  int rv;
  nghttp2_hd_deflater *deflater;

  if (mem == NULL) {
    mem = nghttp2_mem_default();
  }

  deflater = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_deflater));

  if (deflater == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }

  rv = nghttp2_hd_deflate_init2(deflater, deflate_hd_table_bufsize_max, mem);

  if (rv != 0) {
    nghttp2_mem_free(mem, deflater);

    return rv;
  }

  *deflater_ptr = deflater;

  return 0;
}

void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater) {
  nghttp2_mem *mem;

  mem = deflater->ctx.mem;

  nghttp2_hd_deflate_free(deflater);

  nghttp2_mem_free(mem, deflater);
}

static void hd_inflate_set_huffman_encoded(nghttp2_hd_inflater *inflater,
                                           const uint8_t *in) {
  inflater->huffman_encoded = (*in & (1 << 7)) != 0;
}

/*
 * Decodes the integer from the range [in, last).  The result is
 * assigned to |inflater->left|.  If the |inflater->left| is 0, then
 * it performs variable integer decoding from scratch. Otherwise, it
 * uses the |inflater->left| as the initial value and continues to
 * decode assuming that [in, last) begins with intermediary sequence.
 *
 * This function returns the number of bytes read if it succeeds, or
 * one of the following negative error codes:
 *
 * NGHTTP2_ERR_HEADER_COMP
 *   Integer decoding failed
 */
static ssize_t hd_inflate_read_len(nghttp2_hd_inflater *inflater, int *rfin,
                                   uint8_t *in, uint8_t *last, size_t prefix,
                                   size_t maxlen) {
  ssize_t rv;
  uint32_t out;

  *rfin = 0;

  rv = decode_length(&out, &inflater->shift, rfin, (uint32_t)inflater->left,
                     inflater->shift, in, last, prefix);

  if (rv == -1) {
    DEBUGF(fprintf(stderr, "inflatehd: integer decoding failed\n"));
    return NGHTTP2_ERR_HEADER_COMP;
  }

  if (out > maxlen) {
    DEBUGF(fprintf(
        stderr, "inflatehd: integer exceeded the maximum value %zu\n", maxlen));
    return NGHTTP2_ERR_HEADER_COMP;
  }

  inflater->left = out;

  DEBUGF(fprintf(stderr, "inflatehd: decoded integer is %u\n", out));

  return rv;
}

/*
 * Reads |inflater->left| bytes from the range [in, last) and performs
 * huffman decoding against them and pushes the result into the
 * |buffer|.
 *
 * This function returns the number of bytes read if it succeeds, or
 * one of the following negative error codes:
 *
 * NGHTTP2_ERR_NOMEM
 *   Out of memory
 * NGHTTP2_ERR_HEADER_COMP
 *   Huffman decoding failed
 * NGHTTP2_ERR_BUFFER_ERROR
 *     Out of buffer space.
 */
static ssize_t hd_inflate_read_huff(nghttp2_hd_inflater *inflater,
                                    nghttp2_bufs *bufs, uint8_t *in,
                                    uint8_t *last) {
  ssize_t readlen;
  int final = 0;
  if ((size_t)(last - in) >= inflater->left) {
    last = in + inflater->left;
    final = 1;
  }
  readlen = nghttp2_hd_huff_decode(&inflater->huff_decode_ctx, bufs, in,
                                   (size_t)(last - in), final);

  if (readlen < 0) {
    DEBUGF(fprintf(stderr, "inflatehd: huffman decoding failed\n"));
    return readlen;
  }
  inflater->left -= (size_t)readlen;
  return readlen;
}

/*
 * Reads |inflater->left| bytes from the range [in, last) and copies
 * them into the |buffer|.
 *
 * This function returns the number of bytes read if it succeeds, or
 * one of the following negative error codes:
 *
 * NGHTTP2_ERR_NOMEM
 *   Out of memory
 * NGHTTP2_ERR_HEADER_COMP
 *   Header decompression failed
 * NGHTTP2_ERR_BUFFER_ERROR
 *     Out of buffer space.
 */
static ssize_t hd_inflate_read(nghttp2_hd_inflater *inflater,
                               nghttp2_bufs *bufs, uint8_t *in, uint8_t *last) {
  int rv;
  size_t len = nghttp2_min((size_t)(last - in), inflater->left);
  rv = nghttp2_bufs_add(bufs, in, len);
  if (rv != 0) {
    return rv;
  }
  inflater->left -= len;
  return (ssize_t)len;
}

/*
 * Finalize indexed header representation reception. If header is
 * emitted, |*nv_out| is filled with that value and 0 is returned. If
 * no header is emitted, 1 is returned.
 *
 * This function returns either 0 or 1 if it succeeds, or one of the
 * following negative error codes:
 *
 * NGHTTP2_ERR_NOMEM
 *   Out of memory
 */
static int hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater,
                                     nghttp2_nv *nv_out, int *token_out) {
  nghttp2_hd_entry *ent = nghttp2_hd_table_get(&inflater->ctx, inflater->index);

  emit_indexed_header(nv_out, token_out, ent);

  return 0;
}

static int hd_inflate_remove_bufs(nghttp2_hd_inflater *inflater, nghttp2_nv *nv,
                                  int value_only) {
  ssize_t rv;
  size_t buflen;
  uint8_t *buf;
  nghttp2_buf *pbuf;

  if (inflater->index_required ||
      inflater->nvbufs.head != inflater->nvbufs.cur) {

    rv = nghttp2_bufs_remove(&inflater->nvbufs, &buf);

    if (rv < 0) {
      return NGHTTP2_ERR_NOMEM;
    }

    nghttp2_bufs_reset(&inflater->nvbufs);

    buflen = (size_t)rv;

    if (value_only) {
      /* we don't use this value, so no need to NULL-terminate */
      nv->name = NULL;
      nv->namelen = 0;

      nv->value = buf;
      nv->valuelen = buflen - 1;
    } else {
      nv->name = buf;
      nv->namelen = inflater->newnamelen;

      nv->value = buf + nv->namelen + 1;
      nv->valuelen = buflen - nv->namelen - 2;
    }

    return 0;
  }

  /* If we are not going to store header in header table and
     name/value are in first chunk, we just refer them from nv,
     instead of mallocing another memory. */

  pbuf = &inflater->nvbufs.head->buf;

  if (value_only) {
    /* we don't use this value, so no need to NULL-terminate */
    nv->name = NULL;
    nv->namelen = 0;

    nv->value = pbuf->pos;
    nv->valuelen = nghttp2_buf_len(pbuf) - 1;
  } else {
    nv->name = pbuf->pos;
    nv->namelen = inflater->newnamelen;

    nv->value = pbuf->pos + nv->namelen + 1;
    nv->valuelen = nghttp2_buf_len(pbuf) - nv->namelen - 2;
  }

  /* Resetting does not change the content of first buffer */
  nghttp2_bufs_reset(&inflater->nvbufs);

  return 0;
}

static int hd_inflate_remove_bufs_with_name(nghttp2_hd_inflater *inflater,
                                            nghttp2_nv *nv,
                                            nghttp2_hd_entry *ent_name) {
#ifndef NDEBUG
  size_t rv;
#endif
  size_t buflen;
  uint8_t *buf;
  nghttp2_mem *mem;

  mem = inflater->ctx.mem;

  /* Allocate buffer including name in ent_name, plus terminating
     NULL. */
  buflen = ent_name->nv.namelen + 1 + nghttp2_bufs_len(&inflater->nvbufs);

  buf = nghttp2_mem_malloc(mem, buflen);
  if (buf == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }

  /* Copy including terminal NULL */
  memcpy(buf, ent_name->nv.name, ent_name->nv.namelen + 1);
#ifndef NDEBUG
  rv =
#endif
      nghttp2_bufs_remove_copy(&inflater->nvbufs,
                               buf + ent_name->nv.namelen + 1);
  assert(ent_name->nv.namelen + 1 + rv == buflen);

  nghttp2_bufs_reset(&inflater->nvbufs);

  nv->name = buf;
  nv->namelen = ent_name->nv.namelen;

  nv->value = buf + nv->namelen + 1;
  nv->valuelen = buflen - nv->namelen - 2;

  return 0;
}

/*
 * Finalize literal header representation - new name- reception. If
 * header is emitted, |*nv_out| is filled with that value and 0 is
 * returned.
 *
 * This function returns 0 if it succeeds, or one of the following
 * negative error codes:
 *
 * NGHTTP2_ERR_NOMEM
 *   Out of memory
 */
static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater,
                                     nghttp2_nv *nv_out, int *token_out) {
  int rv;
  nghttp2_nv nv;
  nghttp2_mem *mem;

  mem = inflater->ctx.mem;

  rv = hd_inflate_remove_bufs(inflater, &nv, 0 /* name and value */);
  if (rv != 0) {
    return NGHTTP2_ERR_NOMEM;
  }

  if (inflater->no_index) {
    nv.flags = NGHTTP2_NV_FLAG_NO_INDEX;
  } else {
    nv.flags = NGHTTP2_NV_FLAG_NONE;
  }

  if (inflater->index_required) {
    nghttp2_hd_entry *new_ent;
    uint8_t ent_flags;

    /* nv->value points to the middle of the buffer pointed by
       nv->name.  So we just need to keep track of nv->name for memory
       management. */
    ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_NAME_GIFT;

    new_ent = add_hd_table_incremental(&inflater->ctx, &nv,
                                       lookup_token(nv.name, nv.namelen),
                                       ent_flags, NULL, 0);

    if (new_ent) {
      emit_indexed_header(nv_out, token_out, new_ent);
      inflater->ent_keep = new_ent;

      return 0;
    }

    nghttp2_mem_free(mem, nv.name);

    return NGHTTP2_ERR_NOMEM;
  }

  emit_literal_header(nv_out, token_out, &nv);

  if (nv.name != inflater->nvbufs.head->buf.pos) {
    inflater->nv_keep = nv.name;
  }

  return 0;
}

/*
 * Finalize literal header representation - indexed name-
 * reception. If header is emitted, |*nv_out| is filled with that
 * value and 0 is returned.
 *
 * This function returns 0 if it succeeds, or one of the following
 * negative error codes:
 *
 * NGHTTP2_ERR_NOMEM
 *   Out of memory
 */
static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
                                     nghttp2_nv *nv_out, int *token_out) {
  int rv;
  nghttp2_nv nv;
  nghttp2_hd_entry *ent_name;
  nghttp2_mem *mem;

  mem = inflater->ctx.mem;

  if (inflater->no_index) {
    nv.flags = NGHTTP2_NV_FLAG_NO_INDEX;
  } else {
    nv.flags = NGHTTP2_NV_FLAG_NONE;
  }

  ent_name = nghttp2_hd_table_get(&inflater->ctx, inflater->index);

  if (inflater->index_required) {
    nghttp2_hd_entry *new_ent;
    uint8_t ent_flags;

    if (inflater->index < NGHTTP2_STATIC_TABLE_LENGTH) {
      /* We don't copy name in static table */
      rv = hd_inflate_remove_bufs(inflater, &nv, 1 /* value only */);
      if (rv != 0) {
        return NGHTTP2_ERR_NOMEM;
      }
      nv.name = ent_name->nv.name;
      nv.namelen = ent_name->nv.namelen;

      ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC | NGHTTP2_HD_FLAG_VALUE_GIFT;
    } else {
      rv = hd_inflate_remove_bufs_with_name(inflater, &nv, ent_name);
      if (rv != 0) {
        return NGHTTP2_ERR_NOMEM;
      }
      /* nv->name and nv->value are in the same buffer. */
      ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_NAME_GIFT;
    }

    new_ent = add_hd_table_incremental(&inflater->ctx, &nv, ent_name->token,
                                       ent_flags, NULL, 0);

    /* At this point, ent_name might be deleted. */

    if (new_ent) {
      emit_indexed_header(nv_out, token_out, new_ent);

      inflater->ent_keep = new_ent;

      return 0;
    }

    if (inflater->index < NGHTTP2_STATIC_TABLE_LENGTH) {
      nghttp2_mem_free(mem, nv.value);
    } else {
      nghttp2_mem_free(mem, nv.name);
    }

    return NGHTTP2_ERR_NOMEM;
  }

  rv = hd_inflate_remove_bufs(inflater, &nv, 1 /* value only */);
  if (rv != 0) {
    return NGHTTP2_ERR_NOMEM;
  }

  nv.name = ent_name->nv.name;
  nv.namelen = ent_name->nv.namelen;

  emit_literal_header(nv_out, token_out, &nv);

  if (nv.value != inflater->nvbufs.head->buf.pos) {
    inflater->nv_keep = nv.value;
  }

  return 0;
}

ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out,
                              int *inflate_flags, uint8_t *in, size_t inlen,
                              int in_final) {
  int token;

  return nghttp2_hd_inflate_hd2(inflater, nv_out, inflate_flags, &token, in,
                                inlen, in_final);
}

ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
                               nghttp2_nv *nv_out, int *inflate_flags,
                               int *token_out, uint8_t *in, size_t inlen,
                               int in_final) {
  ssize_t rv = 0;
  uint8_t *first = in;
  uint8_t *last = in + inlen;
  int rfin = 0;
  int busy = 0;

  if (inflater->ctx.bad) {
    return NGHTTP2_ERR_HEADER_COMP;
  }

  DEBUGF(fprintf(stderr, "inflatehd: start state=%d\n", inflater->state));
  hd_inflate_keep_free(inflater);
  *token_out = -1;
  *inflate_flags = NGHTTP2_HD_INFLATE_NONE;
  for (; in != last || busy;) {
    busy = 0;
    switch (inflater->state) {
    case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE:
      if ((*in & 0xe0u) != 0x20u) {
        DEBUGF(fprintf(stderr, "inflatehd: header table size change was "
                               "expected, but saw 0x%02x as first byte",
                       *in));
        rv = NGHTTP2_ERR_HEADER_COMP;
        goto fail;
      }
    /* fall through */
    case NGHTTP2_HD_STATE_INFLATE_START:
    case NGHTTP2_HD_STATE_OPCODE:
      if ((*in & 0xe0u) == 0x20u) {
        DEBUGF(fprintf(stderr, "inflatehd: header table size change\n"));
        if (inflater->state == NGHTTP2_HD_STATE_OPCODE) {
          DEBUGF(fprintf(stderr, "inflatehd: header table size change must "
                                 "appear at the head of header block\n"));
          rv = NGHTTP2_ERR_HEADER_COMP;
          goto fail;
        }
        inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
        inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE;
      } else if (*in & 0x80u) {
        DEBUGF(fprintf(stderr, "inflatehd: indexed repr\n"));
        inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
        inflater->state = NGHTTP2_HD_STATE_READ_INDEX;
      } else {
        if (*in == 0x40u || *in == 0 || *in == 0x10u) {
          DEBUGF(
              fprintf(stderr, "inflatehd: literal header repr - new name\n"));
          inflater->opcode = NGHTTP2_HD_OPCODE_NEWNAME;
          inflater->state = NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN;
        } else {
          DEBUGF(fprintf(stderr,
                         "inflatehd: literal header repr - indexed name\n"));
          inflater->opcode = NGHTTP2_HD_OPCODE_INDNAME;
          inflater->state = NGHTTP2_HD_STATE_READ_INDEX;
        }
        inflater->index_required = (*in & 0x40) != 0;
        inflater->no_index = (*in & 0xf0u) == 0x10u;
        DEBUGF(fprintf(stderr, "inflatehd: indexing required=%d, no_index=%d\n",
                       inflater->index_required, inflater->no_index));
        if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
          ++in;
        }
      }
      inflater->left = 0;
      inflater->shift = 0;
      break;
    case NGHTTP2_HD_STATE_READ_TABLE_SIZE:
      rfin = 0;
      rv = hd_inflate_read_len(
          inflater, &rfin, in, last, 5,
          nghttp2_min(inflater->min_hd_table_bufsize_max,
                      inflater->settings_hd_table_bufsize_max));
      if (rv < 0) {
        goto fail;
      }
      in += rv;
      if (!rfin) {
        goto almost_ok;
      }
      DEBUGF(fprintf(stderr, "inflatehd: table_size=%zu\n", inflater->left));
      inflater->min_hd_table_bufsize_max = UINT32_MAX;
      inflater->ctx.hd_table_bufsize_max = inflater->left;
      hd_context_shrink_table_size(&inflater->ctx, NULL);
      inflater->state = NGHTTP2_HD_STATE_INFLATE_START;
      break;
    case NGHTTP2_HD_STATE_READ_INDEX: {
      size_t prefixlen;

      if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) {
        prefixlen = 7;
      } else if (inflater->index_required) {
        prefixlen = 6;
      } else {
        prefixlen = 4;
      }

      rfin = 0;
      rv = hd_inflate_read_len(inflater, &rfin, in, last, prefixlen,
                               get_max_index(&inflater->ctx) + 1);
      if (rv < 0) {
        goto fail;
      }

      in += rv;

      if (!rfin) {
        goto almost_ok;
      }

      if (inflater->left == 0) {
        rv = NGHTTP2_ERR_HEADER_COMP;
        goto fail;
      }

      DEBUGF(fprintf(stderr, "inflatehd: index=%zu\n", inflater->left));
      if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) {
        inflater->index = inflater->left;
        --inflater->index;

        rv = hd_inflate_commit_indexed(inflater, nv_out, token_out);
        if (rv < 0) {
          goto fail;
        }
        inflater->state = NGHTTP2_HD_STATE_OPCODE;
        /* If rv == 1, no header was emitted */
        if (rv == 0) {
          *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
          return (ssize_t)(in - first);
        }
      } else {
        inflater->index = inflater->left;
        --inflater->index;

        inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
      }
      break;
    }
    case NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN:
      hd_inflate_set_huffman_encoded(inflater, in);
      inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN;
      inflater->left = 0;
      inflater->shift = 0;
      DEBUGF(fprintf(stderr, "inflatehd: huffman encoded=%d\n",
                     inflater->huffman_encoded != 0));
    /* Fall through */
    case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN:
      rfin = 0;
      rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, NGHTTP2_HD_MAX_NV);
      if (rv < 0) {
        goto fail;
      }
      in += rv;
      if (!rfin) {
        DEBUGF(fprintf(stderr,
                       "inflatehd: integer not fully decoded. current=%zu\n",
                       inflater->left));

        goto almost_ok;
      }

      if (inflater->huffman_encoded) {
        nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx);

        inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF;
      } else {
        inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME;
      }
      break;
    case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF:
      rv = hd_inflate_read_huff(inflater, &inflater->nvbufs, in, last);
      if (rv < 0) {
        goto fail;
      }

      in += rv;

      DEBUGF(fprintf(stderr, "inflatehd: %zd bytes read\n", rv));

      if (inflater->left) {
        DEBUGF(fprintf(stderr, "inflatehd: still %zu bytes to go\n",
                       inflater->left));

        goto almost_ok;
      }

      inflater->newnamelen = nghttp2_bufs_len(&inflater->nvbufs);

      rv = nghttp2_bufs_addb(&inflater->nvbufs, '\0');
      if (rv != 0) {
        goto fail;
      }

      inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;

      break;
    case NGHTTP2_HD_STATE_NEWNAME_READ_NAME:
      rv = hd_inflate_read(inflater, &inflater->nvbufs, in, last);
      if (rv < 0) {
        goto fail;
      }

      in += rv;

      DEBUGF(fprintf(stderr, "inflatehd: %zd bytes read\n", rv));
      if (inflater->left) {
        DEBUGF(fprintf(stderr, "inflatehd: still %zu bytes to go\n",
                       inflater->left));

        goto almost_ok;
      }

      inflater->newnamelen = nghttp2_bufs_len(&inflater->nvbufs);

      rv = nghttp2_bufs_addb(&inflater->nvbufs, '\0');
      if (rv != 0) {
        goto fail;
      }

      inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;

      break;
    case NGHTTP2_HD_STATE_CHECK_VALUELEN:
      hd_inflate_set_huffman_encoded(inflater, in);
      inflater->state = NGHTTP2_HD_STATE_READ_VALUELEN;
      inflater->left = 0;
      inflater->shift = 0;
      DEBUGF(fprintf(stderr, "inflatehd: huffman encoded=%d\n",
                     inflater->huffman_encoded != 0));
    /* Fall through */
    case NGHTTP2_HD_STATE_READ_VALUELEN:
      rfin = 0;
      rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, NGHTTP2_HD_MAX_NV);
      if (rv < 0) {
        goto fail;
      }

      in += rv;

      if (!rfin) {
        goto almost_ok;
      }

      DEBUGF(fprintf(stderr, "inflatehd: valuelen=%zu\n", inflater->left));

      if (inflater->huffman_encoded) {
        nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx);

        inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF;
      } else {
        inflater->state = NGHTTP2_HD_STATE_READ_VALUE;
      }

      busy = 1;

      break;
    case NGHTTP2_HD_STATE_READ_VALUEHUFF:
      rv = hd_inflate_read_huff(inflater, &inflater->nvbufs, in, last);
      if (rv < 0) {
        goto fail;
      }

      in += rv;

      DEBUGF(fprintf(stderr, "inflatehd: %zd bytes read\n", rv));

      if (inflater->left) {
        DEBUGF(fprintf(stderr, "inflatehd: still %zu bytes to go\n",
                       inflater->left));

        goto almost_ok;
      }

      rv = nghttp2_bufs_addb(&inflater->nvbufs, '\0');
      if (rv != 0) {
        goto fail;
      }

      if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
        rv = hd_inflate_commit_newname(inflater, nv_out, token_out);
      } else {
        rv = hd_inflate_commit_indname(inflater, nv_out, token_out);
      }

      if (rv != 0) {
        goto fail;
      }

      inflater->state = NGHTTP2_HD_STATE_OPCODE;
      *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;

      return (ssize_t)(in - first);
    case NGHTTP2_HD_STATE_READ_VALUE:
      rv = hd_inflate_read(inflater, &inflater->nvbufs, in, last);
      if (rv < 0) {
        DEBUGF(fprintf(stderr, "inflatehd: value read failure %zd: %s\n", rv,
                       nghttp2_strerror((int)rv)));
        goto fail;
      }

      in += rv;

      DEBUGF(fprintf(stderr, "inflatehd: %zd bytes read\n", rv));

      if (inflater->left) {
        DEBUGF(fprintf(stderr, "inflatehd: still %zu bytes to go\n",
                       inflater->left));
        goto almost_ok;
      }

      rv = nghttp2_bufs_addb(&inflater->nvbufs, '\0');
      if (rv != 0) {
        goto fail;
      }

      if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
        rv = hd_inflate_commit_newname(inflater, nv_out, token_out);
      } else {
        rv = hd_inflate_commit_indname(inflater, nv_out, token_out);
      }

      if (rv != 0) {
        goto fail;
      }

      inflater->state = NGHTTP2_HD_STATE_OPCODE;
      *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;

      return (ssize_t)(in - first);
    }
  }

  assert(in == last);

  DEBUGF(fprintf(stderr, "inflatehd: all input bytes were processed\n"));

  if (in_final) {
    DEBUGF(fprintf(stderr, "inflatehd: in_final set\n"));

    if (inflater->state != NGHTTP2_HD_STATE_OPCODE &&
        inflater->state != NGHTTP2_HD_STATE_INFLATE_START) {
      DEBUGF(fprintf(stderr, "inflatehd: unacceptable state=%d\n",
                     inflater->state));
      rv = NGHTTP2_ERR_HEADER_COMP;

      goto fail;
    }
    *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL;
  }
  return (ssize_t)(in - first);

almost_ok:
  if (in_final) {
    DEBUGF(fprintf(stderr, "inflatehd: input ended prematurely\n"));

    rv = NGHTTP2_ERR_HEADER_COMP;

    goto fail;
  }
  return (ssize_t)(in - first);

fail:
  DEBUGF(fprintf(stderr, "inflatehd: error return %zd\n", rv));

  inflater->ctx.bad = 1;
  return rv;
}

int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater) {
  hd_inflate_keep_free(inflater);
  inflater->state = NGHTTP2_HD_STATE_INFLATE_START;
  return 0;
}

int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr) {
  return nghttp2_hd_inflate_new2(inflater_ptr, NULL);
}

int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr,
                            nghttp2_mem *mem) {
  int rv;
  nghttp2_hd_inflater *inflater;

  if (mem == NULL) {
    mem = nghttp2_mem_default();
  }

  inflater = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_inflater));

  if (inflater == NULL) {
    return NGHTTP2_ERR_NOMEM;
  }

  rv = nghttp2_hd_inflate_init(inflater, mem);

  if (rv != 0) {
    nghttp2_mem_free(mem, inflater);

    return rv;
  }

  *inflater_ptr = inflater;

  return 0;
}

void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater) {
  nghttp2_mem *mem;

  mem = inflater->ctx.mem;
  nghttp2_hd_inflate_free(inflater);

  nghttp2_mem_free(mem, inflater);
}

int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t idx,
                                  nghttp2_nv *nv, int indexing_mode) {

  return emit_indname_block(bufs, idx, nv, indexing_mode);
}

int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv,
                                  int indexing_mode) {
  return emit_newname_block(bufs, nv, indexing_mode);
}

int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size) {
  return emit_table_size(bufs, table_size);
}

ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *final,
                                 uint32_t initial, size_t shift, uint8_t *in,
                                 uint8_t *last, size_t prefix) {
  return decode_length(res, shift_ptr, final, initial, shift, in, last, prefix);
}

static size_t hd_get_num_table_entries(nghttp2_hd_context *context) {
  return context->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH;
}

static const nghttp2_nv *hd_get_table_entry(nghttp2_hd_context *context,
                                            size_t idx) {
  if (idx == 0) {
    return NULL;
  }

  --idx;

  if (!INDEX_RANGE_VALID(context, idx)) {
    return NULL;
  }

  return &nghttp2_hd_table_get(context, idx)->nv;
}

size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater) {
  return hd_get_num_table_entries(&deflater->ctx);
}

const nghttp2_nv *
nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx) {
  return hd_get_table_entry(&deflater->ctx, idx);
}

size_t
nghttp2_hd_deflate_get_dynamic_table_size(nghttp2_hd_deflater *deflater) {
  return deflater->ctx.hd_table_bufsize;
}

size_t
nghttp2_hd_deflate_get_max_dynamic_table_size(nghttp2_hd_deflater *deflater) {
  return deflater->ctx.hd_table_bufsize_max;
}

size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater) {
  return hd_get_num_table_entries(&inflater->ctx);
}

const nghttp2_nv *
nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx) {
  return hd_get_table_entry(&inflater->ctx, idx);
}

size_t
nghttp2_hd_inflate_get_dynamic_table_size(nghttp2_hd_inflater *inflater) {
  return inflater->ctx.hd_table_bufsize;
}

size_t
nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater) {
  return inflater->ctx.hd_table_bufsize_max;
}
