diff --git a/README.md b/README.md
index 98ecd21..ca52095 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,8 @@ Here is the basic patch content.
| openssl-equal-pre2.patch | **_Not support_** draft **28**. |
| openssl-equal-pre7.patch
openssl-equal-pre8.patch | TLS 1.3 cipher settings **_can not_** be changed on _nginx_. |
| openssl-equal-pre7_ciphers.patch
openssl-equal-pre8_ciphers.patch | TLS 1.3 cipher settings **_can_** be changed on _nginx_. |
+| nginx_hpack_push.patch | _Patch both_ the HPACK patch and the **PUSH ERROR**. |
+| nginx_hpack_push_fix.patch | _Patch only_ the **PUSH ERROR** of the hpack patch. (If the HPACK patch has already been completed) |
**The "_ciphers" patch file is a temporary change to the TLS 1.3 configuration.**
diff --git a/nginx_hpack_push.patch b/nginx_hpack_push.patch
index 02da832..0d65f97 100644
--- a/nginx_hpack_push.patch
+++ b/nginx_hpack_push.patch
@@ -1,29 +1,596 @@
+diff --git a/auto/modules b/auto/modules
+index 73a9bae9..67fe2cb3 100644
+--- a/auto/modules
++++ b/auto/modules
+@@ -438,6 +438,10 @@ if [ $HTTP = YES ]; then
+ . auto/module
+ fi
+
++ if [ $HTTP_V2_HPACK_ENC = YES ]; then
++ have=NGX_HTTP_V2_HPACK_ENC . auto/have
++ fi
++
+ if :; then
+ ngx_module_name=ngx_http_static_module
+ ngx_module_incs=
+diff --git a/auto/options b/auto/options
+index 59f0449d..4abd20ac 100644
+--- a/auto/options
++++ b/auto/options
+@@ -59,6 +59,7 @@ HTTP_CHARSET=YES
+ HTTP_GZIP=YES
+ HTTP_SSL=NO
+ HTTP_V2=NO
++HTTP_V2_HPACK_ENC=NO
+ HTTP_SSI=YES
+ HTTP_POSTPONE=NO
+ HTTP_REALIP=NO
+@@ -223,6 +224,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated"
+
+ --with-http_ssl_module) HTTP_SSL=YES ;;
+ --with-http_v2_module) HTTP_V2=YES ;;
++ --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;;
+ --with-http_realip_module) HTTP_REALIP=YES ;;
+ --with-http_addition_module) HTTP_ADDITION=YES ;;
+ --with-http_xslt_module) HTTP_XSLT=YES ;;
+@@ -434,6 +436,7 @@ cat << END
+
+ --with-http_ssl_module enable ngx_http_ssl_module
+ --with-http_v2_module enable ngx_http_v2_module
++ --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc
+ --with-http_realip_module enable ngx_http_realip_module
+ --with-http_addition_module enable ngx_http_addition_module
+ --with-http_xslt_module enable ngx_http_xslt_module
+diff --git a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c
+index 5ade658d..4932f20d 100644
+--- a/src/core/ngx_murmurhash.c
++++ b/src/core/ngx_murmurhash.c
+@@ -50,3 +50,63 @@ ngx_murmur_hash2(u_char *data, size_t len)
+
+ return h;
+ }
++
++
++uint64_t
++ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed)
++{
++ uint64_t h, k;
++
++ h = seed ^ len;
++
++ while (len >= 8) {
++ k = data[0];
++ k |= data[1] << 8;
++ k |= data[2] << 16;
++ k |= data[3] << 24;
++ k |= (uint64_t)data[4] << 32;
++ k |= (uint64_t)data[5] << 40;
++ k |= (uint64_t)data[6] << 48;
++ k |= (uint64_t)data[7] << 56;
++
++ k *= 0xc6a4a7935bd1e995ull;
++ k ^= k >> 47;
++ k *= 0xc6a4a7935bd1e995ull;
++
++ h ^= k;
++ h *= 0xc6a4a7935bd1e995ull;
++
++ data += 8;
++ len -= 8;
++ }
++
++ switch (len) {
++ case 7:
++ h ^= (uint64_t)data[6] << 48;
++ /* fall through */
++ case 6:
++ h ^= (uint64_t)data[5] << 40;
++ /* fall through */
++ case 5:
++ h ^= (uint64_t)data[4] << 32;
++ /* fall through */
++ case 4:
++ h ^= data[3] << 24;
++ /* fall through */
++ case 3:
++ h ^= data[2] << 16;
++ /* fall through */
++ case 2:
++ h ^= data[1] << 8;
++ /* fall through */
++ case 1:
++ h ^= data[0];
++ h *= 0xc6a4a7935bd1e995ull;
++ }
++
++ h ^= h >> 47;
++ h *= 0xc6a4a7935bd1e995ull;
++ h ^= h >> 47;
++
++ return h;
++}
+diff --git a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h
+index 54e867d3..322b3df9 100644
+--- a/src/core/ngx_murmurhash.h
++++ b/src/core/ngx_murmurhash.h
+@@ -15,5 +15,7 @@
+
+ uint32_t ngx_murmur_hash2(u_char *data, size_t len);
+
++uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed);
++
+
+ #endif /* _NGX_MURMURHASH_H_INCLUDED_ */
+diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c
+index 77ebb847..303d157f 100644
+--- a/src/http/v2/ngx_http_v2.c
++++ b/src/http/v2/ngx_http_v2.c
+@@ -272,6 +272,8 @@ ngx_http_v2_init(ngx_event_t *rev)
+
+ h2c->table_update = 1;
+
++ h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE;
++
+ h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module);
+
+ h2c->concurrent_pushes = h2scf->concurrent_pushes;
+@@ -2075,6 +2077,17 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos,
+ h2c->concurrent_pushes = ngx_min(value, h2scf->concurrent_pushes);
+ break;
+
++ case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING:
++
++ if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) {
++ h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE;
++ } else {
++ h2c->max_hpack_table_size = value;
++ }
++
++ h2c->indicate_resize = 1;
++ break;
++
+ default:
+ break;
+ }
diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h
-index ae826af..41086a6 100644
+index ebd0e77c..bb89a634 100644
--- a/src/http/v2/ngx_http_v2.h
+++ b/src/http/v2/ngx_http_v2.h
-@@ -490,4 +490,8 @@ ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c);
- ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
- val.data, val.len, tmp);
+@@ -52,6 +52,14 @@
+ #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1)
+ #define NGX_HTTP_V2_DEFAULT_WINDOW 65535
-+#define ngx_http_v2_write_header_pot(key, val) \
++#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */
++#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128)
++#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */
++#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */
++
++#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096
++#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */
++
+ #define NGX_HTTP_V2_DEFAULT_WEIGHT 16
+
+
+@@ -115,6 +123,46 @@ typedef struct {
+ } ngx_http_v2_hpack_t;
+
+
++#if (NGX_HTTP_V2_HPACK_ENC)
++typedef struct {
++ uint64_t hash_val;
++ uint32_t index;
++ uint16_t pos;
++ uint16_t klen, vlen;
++ uint16_t size;
++ uint16_t next;
++} ngx_http_v2_hpack_enc_entry_t;
++
++
++typedef struct {
++ uint64_t hash_val;
++ uint32_t index;
++ uint16_t pos;
++ uint16_t klen;
++} ngx_http_v2_hpack_name_entry_t;
++
++
++typedef struct {
++ size_t size; /* size as defined in RFC 7541 */
++ uint32_t top; /* the last entry */
++ uint32_t pos;
++ uint16_t n_elems; /* number of elements */
++ uint16_t base; /* index of the oldest entry */
++ uint16_t last; /* index of the newest entry */
++
++ /* hash table for dynamic entries, instead using a generic hash table,
++ which would be too slow to process a significant amount of headers,
++ this table is not determenistic, and might ocasionally fail to insert
++ a value, at the cost of slightly worse compression, but significantly
++ faster performance */
++ ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ];
++ ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ];
++ u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE +
++ HPACK_ENC_MAX_ENTRY];
++} ngx_http_v2_hpack_enc_t;
++#endif
++
++
+ struct ngx_http_v2_connection_s {
+ ngx_connection_t *connection;
+ ngx_http_connection_t *http_connection;
+@@ -130,6 +178,8 @@ struct ngx_http_v2_connection_s {
+
+ size_t frame_size;
+
++ size_t max_hpack_table_size;
++
+ ngx_queue_t waiting;
+
+ ngx_http_v2_state_t state;
+@@ -157,6 +207,11 @@ struct ngx_http_v2_connection_s {
+ unsigned blocked:1;
+ unsigned goaway:1;
+ unsigned push_disabled:1;
++ unsigned indicate_resize:1;
++
++#if (NGX_HTTP_V2_HPACK_ENC)
++ ngx_http_v2_hpack_enc_t hpack_enc;
++#endif
+ };
+
+
+@@ -198,6 +253,8 @@ struct ngx_http_v2_stream_s {
+
+ ngx_array_t *cookies;
+
++ size_t header_limit;
++
+ ngx_pool_t *pool;
+
+ unsigned waiting:1;
+@@ -410,4 +467,31 @@ u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
+ u_char *tmp, ngx_uint_t lower);
+
+
++u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
++ u_char *tmp, ngx_uint_t lower);
++
++u_char *
++ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value);
++
++#define ngx_http_v2_write_name(dst, src, len, tmp) \
++ ngx_http_v2_string_encode(dst, src, len, tmp, 1)
++#define ngx_http_v2_write_value(dst, src, len, tmp) \
++ ngx_http_v2_string_encode(dst, src, len, tmp, 0)
++
++u_char *
++ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
++ u_char *key, size_t key_len, u_char *value, size_t value_len,
++ u_char *tmp);
++
++void
++ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c);
++
++#define ngx_http_v2_write_header_str(key, value) \
+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
-+ val->data, val->len, tmp);
++ (u_char *) value, sizeof(value) - 1, tmp);
++
++#define ngx_http_v2_write_header_tbl(key, val) \
++ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
++ val.data, val.len, tmp);
+
#endif /* _NGX_HTTP_V2_H_INCLUDED_ */
+diff --git a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c
+index ac792084..d1fb7217 100644
+--- a/src/http/v2/ngx_http_v2_encode.c
++++ b/src/http/v2/ngx_http_v2_encode.c
+@@ -10,7 +10,7 @@
+ #include
+
+
+-static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
++u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
+ ngx_uint_t value);
+
+
+@@ -40,7 +40,7 @@ ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp,
+ }
+
+
+-static u_char *
++u_char *
+ ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)
+ {
+ if (value < prefix) {
diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c
-index 6a210d4..bd9a02f 100644
+index 029e8ece..d293f06c 100644
--- a/src/http/v2/ngx_http_v2_filter_module.c
+++ b/src/http/v2/ngx_http_v2_filter_module.c
-@@ -955,6 +955,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+@@ -23,10 +23,53 @@
+ #define ngx_http_v2_literal_size(h) \
+ (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1)
+
++#define ngx_http_v2_indexed(i) (128 + (i))
++#define ngx_http_v2_inc_indexed(i) (64 + (i))
++
++#define NGX_HTTP_V2_ENCODE_RAW 0
++#define NGX_HTTP_V2_ENCODE_HUFF 0x80
++
++#define NGX_HTTP_V2_AUTHORITY_INDEX 1
++#define NGX_HTTP_V2_METHOD_GET_INDEX 2
++#define NGX_HTTP_V2_PATH_INDEX 4
++
++#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6
++#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7
++
++#define NGX_HTTP_V2_STATUS_INDEX 8
++#define NGX_HTTP_V2_STATUS_200_INDEX 8
++#define NGX_HTTP_V2_STATUS_204_INDEX 9
++#define NGX_HTTP_V2_STATUS_206_INDEX 10
++#define NGX_HTTP_V2_STATUS_304_INDEX 11
++#define NGX_HTTP_V2_STATUS_400_INDEX 12
++#define NGX_HTTP_V2_STATUS_404_INDEX 13
++#define NGX_HTTP_V2_STATUS_500_INDEX 14
++
++#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16
++#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17
++#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28
++#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31
++#define NGX_HTTP_V2_DATE_INDEX 33
++#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44
++#define NGX_HTTP_V2_LOCATION_INDEX 46
++#define NGX_HTTP_V2_SERVER_INDEX 54
++#define NGX_HTTP_V2_USER_AGENT_INDEX 58
++#define NGX_HTTP_V2_VARY_INDEX 59
+
+ #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1
+
+
++static const struct {
++ u_char *name;
++ u_char const len;
++} push_header[] = {
++ { (u_char*)":authority" , 10 },
++ { (u_char*)"accept-encoding" , 15 },
++ { (u_char*)"accept-language" , 15 },
++ { (u_char*)"user-agent" , 10 }
++};
++
++
+ typedef struct {
+ ngx_str_t name;
+ u_char index;
+@@ -155,11 +198,9 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
+ #endif
+
+ static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER);
+- static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)];
+
+ static size_t nginx_ver_build_len =
+ ngx_http_v2_literal_size(NGINX_VER_BUILD);
+- static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)];
+
+ stream = r->stream;
+
+@@ -435,7 +476,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
+ }
+
+ tmp = ngx_palloc(r->pool, tmp_len);
+- pos = ngx_pnalloc(r->pool, len);
++ pos = ngx_pnalloc(r->pool, len + 15 + 1);
+
+ if (pos == NULL || tmp == NULL) {
+ return NGX_ERROR;
+@@ -443,11 +484,16 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
+
+ start = pos;
+
+- if (h2c->table_update) {
+- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 table size update: 0");
+- *pos++ = (1 << 5) | 0;
+- h2c->table_update = 0;
++ h2c = r->stream->connection;
++
++ if (h2c->indicate_resize) {
++ *pos = 32;
++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5),
++ h2c->max_hpack_table_size);
++ h2c->indicate_resize = 0;
++#if (NGX_HTTP_V2_HPACK_ENC)
++ ngx_http_v2_table_resize(h2c);
++#endif
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+@@ -458,67 +504,28 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
+ *pos++ = status;
+
+ } else {
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX);
+- *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3;
+- pos = ngx_sprintf(pos, "%03ui", r->headers_out.status);
++ ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status);
++ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status",
++ sizeof(":status") - 1, pos + 8, 3, tmp);
+ }
+
+ if (r->headers_out.server == NULL) {
+-
+- if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"server: %s\"",
+- NGINX_VER);
+-
+- } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"server: %s\"",
+- NGINX_VER_BUILD);
+-
+- } else {
+- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"server: nginx\"");
+- }
+-
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX);
+-
+ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+- if (nginx_ver[0] == '\0') {
+- p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER,
+- sizeof(NGINX_VER) - 1, tmp);
+- nginx_ver_len = p - nginx_ver;
+- }
+-
+- pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len);
++ pos = ngx_http_v2_write_header_str("server", NGINX_VER);
+
+ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+- if (nginx_ver_build[0] == '\0') {
+- p = ngx_http_v2_write_value(nginx_ver_build,
+- (u_char *) NGINX_VER_BUILD,
+- sizeof(NGINX_VER_BUILD) - 1, tmp);
+- nginx_ver_build_len = p - nginx_ver_build;
+- }
+-
+- pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len);
++ pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD);
+
+ } else {
+- pos = ngx_cpymem(pos, nginx, sizeof(nginx));
++ pos = ngx_http_v2_write_header_str("server", "nginx");
+ }
+ }
+
+ if (r->headers_out.date == NULL) {
+- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"date: %V\"",
+- &ngx_cached_http_time);
+-
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX);
+- pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data,
+- ngx_cached_http_time.len, tmp);
++ pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time);
+ }
+
+ if (r->headers_out.content_type.len) {
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX);
+-
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+@@ -544,64 +551,36 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
+ r->headers_out.content_type.data = p - len;
+ }
+
+- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"content-type: %V\"",
+- &r->headers_out.content_type);
+-
+- pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data,
+- r->headers_out.content_type.len, tmp);
++ pos = ngx_http_v2_write_header_tbl("content-type",
++ r->headers_out.content_type);
+ }
+
+ if (r->headers_out.content_length == NULL
+ && r->headers_out.content_length_n >= 0)
+ {
+- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"content-length: %O\"",
+- r->headers_out.content_length_n);
+-
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX);
+-
+- p = pos;
+- pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n);
+- *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1);
++ p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n);
++ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length",
++ sizeof("content-length") - 1, pos + 15,
++ p - (pos + 15), tmp);
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX);
+-
+- ngx_http_time(pos, r->headers_out.last_modified_time);
++ ngx_http_time(pos + 14, r->headers_out.last_modified_time);
+ len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1;
+-
+- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"last-modified: %*s\"",
+- len, pos);
+-
+- /*
+- * Date will always be encoded using huffman in the temporary buffer,
+- * so it's safe here to use src and dst pointing to the same address.
+- */
+- pos = ngx_http_v2_write_value(pos, pos, len, tmp);
++ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified",
++ sizeof("last-modified") - 1, pos + 14,
++ len, tmp);
+ }
+
+ if (r->headers_out.location && r->headers_out.location->value.len) {
+- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"location: %V\"",
+- &r->headers_out.location->value);
+-
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX);
+- pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data,
+- r->headers_out.location->value.len, tmp);
++ pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value);
+ }
+
+ #if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"vary: Accept-Encoding\"");
+-
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX);
+- pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding));
++ pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding");
+ }
+ #endif
+
+@@ -624,23 +603,10 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
+ continue;
+ }
+
+-#if (NGX_DEBUG)
+- if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) {
+- ngx_strlow(tmp, header[i].key.data, header[i].key.len);
+-
+- ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 output header: \"%*s: %V\"",
+- header[i].key.len, tmp, &header[i].value);
+- }
+-#endif
+-
+- *pos++ = 0;
++ pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data,
++ header[i].key.len, header[i].value.data,
++ header[i].value.len, tmp);
+
+- pos = ngx_http_v2_write_name(pos, header[i].key.data,
+- header[i].key.len, tmp);
+-
+- pos = ngx_http_v2_write_value(pos, header[i].value.data,
+- header[i].value.len, tmp);
+ }
+
+ fin = r->header_only
+@@ -998,6 +964,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
len += binary[i].len;
-+ len += sizeof(ph[i].name);
++ len += push_header[i].len + 1;
}
pos = ngx_pnalloc(r->pool, len);
-@@ -964,12 +965,17 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+@@ -1007,12 +974,17 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
start = pos;
@@ -47,7 +614,7 @@ index 6a210d4..bd9a02f 100644
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \":method: GET\"");
-@@ -979,8 +985,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+@@ -1022,8 +994,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \":path: %V\"", path);
@@ -57,7 +624,7 @@ index 6a210d4..bd9a02f 100644
#if (NGX_HTTP_SSL)
if (fc->ssl) {
-@@ -1003,11 +1008,26 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+@@ -1046,11 +1017,15 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
continue;
}
@@ -68,20 +635,448 @@ index 6a210d4..bd9a02f 100644
&ph[i].name, &(*h)->value);
- pos = ngx_cpymem(pos, binary[i].data, binary[i].len);
-+ switch(i) {
-+ case 0:
-+ pos = ngx_http_v2_write_header_pot(":authority", value);
-+ break;
-+ case 1:
-+ pos = ngx_http_v2_write_header_pot("accept-encoding", value);
-+ break;
-+ case 2:
-+ pos = ngx_http_v2_write_header_pot("accept-language", value);
-+ break;
-+ case 3:
-+ pos = ngx_http_v2_write_header_pot("user-agent", value);
-+ break;
-+ }
++ pos = ngx_http_v2_write_header(h2c, pos,
++ push_header[i].name, push_header[i].len, value->data, value->len,
++ tmp);
}
frame = ngx_http_v2_create_push_frame(r, start, pos);
+diff --git a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c
+index 7d49803f..b9ee2048 100644
+--- a/src/http/v2/ngx_http_v2_table.c
++++ b/src/http/v2/ngx_http_v2_table.c
+@@ -361,3 +361,434 @@ ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size)
+
+ return NGX_OK;
+ }
++
++
++#if (NGX_HTTP_V2_HPACK_ENC)
++
++static ngx_int_t
++hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len);
++
++static ngx_int_t
++hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash,
++ uint8_t *key, size_t key_len);
++
++
++void
++ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c)
++{
++ ngx_http_v2_hpack_enc_entry_t *table;
++ uint64_t idx;
++
++ table = h2c->hpack_enc.htable;
++
++ while (h2c->hpack_enc.size > h2c->max_hpack_table_size) {
++ idx = h2c->hpack_enc.base;
++ h2c->hpack_enc.base = table[idx].next;
++ h2c->hpack_enc.size -= table[idx].size;
++ table[idx].hash_val = 0;
++ h2c->hpack_enc.n_elems--;
++ }
++}
++
++
++/* checks if a header is in the hpack table - if so returns the table entry,
++ otherwise encodes and inserts into the table and returns 0,
++ if failed to insert into table, returns -1 */
++static ngx_int_t
++ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c,
++ size_t key_len, size_t val_len, uint8_t *key, uint8_t *val,
++ ngx_int_t *header_idx)
++{
++ uint64_t hash_val, key_hash, idx, lru;
++ int i;
++ size_t size = key_len + val_len + 32;
++ uint8_t *storage = h2c->hpack_enc.storage;
++
++ ngx_http_v2_hpack_enc_entry_t *table;
++ ngx_http_v2_hpack_name_entry_t *name;
++
++ *header_idx = NGX_ERROR;
++ /* step 1: compute the hash value of header */
++ if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) {
++ return NGX_ERROR;
++ }
++
++ key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234);
++ hash_val = ngx_murmur_hash2_64(val, val_len, key_hash);
++
++ if (hash_val == 0) {
++ return NGX_ERROR;
++ }
++
++ /* step 2: check if full header in the table */
++ idx = hash_val;
++ i = -1;
++ while (idx) {
++ /* at most 8 locations are checked, but most will be done in 1 or 2 */
++ table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ];
++ if (table->hash_val == hash_val
++ && table->klen == key_len
++ && table->vlen == val_len
++ && ngx_memcmp(key, storage + table->pos, key_len) == 0
++ && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0)
++ {
++ return (h2c->hpack_enc.top - table->index) + 61;
++ }
++
++ if (table->hash_val == 0 && i == -1) {
++ i = idx % HPACK_ENC_HTABLE_SZ;
++ break;
++ }
++
++ idx >>= 8;
++ }
++
++ /* step 3: check if key is in one of the tables */
++ *header_idx = hpack_get_static_index(h2c, key, key_len);
++
++ if (i == -1) {
++ return NGX_ERROR;
++ }
++
++ if (*header_idx == NGX_ERROR) {
++ *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len);
++ }
++
++ /* step 4: store the new entry */
++ table = h2c->hpack_enc.htable;
++
++ if (h2c->hpack_enc.top == 0xffffffff) {
++ /* just to be on the safe side, avoid overflow */
++ ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t));
++ }
++
++ while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size)
++ || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) {
++ /* make space for the new entry first */
++ idx = h2c->hpack_enc.base;
++ h2c->hpack_enc.base = table[idx].next;
++ h2c->hpack_enc.size -= table[idx].size;
++ table[idx].hash_val = 0;
++ h2c->hpack_enc.n_elems--;
++ }
++
++ table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val,
++ .index = h2c->hpack_enc.top,
++ .pos = h2c->hpack_enc.pos,
++ .klen = key_len,
++ .vlen = val_len,
++ .size = size,
++ .next = 0};
++
++ table[h2c->hpack_enc.last].next = i;
++ if (h2c->hpack_enc.n_elems == 0) {
++ h2c->hpack_enc.base = i;
++ }
++
++ h2c->hpack_enc.last = i;
++ h2c->hpack_enc.top++;
++ h2c->hpack_enc.size += size;
++ h2c->hpack_enc.n_elems++;
++
++ /* update header name lookup */
++ if (*header_idx == NGX_ERROR ) {
++ lru = h2c->hpack_enc.top;
++
++ for (i=0; ihpack_enc.heads[i];
++
++ if ( name->hash_val == 0 || (name->hash_val == key_hash
++ && ngx_memcmp(storage + name->pos, key, key_len) == 0) )
++ {
++ name->hash_val = key_hash;
++ name->pos = h2c->hpack_enc.pos;
++ name->index = h2c->hpack_enc.top - 1;
++ break;
++ }
++
++ if (lru > name->index) {
++ lru = name->index;
++ idx = i;
++ }
++ }
++
++ if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) {
++ name = &h2c->hpack_enc.heads[idx];
++ name->hash_val = hash_val;
++ name->pos = h2c->hpack_enc.pos;
++ name->index = h2c->hpack_enc.top - 1;
++ }
++ }
++
++ ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len);
++ ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len);
++
++ h2c->hpack_enc.pos += size;
++ if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) {
++ h2c->hpack_enc.pos = 0;
++ }
++
++ return NGX_OK;
++}
++
++
++u_char *
++ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
++ u_char *key, size_t key_len,
++ u_char *value, size_t value_len,
++ u_char *tmp)
++{
++ ngx_int_t idx, header_idx;
++
++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
++ "http2 output header: %*s: %*s", key_len, key, value_len,
++ value);
++
++ /* attempt to find the value in the dynamic table */
++ idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value,
++ &header_idx);
++
++ if (idx > 0) {
++ /* positive index indicates success */
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
++ "http2 hpack encode: Indexed Header Field: %ud", idx);
++
++ *pos = 128;
++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx);
++
++ } else {
++
++ if (header_idx == NGX_ERROR) { /* if key is not present */
++
++ if (idx == NGX_ERROR) { /* if header was not added */
++ *pos++ = 0;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
++ "http2 hpack encode: Literal Header Field without"
++ " Indexing — New Name");
++ } else { /* if header was added */
++ *pos++ = 64;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
++ "http2 hpack encode: Literal Header Field with "
++ "Incremental Indexing — New Name");
++ }
++
++ pos = ngx_http_v2_write_name(pos, key, key_len, tmp);
++
++ } else { /* if key is present */
++
++ if (idx == NGX_ERROR) {
++ *pos = 0;
++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
++ "http2 hpack encode: Literal Header Field without"
++ " Indexing — Indexed Name: %ud", header_idx);
++ } else {
++ *pos = 64;
++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
++ "http2 hpack encode: Literal Header Field with "
++ "Incremental Indexing — Indexed Name: %ud", header_idx);
++ }
++ }
++
++ pos = ngx_http_v2_write_value(pos, value, value_len, tmp);
++ }
++
++ return pos;
++}
++
++
++static ngx_int_t
++hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash,
++ uint8_t *key, size_t key_len)
++{
++ ngx_http_v2_hpack_name_entry_t *name;
++ int i;
++
++ for (i=0; ihpack_enc.heads[i];
++
++ if (name->hash_val == key_hash
++ && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0)
++ {
++ if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) {
++ return (h2c->hpack_enc.top - name->index) + 61;
++ }
++ break;
++ }
++ }
++
++ return NGX_ERROR;
++}
++
++
++/* decide if a given header is present in the static dictionary, this could be
++ done in several ways, but it seems the fastest one is "exhaustive" search */
++static ngx_int_t
++hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len)
++{
++ /* the static dictionary of response only headers,
++ although response headers can be put by origin,
++ that would be rare */
++ static const struct {
++ u_char len;
++ const u_char val[28];
++ u_char idx;
++ } server_headers[] = {
++ { 3, "age", 21},//0
++ { 3, "via", 60},
++ { 4, "date", 33},//2
++ { 4, "etag", 34},
++ { 4, "link", 45},
++ { 4, "vary", 59},
++ { 5, "allow", 22},//6
++ { 6, "server", 54},//7
++ { 7, "expires", 36},//8
++ { 7, "refresh", 52},
++ { 8, "location", 46},//10
++ {10, "set-cookie", 55},//11
++ {11, "retry-after", 53},//12
++ {12, "content-type", 31},//13
++ {13, "content-range", 30},//14
++ {13, "accept-ranges", 18},
++ {13, "cache-control", 24},
++ {13, "last-modified", 44},
++ {14, "content-length", 28},//18
++ {16, "content-encoding", 26},//19
++ {16, "content-language", 27},
++ {16, "content-location", 29},
++ {16, "www-authenticate", 61},
++ {17, "transfer-encoding", 57},//23
++ {18, "proxy-authenticate", 48},//24
++ {19, "content-disposition", 25},//25
++ {25, "strict-transport-security", 56},//26
++ {27, "access-control-allow-origin", 20},//27
++ {99, "", 99},
++ }, *header;
++
++ /* for a given length, where to start the search
++ since minimal length is 3, the table has a -3
++ offset */
++ static const int8_t start_at[] = {
++ [3-3] = 0,
++ [4-3] = 2,
++ [5-3] = 6,
++ [6-3] = 7,
++ [7-3] = 8,
++ [8-3] = 10,
++ [9-3] = -1,
++ [10-3] = 11,
++ [11-3] = 12,
++ [12-3] = 13,
++ [13-3] = 14,
++ [14-3] = 18,
++ [15-3] = -1,
++ [16-3] = 19,
++ [17-3] = 23,
++ [18-3] = 24,
++ [19-3] = 25,
++ [20-3] = -1,
++ [21-3] = -1,
++ [22-3] = -1,
++ [23-3] = -1,
++ [24-3] = -1,
++ [25-3] = 26,
++ [26-3] = -1,
++ [27-3] = 27,
++ };
++
++ uint64_t pref;
++ size_t save_len = len, i;
++ int8_t start;
++
++ /* early exit for out of bounds lengths */
++ if (len < 3 || len > 27) {
++ return NGX_ERROR;
++ }
++
++ start = start_at[len - 3];
++ if (start == -1) {
++ /* exit for non existent lengths */
++ return NGX_ERROR;
++ }
++
++ header = &server_headers[start_at[len - 3]];
++
++ /* load first 8 bytes of key, for fast comparison */
++ if (len < 8) {
++ pref = 0;
++ if (len >= 4) {
++ pref = *(uint32_t *)(val + len - 4) | 0x20202020;
++ len -= 4;
++ }
++ while (len > 0) { /* 3 iterations at most */
++ pref = (pref << 8) ^ (val[len - 1] | 0x20);
++ len--;
++ }
++ } else {
++ pref = *(uint64_t *)val | 0x2020202020202020;
++ len -= 8;
++ }
++
++ /* iterate over headers with the right length */
++ while (header->len == save_len) {
++ /* quickly compare the first 8 bytes, most tests will end here */
++ if (pref != *(uint64_t *) header->val) {
++ header++;
++ continue;
++ }
++
++ if (len == 0) {
++ /* len == 0, indicates prefix held the entire key */
++ return header->idx;
++ }
++ /* for longer keys compare the rest */
++ i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */
++
++ while (i + 8 <= save_len) { /* 3 iterations at most */
++ if ( *(uint64_t *)&header->val[i]
++ != (*(uint64_t *) &val[i]| 0x2020202020202020) )
++ {
++ header++;
++ i = 0;
++ break;
++ }
++ i += 8;
++ }
++
++ if (i == 0) {
++ continue;
++ }
++
++ /* found the corresponding entry in the static dictionary */
++ return header->idx;
++ }
++
++ return NGX_ERROR;
++}
++
++#else
++
++u_char *
++ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
++ u_char *key, size_t key_len,
++ u_char *value, size_t value_len,
++ u_char *tmp)
++{
++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
++ "http2 output header: %*s: %*s", key_len, key, value_len,
++ value);
++
++ *pos++ = 64;
++ pos = ngx_http_v2_write_name(pos, key, key_len, tmp);
++ pos = ngx_http_v2_write_value(pos, value, value_len, tmp);
++
++ return pos;
++}
++
++#endif
diff --git a/nginx_hpack_push_fix.patch b/nginx_hpack_push_fix.patch
new file mode 100644
index 0000000..0798695
--- /dev/null
+++ b/nginx_hpack_push_fix.patch
@@ -0,0 +1,81 @@
+diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c
+index 6a210d4..8a59424 100644
+--- a/src/http/v2/ngx_http_v2_filter_module.c
++++ b/src/http/v2/ngx_http_v2_filter_module.c
+@@ -59,6 +59,17 @@
+ #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1
+
+
++static const struct {
++ u_char *name;
++ u_char const len;
++} push_header[] = {
++ { (u_char*)":authority" , 10 },
++ { (u_char*)"accept-encoding" , 15 },
++ { (u_char*)"accept-language" , 15 },
++ { (u_char*)"user-agent" , 10 }
++};
++
++
+ typedef struct {
+ ngx_str_t name;
+ u_char index;
+@@ -955,6 +966,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+
+ for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
+ len += binary[i].len;
++ len += push_header[i].len + 1;
+ }
+
+ pos = ngx_pnalloc(r->pool, len);
+@@ -964,12 +976,17 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+
+ start = pos;
+
+- if (h2c->table_update) {
+- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+- "http2 table size update: 0");
+- *pos++ = (1 << 5) | 0;
+- h2c->table_update = 0;
+- }
++ h2c = r->stream->connection;
++
++ if (h2c->indicate_resize) {
++ *pos = 32;
++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5),
++ h2c->max_hpack_table_size);
++ h2c->indicate_resize = 0;
++#if (NGX_HTTP_V2_HPACK_ENC)
++ ngx_http_v2_table_resize(h2c);
++#endif
++ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+ "http2 push header: \":method: GET\"");
+@@ -979,8 +996,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+ "http2 push header: \":path: %V\"", path);
+
+- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
+- pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp);
++ pos = ngx_http_v2_write_header_pot(":path", path);
+
+ #if (NGX_HTTP_SSL)
+ if (fc->ssl) {
+@@ -1003,11 +1019,15 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+ continue;
+ }
+
++ value = &(*h)->value;
++
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+ "http2 push header: \"%V: %V\"",
+ &ph[i].name, &(*h)->value);
+
+- pos = ngx_cpymem(pos, binary[i].data, binary[i].len);
++ pos = ngx_http_v2_write_header(h2c, pos,
++ push_header[i].name, push_header[i].len, value->data, value->len,
++ tmp);
+ }
+
+ frame = ngx_http_v2_create_push_frame(r, start, pos);