]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
merged backport proposed trunk changes re HTTP/2
authorStefan Eissing <icing@apache.org>
Wed, 4 Nov 2015 15:44:24 +0000 (15:44 +0000)
committerStefan Eissing <icing@apache.org>
Wed, 4 Nov 2015 15:44:24 +0000 (15:44 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4-http2-alpha@1712570 13f79535-47bb-0310-9956-ffa450edef68

41 files changed:
1  2 
CHANGES
modules/http2/config.m4
modules/http2/h2_config.c
modules/http2/h2_config.h
modules/http2/h2_conn.c
modules/http2/h2_conn.h
modules/http2/h2_conn_io.c
modules/http2/h2_conn_io.h
modules/http2/h2_ctx.c
modules/http2/h2_ctx.h
modules/http2/h2_from_h1.c
modules/http2/h2_h2.c
modules/http2/h2_h2.h
modules/http2/h2_io.c
modules/http2/h2_io.h
modules/http2/h2_mplx.c
modules/http2/h2_mplx.h
modules/http2/h2_request.c
modules/http2/h2_response.c
modules/http2/h2_response.h
modules/http2/h2_session.c
modules/http2/h2_session.h
modules/http2/h2_stream.c
modules/http2/h2_stream.h
modules/http2/h2_stream_set.c
modules/http2/h2_stream_set.h
modules/http2/h2_switch.c
modules/http2/h2_task.c
modules/http2/h2_task.h
modules/http2/h2_task_input.c
modules/http2/h2_task_input.h
modules/http2/h2_task_output.c
modules/http2/h2_task_output.h
modules/http2/h2_task_queue.c
modules/http2/h2_task_queue.h
modules/http2/h2_to_h1.c
modules/http2/h2_to_h1.h
modules/http2/h2_util.c
modules/http2/h2_util.h
modules/http2/h2_version.h
modules/http2/mod_http2.dsp

diff --cc CHANGES
index c12e96e957cc4d873ce8da20556978b0bd9e9571,2703361a916cef548f4283aee3dc3ae61eb15bc4..b396b2f15a63dc458265ce85dadf7cc5795983a6
+++ b/CHANGES
@@@ -1,12 -1,20 +1,25 @@@
                                                           -*- coding: utf-8 -*-
  
  Changes with Apache 2.4.18
 +  *) mod_http2: reworked deallocation on connection shutdown and worker
 +     abort. Separate parent pool for all workers. worker threads are joined
 +     on planned worker shutdown.
 +     [Yann Ylavic, Stefan Eissing]
 +     
  
+   *) core: Fix scoreboard crash (SIGBUS) on hardware requiring strict 64bit
+      alignment (SPARC64, PPC64).  [Yann Ylavic]
+   *) mod_cache: Accept HT (Horizontal Tab) when parsing cache related header
+      fields as described in RFC7230. [Christophe Jaillet]
+   *) core/util_script: making REDIRECT_URL a full URL is now opt-in
+      via new 'QualifyRedirectURL' directive.
+   *) mod_ssl: Extend expression parser registration to support ssl variables
+      in any expression using mod_rewrite syntax "%{SSL:VARNAME}" or function
+      syntax "ssl(VARNAME)". [Rainer Jung]
  Changes with Apache 2.4.17
  
    *) mod_http2: added donated HTTP/2 implementation via core module. Similar
index 9c5eb86740e9a30820a34515be0e7356e79eab50,9c5eb86740e9a30820a34515be0e7356e79eab50..35b25e11a3addedf0a3fc9e6520fdb413573888c
@@@ -20,6 -20,6 +20,8 @@@ dnl #  list of module object file
  http2_objs="dnl
  mod_http2.lo dnl
  h2_alt_svc.lo dnl
++h2_bucket_eoc.lo dnl
++h2_bucket_eos.lo dnl
  h2_config.lo dnl
  h2_conn.lo dnl
  h2_conn_io.lo dnl
index 6db702eca80345a997420b4ae62c6b5b78fbd721,6db702eca80345a997420b4ae62c6b5b78fbd721..25fdd29908f4bbba769c1da012fec6bb900468f5
@@@ -49,6 -49,6 +49,10 @@@ static h2_config defconf = 
      0,                /* serialize headers */
      -1,               /* h2 direct mode */
      -1,               /* # session extra files */
++    1,                /* modern TLS only */
++    -1,               /* HTTP/1 Upgrade support */
++    1024*1024,        /* TLS warmup size */
++    1,                /* TLS cooldown secs */
  };
  
  static int files_per_session = 0;
@@@ -100,6 -100,6 +104,11 @@@ static void *h2_config_create(apr_pool_
      conf->serialize_headers    = DEF_VAL;
      conf->h2_direct            = DEF_VAL;
      conf->session_extra_files  = DEF_VAL;
++    conf->modern_tls_only      = DEF_VAL;
++    conf->h2_upgrade           = DEF_VAL;
++    conf->tls_warmup_size      = DEF_VAL;
++    conf->tls_cooldown_secs    = DEF_VAL;
++    
      return conf;
  }
  
@@@ -127,22 -127,22 +136,31 @@@ void *h2_config_merge(apr_pool_t *pool
      strcat(name, "]");
      n->name = name;
  
--    n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams);
--    n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size);
--    n->min_workers    = H2_CONFIG_GET(add, base, min_workers);
--    n->max_workers    = H2_CONFIG_GET(add, base, max_workers);
++    n->h2_max_streams       = H2_CONFIG_GET(add, base, h2_max_streams);
++    n->h2_window_size       = H2_CONFIG_GET(add, base, h2_window_size);
++    n->min_workers          = H2_CONFIG_GET(add, base, min_workers);
++    n->max_workers          = H2_CONFIG_GET(add, base, max_workers);
      n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs);
--    n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size);
--    n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
--    n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
--    n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers);
--    n->h2_direct      = H2_CONFIG_GET(add, base, h2_direct);
--    n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files);
++    n->stream_max_mem_size  = H2_CONFIG_GET(add, base, stream_max_mem_size);
++    n->alt_svcs             = add->alt_svcs? add->alt_svcs : base->alt_svcs;
++    n->alt_svc_max_age      = H2_CONFIG_GET(add, base, alt_svc_max_age);
++    n->serialize_headers    = H2_CONFIG_GET(add, base, serialize_headers);
++    n->h2_direct            = H2_CONFIG_GET(add, base, h2_direct);
++    n->session_extra_files  = H2_CONFIG_GET(add, base, session_extra_files);
++    n->modern_tls_only      = H2_CONFIG_GET(add, base, modern_tls_only);
++    n->h2_upgrade           = H2_CONFIG_GET(add, base, h2_upgrade);
++    n->tls_warmup_size      = H2_CONFIG_GET(add, base, tls_warmup_size);
++    n->tls_cooldown_secs    = H2_CONFIG_GET(add, base, tls_cooldown_secs);
      
      return n;
  }
  
  int h2_config_geti(h2_config *conf, h2_config_var_t var)
++{
++    return (int)h2_config_geti64(conf, var);
++}
++
++apr_int64_t h2_config_geti64(h2_config *conf, h2_config_var_t var)
  {
      int n;
      switch(var) {
              return H2_CONFIG_GET(conf, &defconf, alt_svc_max_age);
          case H2_CONF_SER_HEADERS:
              return H2_CONFIG_GET(conf, &defconf, serialize_headers);
++        case H2_CONF_MODERN_TLS_ONLY:
++            return H2_CONFIG_GET(conf, &defconf, modern_tls_only);
++        case H2_CONF_UPGRADE:
++            return H2_CONFIG_GET(conf, &defconf, h2_upgrade);
          case H2_CONF_DIRECT:
              return H2_CONFIG_GET(conf, &defconf, h2_direct);
          case H2_CONF_SESSION_FILES:
                  n = files_per_session;
              }
              return n;
++        case H2_CONF_TLS_WARMUP_SIZE:
++            return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
++        case H2_CONF_TLS_COOLDOWN_SECS:
++            return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs);
          default:
              return DEF_VAL;
      }
@@@ -290,8 -290,8 +316,8 @@@ static const char *h2_conf_set_session_
  {
      h2_config *cfg = h2_config_sget(parms->server);
      apr_int64_t max = (int)apr_atoi64(value);
--    if (max <= 0) {
--        return "value must be a positive number";
++    if (max < 0) {
++        return "value must be a non-negative number";
      }
      cfg->session_extra_files = (int)max;
      (void)arg;
@@@ -332,8 -332,8 +358,60 @@@ static const char *h2_conf_set_direct(c
      return "value must be On or Off";
  }
  
--#define AP_END_CMD     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
++static const char *h2_conf_set_modern_tls_only(cmd_parms *parms,
++                                               void *arg, const char *value)
++{
++    h2_config *cfg = h2_config_sget(parms->server);
++    if (!strcasecmp(value, "On")) {
++        cfg->modern_tls_only = 1;
++        return NULL;
++    }
++    else if (!strcasecmp(value, "Off")) {
++        cfg->modern_tls_only = 0;
++        return NULL;
++    }
++    
++    (void)arg;
++    return "value must be On or Off";
++}
  
++static const char *h2_conf_set_upgrade(cmd_parms *parms,
++                                       void *arg, const char *value)
++{
++    h2_config *cfg = h2_config_sget(parms->server);
++    if (!strcasecmp(value, "On")) {
++        cfg->h2_upgrade = 1;
++        return NULL;
++    }
++    else if (!strcasecmp(value, "Off")) {
++        cfg->h2_upgrade = 0;
++        return NULL;
++    }
++    
++    (void)arg;
++    return "value must be On or Off";
++}
++
++static const char *h2_conf_set_tls_warmup_size(cmd_parms *parms,
++                                               void *arg, const char *value)
++{
++    h2_config *cfg = h2_config_sget(parms->server);
++    cfg->tls_warmup_size = apr_atoi64(value);
++    (void)arg;
++    return NULL;
++}
++
++static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *parms,
++                                                 void *arg, const char *value)
++{
++    h2_config *cfg = h2_config_sget(parms->server);
++    cfg->tls_cooldown_secs = (int)apr_atoi64(value);
++    (void)arg;
++    return NULL;
++}
++
++
++#define AP_END_CMD     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
  
  const command_rec h2_cmds[] = {
      AP_INIT_TAKE1("H2MaxSessionStreams", h2_conf_set_max_streams, NULL,
                    RSRC_CONF, "set the maximum age (in seconds) that client can rely on alt-svc information"),
      AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL,
                    RSRC_CONF, "on to enable header serialization for compatibility"),
++    AP_INIT_TAKE1("H2ModernTLSOnly", h2_conf_set_modern_tls_only, NULL,
++                  RSRC_CONF, "off to not impose RFC 7540 restrictions on TLS"),
++    AP_INIT_TAKE1("H2Upgrade", h2_conf_set_upgrade, NULL,
++                  RSRC_CONF, "on to allow HTTP/1 Upgrades to h2/h2c"),
      AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
                    RSRC_CONF, "on to enable direct HTTP/2 mode"),
      AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
                    RSRC_CONF, "number of extra file a session might keep open"),
++    AP_INIT_TAKE1("H2TLSWarmUpSize", h2_conf_set_tls_warmup_size, NULL,
++                  RSRC_CONF, "number of bytes on TLS connection before doing max writes"),
++    AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
++                  RSRC_CONF, "seconds of idle time on TLS before shrinking writes"),
      AP_END_CMD
  };
  
index 931af59d302145819c7fe463ae760dad282b56ae,931af59d302145819c7fe463ae760dad282b56ae..9c59817a992b153fab52ddd3e8eac3af52473111
@@@ -34,6 -34,6 +34,10 @@@ typedef enum 
      H2_CONF_SER_HEADERS,
      H2_CONF_DIRECT,
      H2_CONF_SESSION_FILES,
++    H2_CONF_MODERN_TLS_ONLY,
++    H2_CONF_UPGRADE,
++    H2_CONF_TLS_WARMUP_SIZE,
++    H2_CONF_TLS_COOLDOWN_SECS,
  } h2_config_var_t;
  
  /* Apache httpd module configuration for h2. */
@@@ -51,6 -51,6 +55,10 @@@ typedef struct h2_config 
                                       processing, better compatibility */
      int h2_direct;                /* if mod_h2 is active directly */
      int session_extra_files;      /* # of extra files a session may keep open */  
++    int modern_tls_only;          /* Accept only modern TLS in HTTP/2 connections */  
++    int h2_upgrade;               /* Allow HTTP/1 upgrade to h2/h2c */
++    apr_int64_t tls_warmup_size;  /* Amount of TLS data to send before going full write size */
++    int tls_cooldown_secs;        /* Seconds of idle time before going back to small TLS records */
  } h2_config;
  
  
@@@ -67,6 -67,6 +75,7 @@@ h2_config *h2_config_sget(server_rec *s
  h2_config *h2_config_rget(request_rec *r);
  
  int h2_config_geti(h2_config *conf, h2_config_var_t var);
++apr_int64_t h2_config_geti64(h2_config *conf, h2_config_var_t var);
  
  void h2_config_init(apr_pool_t *pool);
  
index 9794699f8844870fe2bff1349d103460ab3f3c21,8bffbc42693cad31e50ff0b3b86e15046c9922da..877447e2ffeb6d5772f1f73b7aa00879923f2df3
@@@ -32,6 -32,6 +32,7 @@@
  #include "h2_session.h"
  #include "h2_stream.h"
  #include "h2_stream_set.h"
++#include "h2_h2.h"
  #include "h2_task.h"
  #include "h2_worker.h"
  #include "h2_workers.h"
@@@ -43,7 -43,7 +44,6 @@@ static apr_status_t h2_conn_loop(h2_ses
  
  static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
  static module *mpm_module;
--static module *ssl_module;
  static int checked;
  
  static void check_modules(void) 
@@@ -64,9 -64,9 +64,6 @@@
                  mpm_type = H2_MPM_PREFORK;
                  mpm_module = m;
              }
--            else if (!strcmp("mod_ssl.c", m->name)) {
--                ssl_module = m;
--            }
          }
          checked = 1;
      }
@@@ -103,9 -103,9 +100,6 @@@ apr_status_t h2_conn_child_init(apr_poo
              mpm_type = H2_MPM_PREFORK;
              mpm_module = m;
          }
--        else if (!strcmp("mod_ssl.c", m->name)) {
--            ssl_module = m;
--        }
      }
      
      if (minw <= 0) {
@@@ -177,7 -177,7 +171,12 @@@ apr_status_t h2_conn_main(conn_rec *c
          return APR_EGENERAL;
      }
      
 -    status = h2_session_process(session);
++    if (!h2_is_acceptable_connection(c, 1)) {
++        nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
++                              NGHTTP2_INADEQUATE_SECURITY, NULL, 0);
++    } 
++
 +    status = h2_conn_loop(session);
  
      /* Make sure this connection gets closed properly. */
      c->keepalive = AP_CONN_CLOSE;
@@@ -241,7 -241,7 +240,7 @@@ static apr_status_t h2_conn_loop(h2_ses
                    session->c->local_addr->port);
      if (status != APR_SUCCESS) {
          h2_session_abort(session, status, rv);
--        h2_session_destroy(session);
++        h2_session_cleanup(session);
          return status;
      }
      
      ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
                    "h2_session(%ld): done", session->id);
      
++    h2_session_close(session);
      ap_update_child_status_from_conn(session->c->sbh, SERVER_CLOSING, 
                                       session->c);
--
--    h2_session_close(session);
--    h2_session_destroy(session);
--    
      return DONE;
  }
  
@@@ -412,11 -396,11 +408,11 @@@ conn_rec *h2_conn_create(conn_rec *mast
      return c;
  }
  
--apr_status_t h2_conn_setup(h2_task_env *env, struct h2_worker *worker)
++apr_status_t h2_conn_setup(h2_task *task, struct h2_worker *worker)
  {
--    conn_rec *master = env->mplx->c;
++    conn_rec *master = task->mplx->c;
      
--    ap_log_perror(APLOG_MARK, APLOG_TRACE3, 0, env->pool,
++    ap_log_perror(APLOG_MARK, APLOG_TRACE3, 0, task->pool,
                    "h2_conn(%ld): created from master", master->id);
      
      /* Ok, we are just about to start processing the connection and
       * sub-resources from it, so that we get a nice reuse of
       * pools.
       */
--    env->c.pool = env->pool;
--    env->c.bucket_alloc = h2_worker_get_bucket_alloc(worker);
--    env->c.current_thread = h2_worker_get_thread(worker);
++    task->c->pool = task->pool;
++    task->c->bucket_alloc = h2_worker_get_bucket_alloc(worker);
++    task->c->current_thread = h2_worker_get_thread(worker);
      
--    env->c.conn_config = ap_create_conn_config(env->pool);
--    env->c.notes = apr_table_make(env->pool, 5);
++    task->c->conn_config = ap_create_conn_config(task->pool);
++    task->c->notes = apr_table_make(task->pool, 5);
      
--    ap_set_module_config(env->c.conn_config, &core_module, 
--                         h2_worker_get_socket(worker));
++    /* In order to do this in 2.4.x, we need to add a member to conn_rec */
++    task->c->master = master;
      
--    /* If we serve http:// requests over a TLS connection, we do
--     * not want any mod_ssl vars to be visible.
--     */
--    if (ssl_module && (!env->scheme || strcmp("http", env->scheme))) {
--        /* See #19, there is a range of SSL variables to be gotten from
--         * the main connection that should be available in request handlers
--         */
--        void *sslcfg = ap_get_module_config(master->conn_config, ssl_module);
--        if (sslcfg) {
--            ap_set_module_config(env->c.conn_config, ssl_module, sslcfg);
--        }
--    }
++    ap_set_module_config(task->c->conn_config, &core_module, 
++                         h2_worker_get_socket(worker));
      
      /* This works for mpm_worker so far. Other mpm modules have 
       * different needs, unfortunately. The most interesting one 
              /* all fine */
              break;
          case H2_MPM_EVENT: 
--            fix_event_conn(&env->c, master);
++            fix_event_conn(task->c, master);
              break;
          default:
              /* fingers crossed */
       * 400 Bad Request
       * when names do not match. We prefer a predictable 421 status.
       */
--    env->c.keepalives = 1;
++    task->c->keepalives = 1;
      
      return APR_SUCCESS;
  }
index 49a70db85075358a5950935a61c2e6d205aee500,49a70db85075358a5950935a61c2e6d205aee500..752f28a74cba78be1e812aaa1bbf394b9eb6f2a7
@@@ -17,7 -17,7 +17,6 @@@
  #define __mod_h2__h2_conn__
  
  struct h2_task;
--struct h2_task_env;
  struct h2_worker;
  
  /* Process the connection that is now starting the HTTP/2
@@@ -52,7 -52,7 +51,7 @@@ h2_mpm_type_t h2_conn_mpm_type(void)
  
  conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *stream_pool);
  
--apr_status_t h2_conn_setup(struct h2_task_env *env, struct h2_worker *worker);
++apr_status_t h2_conn_setup(struct h2_task *task, struct h2_worker *worker);
  apr_status_t h2_conn_post(conn_rec *c, struct h2_worker *worker);
  
  apr_status_t h2_conn_process(conn_rec *c, apr_socket_t *socket);
index 08d00a4f3a6924e903d853e4d54f111a918ac5e1,08d00a4f3a6924e903d853e4d54f111a918ac5e1..0299343063cf6b9f2caf69888bbad4e6dc2c3f2a
  #include "h2_h2.h"
  #include "h2_util.h"
  
--#define WRITE_BUFFER_SIZE     (64*1024)
++#define TLS_DATA_MAX          (16*1024) 
++
++/* Calculated like this: assuming MTU 1500 bytes
++ * 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options) 
++ *      - TLS overhead (60-100) 
++ * ~= 1300 bytes */
  #define WRITE_SIZE_INITIAL    1300
--#define WRITE_SIZE_MAX        (16*1024)
--#define WRITE_SIZE_IDLE_USEC  (1*APR_USEC_PER_SEC)
--#define WRITE_SIZE_THRESHOLD  (1*1024*1024)
++/* Calculated like this: max TLS record size 16*1024
++ *   - 40 (IP) - 20 (TCP) - 40 (TCP options) 
++ *    - TLS overhead (60-100) 
++ * which seems to create less TCP packets overall
++ */
++#define WRITE_SIZE_MAX        (TLS_DATA_MAX - 100) 
++
++#define WRITE_BUFFER_SIZE     (8*WRITE_SIZE_MAX)
  
  apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c)
  {
--    io->connection = c;
--    io->input = apr_brigade_create(c->pool, c->bucket_alloc);
--    io->output = apr_brigade_create(c->pool, c->bucket_alloc);
--    io->buflen = 0;
--    /* That is where we start with, 
--     * see https://issues.apache.org/jira/browse/TS-2503 */
--    io->write_size = WRITE_SIZE_INITIAL; 
--    io->last_write = 0;
--    io->buffer_output = h2_h2_is_tls(c);
--
--    /* Currently we buffer only for TLS output. The reason this gives
--     * improved performance is that buckets send to the mod_ssl network
--     * filter will be encrypted in chunks. There is a special filter
--     * that tries to aggregate data, but that does not work well when
--     * bucket sizes alternate between tiny frame headers and large data
--     * chunks.
--     */
++    h2_config *cfg = h2_config_get(c);
++    
++    io->connection         = c;
++    io->input              = apr_brigade_create(c->pool, c->bucket_alloc);
++    io->output             = apr_brigade_create(c->pool, c->bucket_alloc);
++    io->buflen             = 0;
++    io->is_tls             = h2_h2_is_tls(c);
++    io->buffer_output      = io->is_tls;
++    
      if (io->buffer_output) {
          io->bufsize = WRITE_BUFFER_SIZE;
          io->buffer = apr_pcalloc(c->pool, io->bufsize);
          io->bufsize = 0;
      }
      
++    if (io->is_tls) {
++        /* That is where we start with, 
++         * see https://issues.apache.org/jira/browse/TS-2503 */
++        io->warmup_size    = h2_config_geti64(cfg, H2_CONF_TLS_WARMUP_SIZE);
++        io->cooldown_usecs = (h2_config_geti(cfg, H2_CONF_TLS_COOLDOWN_SECS) 
++                              * APR_USEC_PER_SEC);
++        io->write_size     = WRITE_SIZE_INITIAL; 
++    }
++    else {
++        io->warmup_size    = 0;
++        io->cooldown_usecs = 0;
++        io->write_size     = io->bufsize;
++    }
++
++    if (APLOGctrace1(c)) {
++        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
++                      "h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, cd_secs=%f",
++                      io->connection->id, io->buffer_output, (long)io->warmup_size,
++                      ((float)io->cooldown_usecs/APR_USEC_PER_SEC));
++    }
++
      return APR_SUCCESS;
  }
  
--void h2_conn_io_destroy(h2_conn_io *io)
++int h2_conn_io_is_buffered(h2_conn_io *io)
  {
--    io->input = NULL;
--    io->output = NULL;
++    return io->bufsize > 0;
  }
  
  static apr_status_t h2_conn_io_bucket_read(h2_conn_io *io,
@@@ -159,7 -159,7 +181,7 @@@ apr_status_t h2_conn_io_read(h2_conn_i
  
      status = ap_get_brigade(io->connection->input_filters,
                              io->input, AP_MODE_READBYTES,
--                            block, 16 * 4096);
++                            block, 64 * 4096);
      switch (status) {
          case APR_SUCCESS:
              return h2_conn_io_bucket_read(io, block, on_read_cb, puser, &done);
      return status;
  }
  
--static apr_status_t flush_out(apr_bucket_brigade *bb, void *ctx) 
++static apr_status_t pass_out(apr_bucket_brigade *bb, void *ctx) 
  {
      h2_conn_io *io = (h2_conn_io*)ctx;
      apr_status_t status;
      apr_off_t bblen;
      
++    if (APR_BRIGADE_EMPTY(bb)) {
++        return APR_SUCCESS;
++    }
++    
      ap_update_child_status(io->connection->sbh, SERVER_BUSY_WRITE, NULL);
--    status = apr_brigade_length(bb, 1, &bblen);
++    status = apr_brigade_length(bb, 0, &bblen);
      if (status == APR_SUCCESS) {
++        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
++                      "h2_conn_io(%ld): pass_out brigade %ld bytes",
++                      io->connection->id, (long)bblen);
          status = ap_pass_brigade(io->connection->output_filters, bb);
          if (status == APR_SUCCESS) {
              io->bytes_written += (apr_size_t)bblen;
      return status;
  }
  
++/* Bring the current buffer content into the output brigade, appropriately
++ * chunked.
++ */
  static apr_status_t bucketeer_buffer(h2_conn_io *io) {
      const char *data = io->buffer;
      apr_size_t remaining = io->buflen;
      apr_bucket *b;
      int bcount, i;
  
--    if (io->write_size > WRITE_SIZE_INITIAL
--        && (apr_time_now() - io->last_write) >= WRITE_SIZE_IDLE_USEC) {
++    if (io->write_size > WRITE_SIZE_INITIAL 
++        && (io->cooldown_usecs > 0)
++        && (apr_time_now() - io->last_write) >= io->cooldown_usecs) {
          /* long time not written, reset write size */
          io->write_size = WRITE_SIZE_INITIAL;
          io->bytes_written = 0;
                        (long)io->connection->id, (long)io->write_size);
      }
      else if (io->write_size < WRITE_SIZE_MAX 
--             && io->bytes_written >= WRITE_SIZE_THRESHOLD) {
++             && io->bytes_written >= io->warmup_size) {
          /* connection is hot, use max size */
          io->write_size = WRITE_SIZE_MAX;
          ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
@@@ -238,16 -238,16 +271,22 @@@ apr_status_t h2_conn_io_write(h2_conn_i
                                const char *buf, size_t length)
  {
      apr_status_t status = APR_SUCCESS;
--    io->unflushed = 1;
      
--    if (io->buffer_output) {
++    io->unflushed = 1;
++    if (io->bufsize > 0) {
          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
                        "h2_conn_io: buffering %ld bytes", (long)length);
++                      
++        if (!APR_BRIGADE_EMPTY(io->output)) {
++            status = h2_conn_io_flush(io);
++            io->unflushed = 1;
++        }
++        
          while (length > 0 && (status == APR_SUCCESS)) {
              apr_size_t avail = io->bufsize - io->buflen;
              if (avail <= 0) {
                  bucketeer_buffer(io);
--                status = flush_out(io->output, io);
++                status = pass_out(io->output, io);
                  io->buflen = 0;
              }
              else if (length > avail) {
          
      }
      else {
--        status = apr_brigade_write(io->output, flush_out, io, buf, length);
--        if (status != APR_SUCCESS) {
--            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection,
--                          "h2_conn_io: write error");
--        }
++        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, io->connection,
++                      "h2_conn_io: writing %ld bytes to brigade", (long)length);
++        status = apr_brigade_write(io->output, pass_out, io, buf, length);
      }
      
      return status;
  }
  
++apr_status_t h2_conn_io_writeb(h2_conn_io *io, apr_bucket *b)
++{
++    APR_BRIGADE_INSERT_TAIL(io->output, b);
++    io->unflushed = 1;
++    return APR_SUCCESS;
++}
++
++apr_status_t h2_conn_io_consider_flush(h2_conn_io *io)
++{
++    apr_status_t status = APR_SUCCESS;
++    int flush_now = 0;
++    
++    /* The HTTP/1.1 network output buffer/flush behaviour does not
++     * give optimal performance in the HTTP/2 case, as the pattern of
++     * buckets (data/eor/eos) is different.
++     * As long as we do not have found out the "best" way to deal with
++     * this, force a flush at least every WRITE_BUFFER_SIZE amount
++     * of data which seems to work nicely.
++     */
++    if (io->unflushed) {
++        apr_off_t len = 0;
++        if (!APR_BRIGADE_EMPTY(io->output)) {
++            apr_brigade_length(io->output, 0, &len);
++        }
++        len += io->buflen;
++        flush_now = (len >= WRITE_BUFFER_SIZE);
++    }
++    
++    if (flush_now) {
++        return h2_conn_io_flush(io);
++    }
++    return status;
++}
  
  apr_status_t h2_conn_io_flush(h2_conn_io *io)
  {
      if (io->unflushed) {
          apr_status_t status; 
          if (io->buflen > 0) {
++            /* something in the buffer, put it in the output brigade */
              ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
                            "h2_conn_io: flush, flushing %ld bytes", (long)io->buflen);
              bucketeer_buffer(io);
              io->buflen = 0;
          }
--        /* Append flush.
--         */
++        
          APR_BRIGADE_INSERT_TAIL(io->output,
                                  apr_bucket_flush_create(io->output->bucket_alloc));
++        /* Send it out */
++        status = pass_out(io->output, io);
          
--        /* Send it out through installed filters (TLS) to the client */
--        status = flush_out(io->output, io);
--        
--        if (status == APR_SUCCESS) {
--            /* These are all fine and no reason for concern. Everything else
--             * is interesting. */
--            io->unflushed = 0;
--        }
--        else {
++        if (status != APR_SUCCESS) {
              ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection,
--                          "h2_conn_io: flush error");
++                          "h2_conn_io: flush");
++            return status;
          }
--        
--        return status;
++
++        io->unflushed = 0;
      }
      return APR_SUCCESS;
  }
index 084445ef2b00d317b528faca30fd8356e4cd3c7f,084445ef2b00d317b528faca30fd8356e4cd3c7f..c5a861605c67adcb2301adb62a6bdac27024df06
@@@ -26,11 -26,11 +26,16 @@@ typedef struct 
      conn_rec *connection;
      apr_bucket_brigade *input;
      apr_bucket_brigade *output;
--    int buffer_output;
++
++    int is_tls;
++    apr_time_t cooldown_usecs;
++    apr_int64_t warmup_size;
++    
      int write_size;
      apr_time_t last_write;
--    apr_size_t bytes_written;
++    apr_int64_t bytes_written;
      
++    int buffer_output;
      char *buffer;
      apr_size_t buflen;
      apr_size_t bufsize;
@@@ -38,7 -38,7 +43,8 @@@
  } h2_conn_io;
  
  apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c);
--void h2_conn_io_destroy(h2_conn_io *io);
++
++int h2_conn_io_is_buffered(h2_conn_io *io);
  
  typedef apr_status_t (*h2_conn_io_on_read_cb)(const char *data, apr_size_t len,
                                           apr_size_t *readlen, int *done,
@@@ -52,6 -52,6 +58,10 @@@ apr_status_t h2_conn_io_read(h2_conn_i
  apr_status_t h2_conn_io_write(h2_conn_io *io,
                           const char *buf,
                           size_t length);
++                         
++apr_status_t h2_conn_io_writeb(h2_conn_io *io, apr_bucket *b);
++
++apr_status_t h2_conn_io_consider_flush(h2_conn_io *io);
  
  apr_status_t h2_conn_io_flush(h2_conn_io *io);
  
index 422835c2df883baadee3954eb99a2d95f0823244,422835c2df883baadee3954eb99a2d95f0823244..08bdd8612dec4eb71de96c4b9d7cd3e4ef62a841
@@@ -32,11 -32,11 +32,11 @@@ static h2_ctx *h2_ctx_create(const conn
      return ctx;
  }
  
--h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task_env *env)
++h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task *task)
  {
      h2_ctx *ctx = h2_ctx_create(c);
      if (ctx) {
--        ctx->task_env = env;
++        ctx->task = task;
      }
      return ctx;
  }
@@@ -76,7 -76,7 +76,7 @@@ h2_ctx *h2_ctx_server_set(h2_ctx *ctx, 
  
  int h2_ctx_is_task(h2_ctx *ctx)
  {
--    return ctx && !!ctx->task_env;
++    return ctx && !!ctx->task;
  }
  
  int h2_ctx_is_active(h2_ctx *ctx)
@@@ -84,7 -84,7 +84,7 @@@
      return ctx && ctx->is_h2;
  }
  
--struct h2_task_env *h2_ctx_get_task(h2_ctx *ctx)
++struct h2_task *h2_ctx_get_task(h2_ctx *ctx)
  {
--    return ctx->task_env;
++    return ctx->task;
  }
index 86c59206eda4f6c14374e70473f221c437330df3,86c59206eda4f6c14374e70473f221c437330df3..e4bc7506ae69ad025431078c7be026b47e2d7bfd
@@@ -16,7 -16,7 +16,7 @@@
  #ifndef __mod_h2__h2_ctx__
  #define __mod_h2__h2_ctx__
  
--struct h2_task_env;
++struct h2_task;
  struct h2_config;
  
  /**
@@@ -30,7 -30,7 +30,7 @@@
  typedef struct h2_ctx {
      int is_h2;                    /* h2 engine is used */
      const char *protocol;         /* the protocol negotiated */
--    struct h2_task_env *task_env; /* the h2_task environment or NULL */
++    struct h2_task *task;         /* the h2_task executing or NULL */
      const char *hostname;         /* hostname negotiated via SNI, optional */
      server_rec *server;           /* httpd server config selected. */
      struct h2_config *config;     /* effective config in this context */
@@@ -38,7 -38,7 +38,7 @@@
  
  h2_ctx *h2_ctx_get(const conn_rec *c);
  h2_ctx *h2_ctx_rget(const request_rec *r);
--h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task_env *env);
++h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task *task);
  
  
  /* Set the h2 protocol established on this connection context or
@@@ -58,6 -58,6 +58,6 @@@ const char *h2_ctx_protocol_get(const c
  int h2_ctx_is_task(h2_ctx *ctx);
  int h2_ctx_is_active(h2_ctx *ctx);
  
--struct h2_task_env *h2_ctx_get_task(h2_ctx *ctx);
++struct h2_task *h2_ctx_get_task(h2_ctx *ctx);
  
  #endif /* defined(__mod_h2__h2_ctx__) */
index be11f5c317e9f742809aadbf320e715e6f5ab576,be11f5c317e9f742809aadbf320e715e6f5ab576..c763ca56ea24c849275ebb818fba73d3a96cabaf
@@@ -78,9 -78,9 +78,9 @@@ h2_response *h2_from_h1_get_response(h2
  
  static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r)
  {
--    from_h1->response = h2_response_create(from_h1->stream_id, 
--                                       from_h1->status, from_h1->hlines,
--                                       from_h1->pool);
++    from_h1->response = h2_response_create(from_h1->stream_id, 0,
++                                           from_h1->status, from_h1->hlines,
++                                           from_h1->pool);
      if (from_h1->response == NULL) {
          ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection,
                        APLOGNO(02915) 
@@@ -492,8 -492,8 +492,8 @@@ static h2_response *create_response(h2_
  
  apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
  {
--    h2_task_env *env = f->ctx;
--    h2_from_h1 *from_h1 = env->output? env->output->from_h1 : NULL;
++    h2_task *task = f->ctx;
++    h2_from_h1 *from_h1 = task->output? task->output->from_h1 : NULL;
      request_rec *r = f->r;
      apr_bucket *b;
      ap_bucket_error *eb = NULL;
      ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
                    "h2_from_h1(%d): output_filter called", from_h1->stream_id);
      
--    if (r->header_only && env->output && from_h1->response) {
++    if (r->header_only && task->output && from_h1->response) {
          /* throw away any data after we have compiled the response */
          apr_brigade_cleanup(bb);
          return OK;
index ac460ef899fe7be8d7d443482002be570e5c3c59,221f5118df9eba4844e05d3cf45e8669a60a4cbc..64ac321db19cab59250f2bc1bd935900901dbc7a
@@@ -53,6 -53,6 +53,384 @@@ APR_DECLARE_OPTIONAL_FN(int, ssl_is_htt
  
  static int (*opt_ssl_engine_disable)(conn_rec*);
  static int (*opt_ssl_is_https)(conn_rec*);
++/*******************************************************************************
++ * SSL var lookup
++ */
++APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
++                        (apr_pool_t *, server_rec *,
++                         conn_rec *, request_rec *,
++                         char *));
++static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *,
++                                   conn_rec *, request_rec *,
++                                   char *);
++
++
++/*******************************************************************************
++ * HTTP/2 error stuff
++ */
++static const char *h2_err_descr[] = {
++    "no error",                    /* 0x0 */
++    "protocol error",
++    "internal error",
++    "flow control error",
++    "settings timeout",
++    "stream closed",               /* 0x5 */
++    "frame size error",
++    "refused stream",
++    "cancel",
++    "compression error",
++    "connect error",               /* 0xa */
++    "enhance your calm",
++    "inadequate security",
++    "http/1.1 required",
++};
++
++const char *h2_h2_err_description(int h2_error)
++{
++    if (h2_error >= 0 
++        && h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
++        return h2_err_descr[h2_error];
++    }
++    return "unknown http/2 errotr code";
++}
++
++/*******************************************************************************
++ * Check connection security requirements of RFC 7540
++ */
++
++/*
++ * Black Listed Ciphers from RFC 7549 Appendix A
++ *
++ */
++static const char *RFC7540_names[] = {
++    /* ciphers with NULL encrpytion */
++    "NULL-MD5",                         /* TLS_NULL_WITH_NULL_NULL */
++    /* same */                          /* TLS_RSA_WITH_NULL_MD5 */
++    "NULL-SHA",                         /* TLS_RSA_WITH_NULL_SHA */
++    "NULL-SHA256",                      /* TLS_RSA_WITH_NULL_SHA256 */
++    "PSK-NULL-SHA",                     /* TLS_PSK_WITH_NULL_SHA */
++    "DHE-PSK-NULL-SHA",                 /* TLS_DHE_PSK_WITH_NULL_SHA */
++    "RSA-PSK-NULL-SHA",                 /* TLS_RSA_PSK_WITH_NULL_SHA */
++    "PSK-NULL-SHA256",                  /* TLS_PSK_WITH_NULL_SHA256 */
++    "PSK-NULL-SHA384",                  /* TLS_PSK_WITH_NULL_SHA384 */
++    "DHE-PSK-NULL-SHA256",              /* TLS_DHE_PSK_WITH_NULL_SHA256 */
++    "DHE-PSK-NULL-SHA384",              /* TLS_DHE_PSK_WITH_NULL_SHA384 */
++    "RSA-PSK-NULL-SHA256",              /* TLS_RSA_PSK_WITH_NULL_SHA256 */
++    "RSA-PSK-NULL-SHA384",              /* TLS_RSA_PSK_WITH_NULL_SHA384 */
++    "ECDH-ECDSA-NULL-SHA",              /* TLS_ECDH_ECDSA_WITH_NULL_SHA */
++    "ECDHE-ECDSA-NULL-SHA",             /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */
++    "ECDH-RSA-NULL-SHA",                /* TLS_ECDH_RSA_WITH_NULL_SHA */
++    "ECDHE-RSA-NULL-SHA",               /* TLS_ECDHE_RSA_WITH_NULL_SHA */
++    "AECDH-NULL-SHA",                   /* TLS_ECDH_anon_WITH_NULL_SHA */
++    "ECDHE-PSK-NULL-SHA",               /* TLS_ECDHE_PSK_WITH_NULL_SHA */
++    "ECDHE-PSK-NULL-SHA256",            /* TLS_ECDHE_PSK_WITH_NULL_SHA256 */
++    "ECDHE-PSK-NULL-SHA384",            /* TLS_ECDHE_PSK_WITH_NULL_SHA384 */
++    
++    /* DES/3DES ciphers */
++    "PSK-3DES-EDE-CBC-SHA",             /* TLS_PSK_WITH_3DES_EDE_CBC_SHA */
++    "DHE-PSK-3DES-EDE-CBC-SHA",         /* TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA */
++    "RSA-PSK-3DES-EDE-CBC-SHA",         /* TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA */
++    "ECDH-ECDSA-DES-CBC3-SHA",          /* TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA */
++    "ECDHE-ECDSA-DES-CBC3-SHA",         /* TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA */
++    "ECDH-RSA-DES-CBC3-SHA",            /* TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA */
++    "ECDHE-RSA-DES-CBC3-SHA",           /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */
++    "AECDH-DES-CBC3-SHA",               /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */
++    "SRP-3DES-EDE-CBC-SHA",             /* TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA */
++    "SRP-RSA-3DES-EDE-CBC-SHA",         /* TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA */
++    "SRP-DSS-3DES-EDE-CBC-SHA",         /* TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA */
++    "ECDHE-PSK-3DES-EDE-CBC-SHA",       /* TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA */
++    "DES-CBC-SHA",                      /* TLS_RSA_WITH_DES_CBC_SHA */
++    "DES-CBC3-SHA",                     /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
++    "DHE-DSS-DES-CBC3-SHA",             /* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */
++    "DHE-RSA-DES-CBC-SHA",              /* TLS_DHE_RSA_WITH_DES_CBC_SHA */
++    "DHE-RSA-DES-CBC3-SHA",             /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
++    "ADH-DES-CBC-SHA",                  /* TLS_DH_anon_WITH_DES_CBC_SHA */
++    "ADH-DES-CBC3-SHA",                 /* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */
++    "EXP-DH-DSS-DES-CBC-SHA",           /* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */
++    "DH-DSS-DES-CBC-SHA",               /* TLS_DH_DSS_WITH_DES_CBC_SHA */
++    "DH-DSS-DES-CBC3-SHA",              /* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */
++    "EXP-DH-RSA-DES-CBC-SHA",           /* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */
++    "DH-RSA-DES-CBC-SHA",               /* TLS_DH_RSA_WITH_DES_CBC_SHA */
++    "DH-RSA-DES-CBC3-SHA",              /* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */
++
++    /* blacklisted EXPORT ciphers */
++    "EXP-RC4-MD5",                      /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */
++    "EXP-RC2-CBC-MD5",                  /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */
++    "EXP-DES-CBC-SHA",                  /* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */
++    "EXP-DHE-DSS-DES-CBC-SHA",          /* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */
++    "EXP-DHE-RSA-DES-CBC-SHA",          /* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */
++    "EXP-ADH-DES-CBC-SHA",              /* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */
++    "EXP-ADH-RC4-MD5",                  /* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */
++
++    /* blacklisted RC4 encryption */
++    "RC4-MD5",                          /* TLS_RSA_WITH_RC4_128_MD5 */
++    "RC4-SHA",                          /* TLS_RSA_WITH_RC4_128_SHA */
++    "ADH-RC4-MD5",                      /* TLS_DH_anon_WITH_RC4_128_MD5 */
++    "KRB5-RC4-SHA",                     /* TLS_KRB5_WITH_RC4_128_SHA */
++    "KRB5-RC4-MD5",                     /* TLS_KRB5_WITH_RC4_128_MD5 */
++    "EXP-KRB5-RC4-SHA",                 /* TLS_KRB5_EXPORT_WITH_RC4_40_SHA */
++    "EXP-KRB5-RC4-MD5",                 /* TLS_KRB5_EXPORT_WITH_RC4_40_MD5 */
++    "PSK-RC4-SHA",                      /* TLS_PSK_WITH_RC4_128_SHA */
++    "DHE-PSK-RC4-SHA",                  /* TLS_DHE_PSK_WITH_RC4_128_SHA */
++    "RSA-PSK-RC4-SHA",                  /* TLS_RSA_PSK_WITH_RC4_128_SHA */
++    "ECDH-ECDSA-RC4-SHA",               /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */
++    "ECDHE-ECDSA-RC4-SHA",              /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA */
++    "ECDH-RSA-RC4-SHA",                 /* TLS_ECDH_RSA_WITH_RC4_128_SHA */
++    "ECDHE-RSA-RC4-SHA",                /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */
++    "AECDH-RC4-SHA",                    /* TLS_ECDH_anon_WITH_RC4_128_SHA */
++    "ECDHE-PSK-RC4-SHA",                /* TLS_ECDHE_PSK_WITH_RC4_128_SHA */
++
++    /* blacklisted AES128 encrpytion ciphers */
++    "AES128-SHA256",                    /* TLS_RSA_WITH_AES_128_CBC_SHA */
++    "DH-DSS-AES128-SHA",                /* TLS_DH_DSS_WITH_AES_128_CBC_SHA */
++    "DH-RSA-AES128-SHA",                /* TLS_DH_RSA_WITH_AES_128_CBC_SHA */
++    "DHE-DSS-AES128-SHA",               /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA */
++    "DHE-RSA-AES128-SHA",               /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA */
++    "ADH-AES128-SHA",                   /* TLS_DH_anon_WITH_AES_128_CBC_SHA */
++    "AES128-SHA256",                    /* TLS_RSA_WITH_AES_128_CBC_SHA256 */
++    "DH-DSS-AES128-SHA256",             /* TLS_DH_DSS_WITH_AES_128_CBC_SHA256 */
++    "DH-RSA-AES128-SHA256",             /* TLS_DH_RSA_WITH_AES_128_CBC_SHA256 */
++    "DHE-DSS-AES128-SHA256",            /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 */
++    "DHE-RSA-AES128-SHA256",            /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 */
++    "ECDH-ECDSA-AES128-SHA",            /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA */
++    "ECDHE-ECDSA-AES128-SHA",           /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */
++    "ECDH-RSA-AES128-SHA",              /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA */
++    "ECDHE-RSA-AES128-SHA",             /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA */
++    "AECDH-AES128-SHA",                 /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */
++    "ECDHE-ECDSA-AES128-SHA256",        /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 */
++    "ECDH-ECDSA-AES128-SHA256",         /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 */
++    "ECDHE-RSA-AES128-SHA256",          /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 */
++    "ECDH-RSA-AES128-SHA256",           /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 */
++    "ADH-AES128-SHA256",                /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */
++    "PSK-AES128-CBC-SHA",               /* TLS_PSK_WITH_AES_128_CBC_SHA */
++    "DHE-PSK-AES128-CBC-SHA",           /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA */
++    "RSA-PSK-AES128-CBC-SHA",           /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA */
++    "PSK-AES128-CBC-SHA256",            /* TLS_PSK_WITH_AES_128_CBC_SHA256 */
++    "DHE-PSK-AES128-CBC-SHA256",        /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 */
++    "RSA-PSK-AES128-CBC-SHA256",        /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 */
++    "ECDHE-PSK-AES128-CBC-SHA",         /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA */
++    "ECDHE-PSK-AES128-CBC-SHA256",      /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 */
++    "AES128-CCM",                       /* TLS_RSA_WITH_AES_128_CCM */
++    "AES128-CCM8",                      /* TLS_RSA_WITH_AES_128_CCM_8 */
++    "PSK-AES128-CCM",                   /* TLS_PSK_WITH_AES_128_CCM */
++    "PSK-AES128-CCM8",                  /* TLS_PSK_WITH_AES_128_CCM_8 */
++    "AES128-GCM-SHA256",                /* TLS_RSA_WITH_AES_128_GCM_SHA256 */
++    "DH-RSA-AES128-GCM-SHA256",         /* TLS_DH_RSA_WITH_AES_128_GCM_SHA256 */
++    "DH-DSS-AES128-GCM-SHA256",         /* TLS_DH_DSS_WITH_AES_128_GCM_SHA256 */
++    "ADH-AES128-GCM-SHA256",            /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */
++    "PSK-AES128-GCM-SHA256",            /* TLS_PSK_WITH_AES_128_GCM_SHA256 */
++    "RSA-PSK-AES128-GCM-SHA256",        /* TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 */
++    "ECDH-ECDSA-AES128-GCM-SHA256",     /* TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 */
++    "ECDH-RSA-AES128-GCM-SHA256",       /* TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 */
++    "SRP-AES-128-CBC-SHA",              /* TLS_SRP_SHA_WITH_AES_128_CBC_SHA */
++    "SRP-RSA-AES-128-CBC-SHA",          /* TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA */
++    "SRP-DSS-AES-128-CBC-SHA",          /* TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA */
++    
++    /* blacklisted AES256 encrpytion ciphers */
++    "AES256-SHA",                       /* TLS_RSA_WITH_AES_256_CBC_SHA */
++    "DH-DSS-AES256-SHA",                /* TLS_DH_DSS_WITH_AES_256_CBC_SHA */
++    "DH-RSA-AES256-SHA",                /* TLS_DH_RSA_WITH_AES_256_CBC_SHA */
++    "DHE-DSS-AES256-SHA",               /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA */
++    "DHE-RSA-AES256-SHA",               /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA */
++    "ADH-AES256-SHA",                   /* TLS_DH_anon_WITH_AES_256_CBC_SHA */
++    "AES256-SHA256",                    /* TLS_RSA_WITH_AES_256_CBC_SHA256 */
++    "DH-DSS-AES256-SHA256",             /* TLS_DH_DSS_WITH_AES_256_CBC_SHA256 */
++    "DH-RSA-AES256-SHA256",             /* TLS_DH_RSA_WITH_AES_256_CBC_SHA256 */
++    "DHE-DSS-AES256-SHA256",            /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 */
++    "DHE-RSA-AES256-SHA256",            /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 */
++    "ADH-AES256-SHA256",                /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */
++    "ECDH-ECDSA-AES256-SHA",            /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA */
++    "ECDHE-ECDSA-AES256-SHA",           /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */
++    "ECDH-RSA-AES256-SHA",              /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA */
++    "ECDHE-RSA-AES256-SHA",             /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA */
++    "AECDH-AES256-SHA",                 /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */
++    "ECDHE-ECDSA-AES256-SHA384",        /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 */
++    "ECDH-ECDSA-AES256-SHA384",         /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 */
++    "ECDHE-RSA-AES256-SHA384",          /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */
++    "ECDH-RSA-AES256-SHA384",           /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 */
++    "PSK-AES256-CBC-SHA",               /* TLS_PSK_WITH_AES_256_CBC_SHA */
++    "DHE-PSK-AES256-CBC-SHA",           /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA */
++    "RSA-PSK-AES256-CBC-SHA",           /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA */
++    "PSK-AES256-CBC-SHA384",            /* TLS_PSK_WITH_AES_256_CBC_SHA384 */
++    "DHE-PSK-AES256-CBC-SHA384",        /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 */
++    "RSA-PSK-AES256-CBC-SHA384",        /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 */
++    "ECDHE-PSK-AES256-CBC-SHA",         /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA */
++    "ECDHE-PSK-AES256-CBC-SHA384",      /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 */
++    "SRP-AES-256-CBC-SHA",              /* TLS_SRP_SHA_WITH_AES_256_CBC_SHA */
++    "SRP-RSA-AES-256-CBC-SHA",          /* TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA */
++    "SRP-DSS-AES-256-CBC-SHA",          /* TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA */
++    "AES256-CCM",                       /* TLS_RSA_WITH_AES_256_CCM */
++    "AES256-CCM8",                      /* TLS_RSA_WITH_AES_256_CCM_8 */
++    "PSK-AES256-CCM",                   /* TLS_PSK_WITH_AES_256_CCM */
++    "PSK-AES256-CCM8",                  /* TLS_PSK_WITH_AES_256_CCM_8 */
++    "AES256-GCM-SHA384",                /* TLS_RSA_WITH_AES_256_GCM_SHA384 */
++    "DH-RSA-AES256-GCM-SHA384",         /* TLS_DH_RSA_WITH_AES_256_GCM_SHA384 */
++    "DH-DSS-AES256-GCM-SHA384",         /* TLS_DH_DSS_WITH_AES_256_GCM_SHA384 */
++    "ADH-AES256-GCM-SHA384",            /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */
++    "PSK-AES256-GCM-SHA384",            /* TLS_PSK_WITH_AES_256_GCM_SHA384 */
++    "RSA-PSK-AES256-GCM-SHA384",        /* TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 */
++    "ECDH-ECDSA-AES256-GCM-SHA384",     /* TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 */
++    "ECDH-RSA-AES256-GCM-SHA384",       /* TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 */
++    
++    /* blacklisted CAMELLIA128 encrpytion ciphers */
++    "CAMELLIA128-SHA",                  /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */
++    "DH-DSS-CAMELLIA128-SHA",           /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA */
++    "DH-RSA-CAMELLIA128-SHA",           /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA */
++    "DHE-DSS-CAMELLIA128-SHA",          /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA */
++    "DHE-RSA-CAMELLIA128-SHA",          /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA */
++    "ADH-CAMELLIA128-SHA",              /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA */
++    "ECDHE-ECDSA-CAMELLIA128-SHA256",   /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
++    "ECDH-ECDSA-CAMELLIA128-SHA256",    /* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
++    "ECDHE-RSA-CAMELLIA128-SHA256",     /* TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++    "ECDH-RSA-CAMELLIA128-SHA256",      /* TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++    "PSK-CAMELLIA128-SHA256",           /* TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
++    "DHE-PSK-CAMELLIA128-SHA256",       /* TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
++    "RSA-PSK-CAMELLIA128-SHA256",       /* TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
++    "ECDHE-PSK-CAMELLIA128-SHA256",     /* TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
++    "CAMELLIA128-GCM-SHA256",           /* TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
++    "DH-RSA-CAMELLIA128-GCM-SHA256",    /* TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
++    "DH-DSS-CAMELLIA128-GCM-SHA256",    /* TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 */
++    "ADH-CAMELLIA128-GCM-SHA256",       /* TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 */
++    "ECDH-ECDSA-CAMELLIA128-GCM-SHA256",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 */
++    "ECDH-RSA-CAMELLIA128-GCM-SHA256",  /* TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
++    "PSK-CAMELLIA128-GCM-SHA256",       /* TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
++    "RSA-PSK-CAMELLIA128-GCM-SHA256",   /* TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
++    "CAMELLIA128-SHA256",               /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++    "DH-DSS-CAMELLIA128-SHA256",        /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
++    "DH-RSA-CAMELLIA128-SHA256",        /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++    "DHE-DSS-CAMELLIA128-SHA256",       /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
++    "DHE-RSA-CAMELLIA128-SHA256",       /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++    "ADH-CAMELLIA128-SHA256",           /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 */
++    
++    /* blacklisted CAMELLIA256 encrpytion ciphers */
++    "CAMELLIA256-SHA",                  /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */
++    "DH-RSA-CAMELLIA256-SHA",           /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA */
++    "DH-DSS-CAMELLIA256-SHA",           /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA */
++    "DHE-DSS-CAMELLIA256-SHA",          /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA */
++    "DHE-RSA-CAMELLIA256-SHA",          /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA */
++    "ADH-CAMELLIA256-SHA",              /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA */
++    "ECDHE-ECDSA-CAMELLIA256-SHA384",   /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
++    "ECDH-ECDSA-CAMELLIA256-SHA384",    /* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
++    "ECDHE-RSA-CAMELLIA256-SHA384",     /* TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
++    "ECDH-RSA-CAMELLIA256-SHA384",      /* TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
++    "PSK-CAMELLIA256-SHA384",           /* TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
++    "DHE-PSK-CAMELLIA256-SHA384",       /* TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
++    "RSA-PSK-CAMELLIA256-SHA384",       /* TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
++    "ECDHE-PSK-CAMELLIA256-SHA384",     /* TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
++    "CAMELLIA256-SHA256",               /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
++    "DH-DSS-CAMELLIA256-SHA256",        /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
++    "DH-RSA-CAMELLIA256-SHA256",        /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
++    "DHE-DSS-CAMELLIA256-SHA256",       /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
++    "DHE-RSA-CAMELLIA256-SHA256",       /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
++    "ADH-CAMELLIA256-SHA256",           /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 */
++    "CAMELLIA256-GCM-SHA384",           /* TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
++    "DH-RSA-CAMELLIA256-GCM-SHA384",    /* TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
++    "DH-DSS-CAMELLIA256-GCM-SHA384",    /* TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 */
++    "ADH-CAMELLIA256-GCM-SHA384",       /* TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 */
++    "ECDH-ECDSA-CAMELLIA256-GCM-SHA384",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 */
++    "ECDH-RSA-CAMELLIA256-GCM-SHA384",  /* TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
++    "PSK-CAMELLIA256-GCM-SHA384",       /* TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
++    "RSA-PSK-CAMELLIA256-GCM-SHA384",   /* TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
++    
++    /* The blacklisted ARIA encrpytion ciphers */
++    "ARIA128-SHA256",                   /* TLS_RSA_WITH_ARIA_128_CBC_SHA256 */
++    "ARIA256-SHA384",                   /* TLS_RSA_WITH_ARIA_256_CBC_SHA384 */
++    "DH-DSS-ARIA128-SHA256",            /* TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 */
++    "DH-DSS-ARIA256-SHA384",            /* TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 */
++    "DH-RSA-ARIA128-SHA256",            /* TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 */
++    "DH-RSA-ARIA256-SHA384",            /* TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 */
++    "DHE-DSS-ARIA128-SHA256",           /* TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 */
++    "DHE-DSS-ARIA256-SHA384",           /* TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 */
++    "DHE-RSA-ARIA128-SHA256",           /* TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 */
++    "DHE-RSA-ARIA256-SHA384",           /* TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 */
++    "ADH-ARIA128-SHA256",               /* TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 */
++    "ADH-ARIA256-SHA384",               /* TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 */
++    "ECDHE-ECDSA-ARIA128-SHA256",       /* TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 */
++    "ECDHE-ECDSA-ARIA256-SHA384",       /* TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 */
++    "ECDH-ECDSA-ARIA128-SHA256",        /* TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 */
++    "ECDH-ECDSA-ARIA256-SHA384",        /* TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 */
++    "ECDHE-RSA-ARIA128-SHA256",         /* TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 */
++    "ECDHE-RSA-ARIA256-SHA384",         /* TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 */
++    "ECDH-RSA-ARIA128-SHA256",          /* TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 */
++    "ECDH-RSA-ARIA256-SHA384",          /* TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 */
++    "ARIA128-GCM-SHA256",               /* TLS_RSA_WITH_ARIA_128_GCM_SHA256 */
++    "ARIA256-GCM-SHA384",               /* TLS_RSA_WITH_ARIA_256_GCM_SHA384 */
++    "DH-DSS-ARIA128-GCM-SHA256",        /* TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 */
++    "DH-DSS-ARIA256-GCM-SHA384",        /* TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 */
++    "DH-RSA-ARIA128-GCM-SHA256",        /* TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 */
++    "DH-RSA-ARIA256-GCM-SHA384",        /* TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 */
++    "ADH-ARIA128-GCM-SHA256",           /* TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 */
++    "ADH-ARIA256-GCM-SHA384",           /* TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 */
++    "ECDH-ECDSA-ARIA128-GCM-SHA256",    /* TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 */
++    "ECDH-ECDSA-ARIA256-GCM-SHA384",    /* TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 */
++    "ECDH-RSA-ARIA128-GCM-SHA256",      /* TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 */
++    "ECDH-RSA-ARIA256-GCM-SHA384",      /* TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 */
++    "PSK-ARIA128-SHA256",               /* TLS_PSK_WITH_ARIA_128_CBC_SHA256 */
++    "PSK-ARIA256-SHA384",               /* TLS_PSK_WITH_ARIA_256_CBC_SHA384 */
++    "DHE-PSK-ARIA128-SHA256",           /* TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 */
++    "DHE-PSK-ARIA256-SHA384",           /* TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 */
++    "RSA-PSK-ARIA128-SHA256",           /* TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 */
++    "RSA-PSK-ARIA256-SHA384",           /* TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 */
++    "ARIA128-GCM-SHA256",               /* TLS_PSK_WITH_ARIA_128_GCM_SHA256 */
++    "ARIA256-GCM-SHA384",               /* TLS_PSK_WITH_ARIA_256_GCM_SHA384 */
++    "RSA-PSK-ARIA128-GCM-SHA256",       /* TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 */
++    "RSA-PSK-ARIA256-GCM-SHA384",       /* TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 */
++    "ECDHE-PSK-ARIA128-SHA256",         /* TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 */
++    "ECDHE-PSK-ARIA256-SHA384",         /* TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 */
++
++    /* blacklisted SEED encryptions */
++    "SEED-SHA",                         /*TLS_RSA_WITH_SEED_CBC_SHA */
++    "DH-DSS-SEED-SHA",                  /* TLS_DH_DSS_WITH_SEED_CBC_SHA */
++    "DH-RSA-SEED-SHA",                  /* TLS_DH_RSA_WITH_SEED_CBC_SHA */
++    "DHE-DSS-SEED-SHA",                 /* TLS_DHE_DSS_WITH_SEED_CBC_SHA */
++    "DHE-RSA-SEED-SHA",                 /* TLS_DHE_RSA_WITH_SEED_CBC_SHA */               
++    "ADH-SEED-SHA",                     /* TLS_DH_anon_WITH_SEED_CBC_SHA */
++
++    /* blacklisted KRB5 ciphers */
++    "KRB5-DES-CBC-SHA",                 /* TLS_KRB5_WITH_DES_CBC_SHA */
++    "KRB5-DES-CBC3-SHA",                /* TLS_KRB5_WITH_3DES_EDE_CBC_SHA */
++    "KRB5-IDEA-CBC-SHA",                /* TLS_KRB5_WITH_IDEA_CBC_SHA */
++    "KRB5-DES-CBC-MD5",                 /* TLS_KRB5_WITH_DES_CBC_MD5 */
++    "KRB5-DES-CBC3-MD5",                /* TLS_KRB5_WITH_3DES_EDE_CBC_MD5 */
++    "KRB5-IDEA-CBC-MD5",                /* TLS_KRB5_WITH_IDEA_CBC_MD5 */
++    "EXP-KRB5-DES-CBC-SHA",             /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA */
++    "EXP-KRB5-DES-CBC-MD5",             /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 */
++    "EXP-KRB5-RC2-CBC-SHA",             /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA */
++    "EXP-KRB5-RC2-CBC-MD5",             /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 */
++  
++    /* blacklisted exoticas */
++    "DHE-DSS-CBC-SHA",                  /* TLS_DHE_DSS_WITH_DES_CBC_SHA */
++    "IDEA-CBC-SHA",                     /* TLS_RSA_WITH_IDEA_CBC_SHA */
++    
++    /* not really sure if the following names are correct */
++    "SSL3_CK_SCSV",                     /* TLS_EMPTY_RENEGOTIATION_INFO_SCSV */
++    "SSL3_CK_FALLBACK_SCSV"
++};
++static size_t RFC7540_names_LEN = sizeof(RFC7540_names)/sizeof(RFC7540_names[0]);
++
++
++static apr_hash_t *BLCNames;
++
++static void cipher_init(apr_pool_t *pool)
++{
++    apr_hash_t *hash = apr_hash_make(pool);
++    const char *source;
++    int i;
++    
++    source = "rfc7540";
++    for (i = 0; i < RFC7540_names_LEN; ++i) {
++        apr_hash_set(hash, RFC7540_names[i], APR_HASH_KEY_STRING, source);
++    }
++    
++    BLCNames = hash;
++}
++
++static int cipher_is_blacklisted(const char *cipher, const char **psource)
++{   
++    *psource = apr_hash_get(BLCNames, cipher, APR_HASH_KEY_STRING);
++    return !!*psource;
++}
++
  /*******************************************************************************
   * Hooks for processing incoming connections:
   * - pre_conn_before_tls switches SSL off for stream connections
@@@ -72,12 -72,12 +450,15 @@@ apr_status_t h2_h2_init(apr_pool_t *poo
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_h2, child_init");
      opt_ssl_engine_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
      opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
++    opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
      
--    if (!opt_ssl_is_https) {
++    if (!opt_ssl_is_https || !opt_ssl_var_lookup) {
          ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
                       APLOGNO(02951) "mod_ssl does not seem to be enabled");
      }
      
++    cipher_init(pool);
++    
      return APR_SUCCESS;
  }
  
@@@ -86,19 -86,18 +467,95 @@@ int h2_h2_is_tls(conn_rec *c
      return opt_ssl_is_https && opt_ssl_is_https(c);
  }
  
--int h2_tls_disable(conn_rec *c)
++int h2_is_acceptable_connection(conn_rec *c, int require_all) 
+ {
 -    if (opt_ssl_engine_disable) {
 -        return opt_ssl_engine_disable(c);
++    int is_tls = h2_h2_is_tls(c);
++    h2_config *cfg = h2_config_get(c);
++
++    if (is_tls && h2_config_geti(cfg, H2_CONF_MODERN_TLS_ONLY) > 0) {
++        /* Check TLS connection for modern TLS parameters, as defined in
++         * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
++         */
++        apr_pool_t *pool = c->pool;
++        server_rec *s = c->base_server;
++        char *val;
++        
++        if (!opt_ssl_var_lookup) {
++            /* unable to check */
++            return 0;
++        }
++        
++        /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2
++         */
++        val = opt_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL");
++        if (val && *val) {
++            if (strncmp("TLS", val, 3) 
++                || !strcmp("TLSv1", val) 
++                || !strcmp("TLSv1.1", val)) {
++            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++                          "h2_h2(%ld): tls protocol not suitable: %s", 
++                          (long)c->id, val);
++                return 0;
++            }
++        }
++        else if (require_all) {
++            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++                          "h2_h2(%ld): tls protocol is indetermined", (long)c->id);
++            return 0;
++        }
++
++        /* Check TLS cipher blacklist
++         */
++        val = opt_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
++        if (val && *val) {
++            const char *source;
++            if (cipher_is_blacklisted(val, &source)) {
++                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++                              "h2_h2(%ld): tls cipher %s blacklisted by %s", 
++                              (long)c->id, val, source);
++                return 0;
++            }
++        }
++        else if (require_all) {
++            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++                          "h2_h2(%ld): tls cipher is indetermined", (long)c->id);
++            return 0;
++        }
+     }
 -    return 0;
++    return 1;
++}
++
++int h2_allows_h2_direct(conn_rec *c)
 +{
-     if (opt_ssl_engine_disable) {
-         return opt_ssl_engine_disable(c);
++    h2_config *cfg = h2_config_get(c);
++    int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT);
++    
++    if (h2_direct < 0) {
++        if (h2_h2_is_tls(c)) {
++            /* disabled by default on TLS */
++            h2_direct = 0;
++        }
++        else {
++            /* enabled if "Protocols h2c" is configured */
++            h2_direct = ap_is_allowed_protocol(c, NULL, NULL, "h2c");
++        }
 +    }
-     return 0;
++    return !!h2_direct;
 +}
 +
++int h2_allows_h2_upgrade(conn_rec *c)
++{
++    h2_config *cfg = h2_config_get(c);
++    int h2_upgrade = h2_config_geti(cfg, H2_CONF_UPGRADE);
++    
++    return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(c));
+ }
  
  /*******************************************************************************
   * Register various hooks
   */
  static const char *const mod_reqtimeout[] = { "reqtimeout.c", NULL};
++static const char* const mod_ssl[]        = {"mod_ssl.c", NULL};
  
  void h2_h2_register_hooks(void)
  {
       * take over, if h2* was selected as protocol.
       */
      ap_hook_process_connection(h2_h2_process_conn, 
--                               NULL, NULL, APR_HOOK_FIRST);
++                               mod_ssl, NULL, APR_HOOK_MIDDLE);
++                               
      /* Perform connection cleanup before the actual processing happens.
       */
      ap_hook_process_connection(h2_h2_remove_timeout, 
@@@ -135,9 -134,9 +593,6 @@@ static int h2_h2_remove_timeout(conn_re
  int h2_h2_process_conn(conn_rec* c)
  {
      h2_ctx *ctx = h2_ctx_get(c);
--    h2_config *cfg = h2_config_get(c);
--    apr_bucket_brigade* temp;
--    int is_tls = h2_h2_is_tls(c);
      
      ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
      if (h2_ctx_is_task(ctx)) {
          return DECLINED;
      }
  
--    /* If we have not already switched to a h2* protocol and the connection 
--     * is on "http/1.1"
--     * -> sniff for the magic PRIamble. On TLS, this might trigger the ALPN.
--     */
--    if (!h2_ctx_protocol_get(c) 
--        && !strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
++    if (h2_ctx_protocol_get(c)) {
++        /* Something has been negotiated */
++    }
++    else if (!strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))
++             && h2_allows_h2_direct(c) 
++             && h2_is_acceptable_connection(c, 1)) {
++        /* connection still is on http/1.1 and H2Direct is enabled. 
++         * Otherwise connection is in a fully acceptable state.
++         * -> peek at the first 24 incoming bytes
++         */
++        apr_bucket_brigade *temp;
          apr_status_t status;
++        char *s = NULL;
++        apr_size_t slen;
          
          temp = apr_brigade_create(c->pool, c->bucket_alloc);
          status = ap_get_brigade(c->input_filters, temp,
                                  AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
--
--        if (status == APR_SUCCESS) {
--            if (h2_ctx_protocol_get(c) 
--                || strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
--                /* h2 or another protocol has been selected. */
--            }
--            else {
--                /* ALPN might have been triggered, but we're still on
--                 * http/1.1. Check the actual bytes read for the H2 Magic
--                 * Token, *if* H2Direct mode is enabled here. 
--                 */
--                int direct_mode = h2_config_geti(cfg, H2_CONF_DIRECT);
--                if (direct_mode > 0 || (direct_mode < 0 && !is_tls)) {
--                    char *s = NULL;
--                    apr_size_t slen;
--                    
--                    apr_brigade_pflatten(temp, &s, &slen, c->pool);
--                    if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) {
--                        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
--                                      "h2_h2, direct mode detected");
--                        h2_ctx_protocol_set(ctx, is_tls? "h2" : "h2c");
--                    }
--                    else {
--                        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
--                                      "h2_h2, not detected in %d bytes: %s", 
--                                      (int)slen, s);
--                    }
--                }
--            }
--        }
--        else {
++        
++        if (status != APR_SUCCESS) {
              ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
                            "h2_h2, error reading 24 bytes speculative");
++            apr_brigade_destroy(temp);
++            return DECLINED;
++        }
++        
++        apr_brigade_pflatten(temp, &s, &slen, c->pool);
++        if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) {
++            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
++                          "h2_h2, direct mode detected");
++            h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c");
          }
++        else {
++            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
++                          "h2_h2, not detected in %d bytes: %s", 
++                          (int)slen, s);
++        }
++        
          apr_brigade_destroy(temp);
      }
++    else {
++        /* the connection is not HTTP/1.1 or not for us, don't touch it */
++        return DECLINED;
++    }
  
      /* If "h2" was selected as protocol (by whatever mechanism), take over
       * the connection.
  static int h2_h2_post_read_req(request_rec *r)
  {
      h2_ctx *ctx = h2_ctx_rget(r);
--    struct h2_task_env *env = h2_ctx_get_task(ctx);
--    if (env) {
++    struct h2_task *task = h2_ctx_get_task(ctx);
++    if (task) {
          /* h2_task connection for a stream, not for h2c */
          ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                        "adding h1_to_h2_resp output filter");
--        if (env->serialize_headers) {
++        if (task->serialize_headers) {
              ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP");
--            ap_add_output_filter("H1_TO_H2_RESP", env, r, r->connection);
++            ap_add_output_filter("H1_TO_H2_RESP", task, r, r->connection);
          }
          else {
              /* replace the core http filter that formats response headers
               * in HTTP/1 with our own that collects status and headers */
              ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
              ap_remove_output_filter_byhandle(r->output_filters, "H2_RESPONSE");
--            ap_add_output_filter("H2_RESPONSE", env, r, r->connection);
++            ap_add_output_filter("H2_RESPONSE", task, r, r->connection);
          }
      }
      return DECLINED;
index 9a1184d8b697d37f1b24de6faedadc5216905078,9a1184d8b697d37f1b24de6faedadc5216905078..4cf2785929da64ec60feb702e68758b1ac3e5444
@@@ -34,6 -34,6 +34,31 @@@ extern const char *h2_tls_protos[]
   */
  extern const char *H2_MAGIC_TOKEN;
  
++#define H2_ERR_NO_ERROR             (0x00)
++#define H2_ERR_PROTOCOL_ERROR       (0x01)
++#define H2_ERR_INTERNAL_ERROR       (0x02)
++#define H2_ERR_FLOW_CONTROL_ERROR   (0x03)
++#define H2_ERR_SETTINGS_TIMEOUT     (0x04)
++#define H2_ERR_STREAM_CLOSED        (0x05)
++#define H2_ERR_FRAME_SIZE_ERROR     (0x06)
++#define H2_ERR_REFUSED_STREAM       (0x07)
++#define H2_ERR_CANCEL               (0x08)
++#define H2_ERR_COMPRESSION_ERROR    (0x09)
++#define H2_ERR_CONNECT_ERROR        (0x0a)
++#define H2_ERR_ENHANCE_YOUR_CALM    (0x0b)
++#define H2_ERR_INADEQUATE_SECURITY  (0x0c)
++#define H2_ERR_HTTP_1_1_REQUIRED    (0x0d)
++
++/* Maximum number of padding bytes in a frame, rfc7540 */
++#define H2_MAX_PADLEN               256
++
++/**
++ * Provide a user readable description of the HTTP/2 error code-
++ * @param h2_error http/2 error code, as in rfc 7540, ch. 7
++ * @return textual description of code or that it is unknown.
++ */
++const char *h2_h2_err_description(int h2_error);
++
  /*
   * One time, post config intialization.
   */
@@@ -43,15 -43,15 +68,35 @@@ apr_status_t h2_h2_init(apr_pool_t *poo
   */
  int h2_h2_is_tls(conn_rec *c);
  
--/* Disable SSL for this connection, can only be invoked in a pre-
-- * connection hook before mod_ssl.
-- * @return != 0 iff disable worked
-- */
--int h2_tls_disable(conn_rec *c);
--
  /* Register apache hooks for h2 protocol
   */
  void h2_h2_register_hooks(void);
  
++/**
++ * Check if the given connection fulfills the requirements as configured.
++ * @param c the connection
++ * @param require_all != 0 iff any missing connection properties make
++ *    the test fail. For example, a cipher might not have been selected while
++ *    the handshake is still ongoing.
++ * @return != 0 iff connection requirements are met
++ */
++int h2_is_acceptable_connection(conn_rec *c, int require_all);
++
++/**
++ * Check if the "direct" HTTP/2 mode of protocol handling is enabled
++ * for the given connection.
++ * @param c the connection to check
++ * @return != 0 iff direct mode is enabled
++ */
++int h2_allows_h2_direct(conn_rec *c);
++
++/**
++ * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
++ * for the given connection.
++ * @param c the connection to check
++ * @return != 0 iff Upgrade switching is enabled
++ */
++int h2_allows_h2_upgrade(conn_rec *c);
++
  
  #endif /* defined(__mod_h2__h2_h2__) */
index 93bcca86085b7da64e6fccaa83e28d4ce778391d,42734430fa48f517a7aafa699918ee300efdaa9c..e952ad0f6b4cb29884acbbf6009761f27395568a
@@@ -23,6 -23,6 +23,7 @@@
  #include "h2_private.h"
  #include "h2_io.h"
  #include "h2_response.h"
++#include "h2_task.h"
  #include "h2_util.h"
  
  h2_io *h2_io_create(int id, apr_pool_t *pool, apr_bucket_alloc_t *bucket_alloc)
  
  static void h2_io_cleanup(h2_io *io)
  {
--    (void)io;
++    if (io->task) {
++        h2_task_destroy(io->task);
++        io->task = NULL;
++    }
  }
  
  void h2_io_destroy(h2_io *io)
      h2_io_cleanup(io);
  }
  
++void h2_io_set_response(h2_io *io, h2_response *response) 
++{
++    AP_DEBUG_ASSERT(response);
++    AP_DEBUG_ASSERT(!io->response);
++    io->response = h2_response_copy(io->pool, response);
++    if (response->rst_error) {
++        h2_io_rst(io, response->rst_error);
++    }
++}
++
++
 +void h2_io_rst(h2_io *io, int error)
 +{
 +    io->rst_error = error;
 +    io->eos_in = 1;
 +}
 +
  int h2_io_in_has_eos_for(h2_io *io)
  {
      return io->eos_in || (io->bbin && h2_util_has_eos(io->bbin, 0));
@@@ -90,8 -80,8 +105,7 @@@ apr_status_t h2_io_in_read(h2_io *io, a
      
      apr_brigade_length(bb, 1, &start_len);
      last = APR_BRIGADE_LAST(bb);
--    status = h2_util_move(bb, io->bbin, maxlen, 0, 
--                                       "h2_io_in_read");
++    status = h2_util_move(bb, io->bbin, maxlen, NULL, "h2_io_in_read");
      if (status == APR_SUCCESS) {
          apr_bucket *nlast = APR_BRIGADE_LAST(bb);
          apr_off_t end_len = 0;
@@@ -119,7 -105,7 +133,7 @@@ apr_status_t h2_io_in_write(h2_io *io, 
              io->bbin = apr_brigade_create(io->bbout->p, 
                                            io->bbout->bucket_alloc);
          }
--        return h2_util_move(io->bbin, bb, 0, 0, "h2_io_in_write");
++        return h2_util_move(io->bbin, bb, 0, NULL, "h2_io_in_write");
      }
      return APR_SUCCESS;
  }
@@@ -142,32 -124,13 +156,50 @@@ apr_status_t h2_io_out_readx(h2_io *io
                               h2_io_data_cb *cb, void *ctx, 
                               apr_size_t *plen, int *peos)
  {
 +    apr_status_t status;
 +    
 +    if (io->rst_error) {
 +        return APR_ECONNABORTED;
 +    }
 +    
 +    if (io->eos_out) {
 +        *plen = 0;
 +        *peos = 1;
 +        return APR_SUCCESS;
 +    }
 +    
      if (cb == NULL) {
          /* just checking length available */
 -        return h2_util_bb_avail(io->bbout, plen, peos);
 +        status = h2_util_bb_avail(io->bbout, plen, peos);
      }
 -    return h2_util_bb_readx(io->bbout, cb, ctx, plen, peos);
 +    else {
 +        status = h2_util_bb_readx(io->bbout, cb, ctx, plen, peos);
 +        if (status == APR_SUCCESS) {
 +            io->eos_out = *peos;
 +        }
 +    }
 +    
 +    return status;
 +}
 +
++apr_status_t h2_io_out_read_to(h2_io *io, apr_bucket_brigade *bb, 
++                               apr_size_t *plen, int *peos)
++{
++    if (io->rst_error) {
++        return APR_ECONNABORTED;
++    }
++    
++    if (io->eos_out) {
++        *plen = 0;
++        *peos = 1;
++        return APR_SUCCESS;
++    }
++    
++
++    io->eos_out = *peos = h2_util_has_eos(io->bbout, *plen);
++    return h2_util_move(bb, io->bbout, *plen, NULL, "h2_io_read_to");
+ }
  apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, 
                               apr_size_t maxlen, int *pfile_handles_allowed)
  {
index 23655d21dfa60d70f075868dc28442325c0c5e11,946ee44334e8d995a3a0e8ab05ad4b7f86549c34..d8769dd1182c6c7f1f19cfcdf9af31bbbe3479db
@@@ -21,8 -21,8 +21,9 @@@ struct apr_thread_cond_t
  struct h2_task;
  
  
--typedef apr_status_t h2_io_data_cb(void *ctx, 
--                                   const char *data, apr_size_t len);
++typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_size_t len);
++
++typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx);
  
  
  typedef struct h2_io h2_io;
@@@ -33,8 -33,7 +34,11 @@@ struct h2_io 
      apr_bucket_brigade *bbin;    /* input data for stream */
      int eos_in;
      int task_done;
 +    int rst_error;
++    int zombie;
      
++    struct h2_task *task;       /* task created for this io */
++
      apr_size_t input_consumed;   /* how many bytes have been read */
      struct apr_thread_cond_t *input_arrived; /* block on reading */
      
@@@ -60,11 -58,6 +64,16 @@@ h2_io *h2_io_create(int id, apr_pool_t 
   */
  void h2_io_destroy(h2_io *io);
  
++/**
++ * Set the response of this stream.
++ */
++void h2_io_set_response(h2_io *io, struct h2_response *response);
++
 +/**
 + * Reset the stream with the given error code.
 + */
 +void h2_io_rst(h2_io *io, int error);
 +
  /**
   * The input data is completely queued. Blocked reads will return immediately
   * and give either data or EOF.
@@@ -114,6 -107,6 +123,10 @@@ apr_status_t h2_io_out_readx(h2_io *io
                               h2_io_data_cb *cb, void *ctx, 
                               apr_size_t *plen, int *peos);
  
++apr_status_t h2_io_out_read_to(h2_io *io, 
++                               apr_bucket_brigade *bb, 
++                               apr_size_t *plen, int *peos);
++
  apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, 
                               apr_size_t maxlen, int *pfile_buckets_allowed);
  
index ae33766e4ac30bd783aa9ada52c373ca1402114e,2d07b1eb6c344c64ccbb6b87c3781adbe3076204..ad6bd30e32d037de69890a0dc83975d9f3210b7b
@@@ -60,10 -58,10 +60,6 @@@ static void h2_mplx_destroy(h2_mplx *m
  {
      AP_DEBUG_ASSERT(m);
      m->aborted = 1;
--    if (m->q) {
--        h2_tq_destroy(m->q);
--        m->q = NULL;
--    }
      if (m->ready_ios) {
          h2_io_set_destroy(m->ready_ios);
          m->ready_ios = NULL;
@@@ -128,10 -126,10 +124,9 @@@ h2_mplx *h2_mplx_create(conn_rec *c, ap
          
          m->bucket_alloc = apr_bucket_alloc_create(m->pool);
          
--        m->q = h2_tq_create(m->id, m->pool);
++        m->q = h2_tq_create(m->pool, h2_config_geti(conf, H2_CONF_MAX_STREAMS));
          m->stream_ios = h2_io_set_create(m->pool);
          m->ready_ios = h2_io_set_create(m->pool);
--        m->closed = h2_stream_set_create(m->pool);
          m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
          m->workers = workers;
          
@@@ -226,52 -230,52 +221,18 @@@ void h2_mplx_abort(h2_mplx *m
  }
  
  
--h2_stream *h2_mplx_open_io(h2_mplx *m, int stream_id)
 -{
 -    h2_stream *stream = NULL;
 -    apr_status_t status; 
 -    h2_io *io;
 -
 -    if (m->aborted) {
 -        return NULL;
 -    }
 -    status = apr_thread_mutex_lock(m->lock);
 -    if (APR_SUCCESS == status) {
 -        apr_pool_t *stream_pool = m->spare_pool;
 -        
 -        if (!stream_pool) {
 -            apr_pool_create(&stream_pool, m->pool);
 -        }
 -        else {
 -            m->spare_pool = NULL;
 -        }
 -        
 -        stream = h2_stream_create(stream_id, stream_pool, m);
 -        stream->state = H2_STREAM_ST_OPEN;
 -        
 -        io = h2_io_set_get(m->stream_ios, stream_id);
 -        if (!io) {
 -            io = h2_io_create(stream_id, stream_pool, m->bucket_alloc);
 -            h2_io_set_add(m->stream_ios, io);
 -        }
 -        status = io? APR_SUCCESS : APR_ENOMEM;
 -        apr_thread_mutex_unlock(m->lock);
 -    }
 -    return stream;
 -}
 -
 -static void stream_destroy(h2_mplx *m, h2_stream *stream, h2_io *io)
++static void io_destroy(h2_mplx *m, h2_io *io)
  {
-     h2_stream *stream = NULL;
-     apr_status_t status; 
-     h2_io *io;
-     if (m->aborted) {
-         return NULL;
-     }
-     status = apr_thread_mutex_lock(m->lock);
-     if (APR_SUCCESS == status) {
-         apr_pool_t *stream_pool = m->spare_pool;
-         
-         if (!stream_pool) {
-             apr_pool_create(&stream_pool, m->pool);
-         }
-         else {
-             m->spare_pool = NULL;
-         }
-         
-         stream = h2_stream_create(stream_id, stream_pool, m);
-         stream->state = H2_STREAM_ST_OPEN;
-         
-         io = h2_io_set_get(m->stream_ios, stream_id);
-         if (!io) {
-             io = h2_io_create(stream_id, stream_pool, m->bucket_alloc);
-             h2_io_set_add(m->stream_ios, io);
-         }
-         status = io? APR_SUCCESS : APR_ENOMEM;
-         apr_thread_mutex_unlock(m->lock);
-     }
-     return stream;
- }
- static void stream_destroy(h2_mplx *m, h2_stream *stream, h2_io *io)
- {
--    apr_pool_t *pool = h2_stream_detach_pool(stream);
--    if (pool) {
--        apr_pool_clear(pool);
--        if (m->spare_pool) {
--            apr_pool_destroy(m->spare_pool);
--        }
--        m->spare_pool = pool;
--    }
--    h2_stream_destroy(stream);
      if (io) {
++        apr_pool_t *pool = io->pool;
++        if (pool) {
++            io->pool = NULL;
++            apr_pool_clear(pool);
++            if (m->spare_pool) {
++                apr_pool_destroy(m->spare_pool);
++            }
++            m->spare_pool = pool;
++        }
          /* The pool is cleared/destroyed which also closes all
           * allocated file handles. Give this count back to our
           * file handle pool. */
      }
  }
  
--apr_status_t h2_mplx_cleanup_stream(h2_mplx *m, h2_stream *stream)
++apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error)
  {
      apr_status_t status;
++    
      AP_DEBUG_ASSERT(m);
++    if (m->aborted) {
++        return APR_ECONNABORTED;
++    }
      status = apr_thread_mutex_lock(m->lock);
      if (APR_SUCCESS == status) {
--        h2_io *io = h2_io_set_get(m->stream_ios, stream->id);
 -        if (!io || io->task_done) {
 -            /* No more io or task already done -> cleanup immediately */
 -            stream_destroy(m, stream, io);
 -        }
 -        else {
 -            /* Add stream to closed set for cleanup when task is done */
 -            h2_stream_set_add(m->closed, stream);
++        h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
++        
 +        if (io) {
 +            /* Remove io from ready set, we will never submit it */
 +            h2_io_set_remove(m->ready_ios, io);
-             if (stream->rst_error) {
-                 /* Forward error code to fail any further attempt to
-                  * write to io */
-                 h2_io_rst(io, stream->rst_error);
++            
++            if (io->task_done) {
++                io_destroy(m, io);
++            }
++            else {
++                /* cleanup once task is done */
++                io->zombie = 1;
++                if (rst_error) {
++                    /* Forward error code to fail any further attempt to
++                     * write to io */
++                    h2_io_rst(io, rst_error);
++                }
 +            }
          }
-         if (!io || io->task_done) {
-             /* No more io or task already done -> cleanup immediately */
-             stream_destroy(m, stream, io);
-         }
-         else {
-             /* Add stream to closed set for cleanup when task is done */
-             h2_stream_set_add(m->closed, stream);
-         }
 +        
          apr_thread_mutex_unlock(m->lock);
      }
      return status;
@@@ -316,21 -310,21 +282,17 @@@ void h2_mplx_task_done(h2_mplx *m, int 
  {
      apr_status_t status = apr_thread_mutex_lock(m->lock);
      if (APR_SUCCESS == status) {
--        h2_stream *stream = h2_stream_set_get(m->closed, stream_id);
          h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
          ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
                        "h2_mplx(%ld): task(%d) done", m->id, stream_id);
--        if (stream) {
--            /* stream was already closed by main connection and is in 
--             * zombie state. Now that the task is done with it, we
--             * can free its resources. */
--            h2_stream_set_remove(m->closed, stream);
--            stream_destroy(m, stream, io);
--        }
--        else if (io) {
--            /* main connection has not finished stream. Mark task as done
--             * so that eventual cleanup can start immediately. */
++        if (io) {
              io->task_done = 1;
++            if (io->zombie) {
++                io_destroy(m, io);
++            }
++            else {
++                /* hang around until the stream deregisteres */
++            }
          }
          apr_thread_mutex_unlock(m->lock);
      }
@@@ -480,23 -467,19 +442,53 @@@ apr_status_t h2_mplx_out_readx(h2_mplx 
      if (APR_SUCCESS == status) {
          h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
          if (io) {
 +            H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_pre");
 +            
              status = h2_io_out_readx(io, cb, ctx, plen, peos);
 +            
 +            H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_post");
 +            if (status == APR_SUCCESS && cb && io->output_drained) {
 +                apr_thread_cond_signal(io->output_drained);
 +            }
 +        }
 +        else {
 +            status = APR_ECONNABORTED;
 +        }
 +        apr_thread_mutex_unlock(m->lock);
 +    }
 +    return status;
 +}
 +
++apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id, 
++                                 apr_bucket_brigade *bb, 
++                                 apr_size_t *plen, int *peos)
++{
++    apr_status_t status;
++    AP_DEBUG_ASSERT(m);
++    if (m->aborted) {
++        return APR_ECONNABORTED;
++    }
++    status = apr_thread_mutex_lock(m->lock);
++    if (APR_SUCCESS == status) {
++        h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
++        if (io) {
++            H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_pre");
++            
++            status = h2_io_out_read_to(io, bb, plen, peos);
++            
++            H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_post");
+             if (status == APR_SUCCESS && io->output_drained) {
+                 apr_thread_cond_signal(io->output_drained);
+             }
+         }
+         else {
+             status = APR_ECONNABORTED;
+         }
+         apr_thread_mutex_unlock(m->lock);
+     }
+     return status;
+ }
  h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams)
  {
      apr_status_t status;
      }
      status = apr_thread_mutex_lock(m->lock);
      if (APR_SUCCESS == status) {
 -        h2_io *io = h2_io_set_get_highest_prio(m->ready_ios);
 +        h2_io *io = h2_io_set_pop_highest_prio(m->ready_ios);
          if (io) {
 -            h2_response *response = io->response;
 -            
 -            AP_DEBUG_ASSERT(response);
 -            h2_io_set_remove(m->ready_ios, io);
 -            
 -            stream = h2_stream_set_get(streams, response->stream_id);
 +            stream = h2_stream_set_get(streams, io->id);
              if (stream) {
 -                h2_stream_set_response(stream, response, io->bbout);
 -                if (io->output_drained) {
 -                    apr_thread_cond_signal(io->output_drained);
 +                if (io->rst_error) {
 +                    h2_stream_rst(stream, io->rst_error);
 +                }
 +                else {
 +                    AP_DEBUG_ASSERT(io->response);
 +                    h2_stream_set_response(stream, io->response, io->bbout);
                  }
 +                
              }
              else {
 -                ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_NOTFOUND, m->c,
 -                              APLOGNO(02953) "h2_mplx(%ld): stream for response %d",
 -                              m->id, response->stream_id);
 +                /* We have the io ready, but the stream has gone away, maybe
 +                 * reset by the client. Should no longer happen since such
 +                 * streams should clear io's from the ready queue.
 +                 */
 +                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c, APLOGNO(02953) 
 +                              "h2_mplx(%ld): stream for response %d closed, "
 +                              "resetting io to close request processing",
 +                              m->id, io->id);
-                 h2_io_rst(io, NGHTTP2_ERR_STREAM_CLOSED);
++                h2_io_rst(io, H2_ERR_STREAM_CLOSED);
 +            }
 +            
 +            if (io->output_drained) {
 +                apr_thread_cond_signal(io->output_drained);
              }
          }
          apr_thread_mutex_unlock(m->lock);
@@@ -588,12 -562,12 +580,12 @@@ static apr_status_t out_open(h2_mplx *m
      if (io) {
          if (f) {
              ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
--                          "h2_mplx(%ld-%d): open response: %s",
--                          m->id, stream_id, response->status);
++                          "h2_mplx(%ld-%d): open response: %s, rst=%d",
++                          m->id, stream_id, response->status, 
++                          response->rst_error);
          }
          
--        io->response = h2_response_copy(io->pool, response);
--        AP_DEBUG_ASSERT(io->response);
++        h2_io_set_response(io, response);
          h2_io_set_add(m->ready_ios, io);
          if (bb) {
              status = out_write(m, io, f, bb, iowait);
@@@ -681,16 -650,11 +673,16 @@@ apr_status_t h2_mplx_out_close(h2_mplx 
                       * insert an error one so that our streams can properly
                       * reset.
                       */
--                    h2_response *r = h2_response_create(stream_id, 
++                    h2_response *r = h2_response_create(stream_id, 0, 
                                                          "500", NULL, m->pool);
                      status = out_open(m, stream_id, r, NULL, NULL, NULL);
 +                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c,
 +                                  "h2_mplx(%ld-%d): close, no response, no rst", 
 +                                  m->id, io->id);
                  }
                  status = h2_io_out_close(io);
 +                H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_close");
 +                
                  have_out_data_for(m, stream_id);
                  if (m->aborted) {
                      /* if we were the last output, the whole session might
      return status;
  }
  
-                 if (m->aborted) {
-                     /* if we were the last output, the whole session might
-                      * have gone down in the meantime.
-                      */
-                     return APR_SUCCESS;
 +apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error)
 +{
 +    apr_status_t status;
 +    AP_DEBUG_ASSERT(m);
 +    if (m->aborted) {
 +        return APR_ECONNABORTED;
 +    }
 +    status = apr_thread_mutex_lock(m->lock);
 +    if (APR_SUCCESS == status) {
 +        if (!m->aborted) {
 +            h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
 +            if (io && !io->rst_error) {
 +                h2_io_rst(io, error);
 +                if (!io->response) {
 +                        h2_io_set_add(m->ready_ios, io);
 +                }
 +                H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_rst");
 +                
 +                have_out_data_for(m, stream_id);
++                if (io->output_drained) {
++                    apr_thread_cond_signal(io->output_drained);
 +                }
 +            }
 +            else {
 +                status = APR_ECONNABORTED;
 +            }
 +        }
 +        apr_thread_mutex_unlock(m->lock);
 +    }
 +    return status;
 +}
 +
  int h2_mplx_in_has_eos_for(h2_mplx *m, int stream_id)
  {
      int has_eos = 0;
@@@ -813,61 -742,61 +802,116 @@@ static void have_out_data_for(h2_mplx *
      }
  }
  
--apr_status_t h2_mplx_do_task(h2_mplx *m, struct h2_task *task)
++typedef struct {
++    h2_stream_pri_cmp *cmp;
++    void *ctx;
++} cmp_ctx;
++
++static int task_cmp(h2_task *t1, h2_task *t2, void *ctx)
++{
++    cmp_ctx *x = ctx;
++    return x->cmp(t1->stream_id, t2->stream_id, x->ctx);
++}
++
++apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
  {
      apr_status_t status;
++    
      AP_DEBUG_ASSERT(m);
      if (m->aborted) {
          return APR_ECONNABORTED;
      }
      status = apr_thread_mutex_lock(m->lock);
      if (APR_SUCCESS == status) {
--        /* TODO: needs to sort queue by priority */
++        cmp_ctx x;
++        
++        x.cmp = cmp;
++        x.ctx = ctx;
++        h2_tq_sort(m->q, task_cmp, &x);
++        
          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
--                      "h2_mplx: do task(%s)", task->id);
--        h2_tq_append(m->q, task);
++                      "h2_mplx(%ld): reprioritize tasks", m->id);
          apr_thread_mutex_unlock(m->lock);
      }
      workers_register(m);
      return status;
  }
  
--h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more)
++static h2_io *open_io(h2_mplx *m, int stream_id)
++{
++    apr_pool_t *io_pool = m->spare_pool;
++    h2_io *io;
++    
++    if (!io_pool) {
++        apr_pool_create(&io_pool, m->pool);
++    }
++    else {
++        m->spare_pool = NULL;
++    }
++    
++    io = h2_io_create(stream_id, io_pool, m->bucket_alloc);
++    h2_io_set_add(m->stream_ios, io);
++    
++    return io;
++}
++
++
++apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
++                             struct h2_request *r, int eos, 
++                             h2_stream_pri_cmp *cmp, void *ctx)
  {
--    h2_task *task = NULL;
      apr_status_t status;
++    
      AP_DEBUG_ASSERT(m);
      if (m->aborted) {
--        *has_more = 0;
--        return NULL;
++        return APR_ECONNABORTED;
      }
      status = apr_thread_mutex_lock(m->lock);
      if (APR_SUCCESS == status) {
--        task = h2_tq_pop_first(m->q);
--        if (task) {
--            h2_task_set_started(task);
++        conn_rec *c;
++        h2_io *io;
++        cmp_ctx x;
++        
++        io = open_io(m, stream_id);
++        c = h2_conn_create(m->c, io->pool);
++        io->task = h2_task_create(m->id, stream_id, io->pool, m, c);
++            
++        status = h2_request_end_headers(r, m, io->task, eos);
++        if (status == APR_SUCCESS && eos) {
++            status = h2_io_in_close(io);
          }
--        *has_more = !h2_tq_empty(m->q);
++        
++        if (status == APR_SUCCESS) {
++            x.cmp = cmp;
++            x.ctx = ctx;
++            h2_tq_add(m->q, io->task, task_cmp, &x);
++        }
++        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
++                      "h2_mplx(%ld-%d): process", m->c->id, stream_id);
          apr_thread_mutex_unlock(m->lock);
      }
--    return task;
++    
++    if (status == APR_SUCCESS) {
++        workers_register(m);
++    }
++    return status;
  }
  
--apr_status_t h2_mplx_create_task(h2_mplx *m, struct h2_stream *stream)
++h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more)
  {
++    h2_task *task = NULL;
      apr_status_t status;
      AP_DEBUG_ASSERT(m);
      if (m->aborted) {
--        return APR_ECONNABORTED;
++        *has_more = 0;
++        return NULL;
      }
      status = apr_thread_mutex_lock(m->lock);
      if (APR_SUCCESS == status) {
--        conn_rec *c = h2_conn_create(m->c, stream->pool);
--        stream->task = h2_task_create(m->id, stream->id, 
--                                      stream->pool, m, c);
--        
++        task = h2_tq_shift(m->q);
++        *has_more = !h2_tq_empty(m->q);
          apr_thread_mutex_unlock(m->lock);
      }
--    return status;
++    return task;
  }
  
index 5cb40af16fa393040e2a7357439644e98867f56a,62977d6157cecd675eb3fce9dcfcd8c706bfe191..2bb650535efb34de3510a9af47b8b45b0c14d159
@@@ -41,6 -41,6 +41,7 @@@ struct h2_config
  struct h2_response;
  struct h2_task;
  struct h2_stream;
++struct h2_request;
  struct h2_io_set;
  struct apr_thread_cond_t;
  struct h2_workers;
@@@ -70,12 -70,12 +71,13 @@@ struct h2_mplx 
      int aborted;
      apr_size_t stream_max_mem;
      
--    apr_pool_t *spare_pool;           /* spare pool, ready for next stream */
--    struct h2_stream_set *closed;     /* streams closed, but task ongoing */
++    apr_pool_t *spare_pool;           /* spare pool, ready for next io */
      struct h2_workers *workers;
      int file_handles_allowed;
  };
  
++
++
  /*******************************************************************************
   * Object lifecycle and information.
   ******************************************************************************/
@@@ -118,15 -117,15 +120,16 @@@ void h2_mplx_task_done(h2_mplx *m, int 
  /*******************************************************************************
   * IO lifetime of streams.
   ******************************************************************************/
--/**
-- * Prepares the multiplexer to handle in-/output on the given stream id.
-- */
--struct h2_stream *h2_mplx_open_io(h2_mplx *mplx, int stream_id);
  
  /**
-- * Ends cleanup of a stream in sync with execution thread.
++ * Notifies mplx that a stream has finished processing.
++ * 
++ * @param m the mplx itself
++ * @param stream_id the id of the stream being done
++ * @param rst_error if != 0, the stream was reset with the error given
++ *
   */
--apr_status_t h2_mplx_cleanup_stream(h2_mplx *m, struct h2_stream *stream);
++apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error);
  
  /* Return != 0 iff the multiplexer has data for the given stream. 
   */
@@@ -144,13 -143,13 +147,29 @@@ apr_status_t h2_mplx_out_trywait(h2_mpl
   ******************************************************************************/
  
  /**
-- * Perform the task on the given stream.
++ * Process a stream request.
++ * 
++ * @param m the multiplexer
++ * @param stream_id the identifier of the stream
++ * @param r the request to be processed
++ * @param eos if input is complete
++ * @param cmp the stream priority compare function
++ * @param ctx context data for the compare function
   */
--apr_status_t h2_mplx_do_task(h2_mplx *mplx, struct h2_task *task);
++apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
++                             struct h2_request *r, int eos, 
++                             h2_stream_pri_cmp *cmp, void *ctx);
  
--struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more);
++/**
++ * Stream priorities have changed, reschedule pending tasks.
++ * 
++ * @param m the multiplexer
++ * @param cmp the stream priority compare function
++ * @param ctx context data for the compare function
++ */
++apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx);
  
--apr_status_t h2_mplx_create_task(h2_mplx *mplx, struct h2_stream *stream);
++struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more);
  
  /*******************************************************************************
   * Input handling of streams.
@@@ -221,7 -220,7 +240,15 @@@ struct h2_stream *h2_mplx_next_submit(h
  apr_status_t h2_mplx_out_readx(h2_mplx *mplx, int stream_id, 
                                 h2_io_data_cb *cb, void *ctx, 
                                 apr_size_t *plen, int *peos);
++/**
++ * Reads output data into the given brigade. Will never block, but
++ * return APR_EAGAIN until data arrives or the stream is closed.
++ */
++apr_status_t h2_mplx_out_read_to(h2_mplx *mplx, int stream_id, 
++                                 apr_bucket_brigade *bb, 
++                                 apr_size_t *plen, int *peos);
 +
  /**
   * Opens the output for the given stream with the specified response.
   */
index ca9a362cb56612ff56342a2924090ab4b678bb23,ca9a362cb56612ff56342a2924090ab4b678bb23..4a1e19058a627d34d5027f91ed04b0411f7f787f
@@@ -151,13 -151,13 +151,21 @@@ apr_status_t h2_request_write_data(h2_r
  apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m,
                                      h2_task *task, int eos)
  {
++    apr_status_t status;
++    
      if (!req->to_h1) {
--        apr_status_t status = insert_request_line(req, m);
++        status = insert_request_line(req, m);
          if (status != APR_SUCCESS) {
              return status;
          }
      }
--    return h2_to_h1_end_headers(req->to_h1, task, eos);
++    status = h2_to_h1_end_headers(req->to_h1, eos);
++    h2_task_set_request(task, req->to_h1->method, 
++                        req->to_h1->scheme, 
++                        req->to_h1->authority, 
++                        req->to_h1->path, 
++                        req->to_h1->headers, eos);
++    return status;
  }
  
  apr_status_t h2_request_close(h2_request *req)
index 9cedd855615549cf80e1c86d5f58cc13c7b5e34d,9cedd855615549cf80e1c86d5f58cc13c7b5e34d..bd0a5fba9795c345804ffdf95aa4204d30a4a5aa
@@@ -25,6 -25,6 +25,7 @@@
  #include <nghttp2/nghttp2.h>
  
  #include "h2_private.h"
++#include "h2_h2.h"
  #include "h2_util.h"
  #include "h2_response.h"
  
@@@ -41,6 -41,6 +42,7 @@@ static int ignore_header(const char *na
  }
  
  h2_response *h2_response_create(int stream_id,
++                                int rst_error,
                                  const char *http_status,
                                  apr_array_header_t *hlines,
                                  apr_pool_t *pool)
@@@ -53,7 -53,7 +55,8 @@@
      }
      
      response->stream_id = stream_id;
--    response->status = http_status;
++    response->rst_error = rst_error;
++    response->status = http_status? http_status : "500";
      response->content_length = -1;
      
      if (hlines) {
@@@ -112,6 -112,6 +115,19 @@@ h2_response *h2_response_rcreate(int st
      response->status = apr_psprintf(pool, "%d", r->status);
      response->content_length = -1;
      response->rheader = header;
++
++    if (r->status == HTTP_FORBIDDEN) {
++        const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
++        if (cause) {
++            /* This request triggered a TLS renegotiation that is now allowed 
++             * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
++             */
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r->status, r, 
++                          "h2_response(%ld-%d): renegotiate forbidden, cause: %s",
++                          (long)r->connection->id, stream_id, cause);
++            response->rst_error = H2_ERR_HTTP_1_1_REQUIRED;
++        }
++    }
      
      return response;
  }
index 456d2226ed28a9c4748484d54992a13560a5bcb1,456d2226ed28a9c4748484d54992a13560a5bcb1..64d68cc9c2295547d105aff4d8d98e20744286c3
@@@ -26,6 -26,6 +26,7 @@@ typedef struct h2_ngheader 
  
  typedef struct h2_response {
      int stream_id;
++    int rst_error;
      const char *status;
      apr_off_t content_length;
      apr_table_t *rheader;
  } h2_response;
  
  h2_response *h2_response_create(int stream_id,
--                                  const char *http_status,
--                                  apr_array_header_t *hlines,
--                                  apr_pool_t *pool);
++                                int rst_error,
++                                const char *http_status,
++                                apr_array_header_t *hlines,
++                                apr_pool_t *pool);
  
  h2_response *h2_response_rcreate(int stream_id, request_rec *r,
                                   apr_table_t *header, apr_pool_t *pool);
index fe567137de13b17d1e2e6a9b87c8915ae452f76f,c3456a0654da6254ae50e3e3e9787d20238f4c1b..fc0287042bb1b2e1ac015f3f98464166bc938a18
@@@ -24,6 -24,6 +24,8 @@@
  #include <http_log.h>
  
  #include "h2_private.h"
++#include "h2_bucket_eoc.h"
++#include "h2_bucket_eos.h"
  #include "h2_config.h"
  #include "h2_h2.h"
  #include "h2_mplx.h"
@@@ -56,41 -57,41 +58,94 @@@ static int h2_session_status_from_apr_s
  static int stream_open(h2_session *session, int stream_id)
  {
      h2_stream * stream;
++    apr_pool_t *stream_pool;
      if (session->aborted) {
          return NGHTTP2_ERR_CALLBACK_FAILURE;
      }
      
--    stream = h2_mplx_open_io(session->mplx, stream_id);
--    if (stream) {
--        h2_stream_set_add(session->streams, stream);
--        if (stream->id > session->max_stream_received) {
--            session->max_stream_received = stream->id;
--        }
--        
--        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
--                      "h2_session: stream(%ld-%d): opened",
--                      session->id, stream_id);
--        
--        return 0;
++    if (session->spare) {
++        stream_pool = session->spare;
++        session->spare = NULL;
++    }
++    else {
++        apr_pool_create(&stream_pool, session->pool);
++    }
++    
++    stream = h2_stream_create(stream_id, stream_pool, session);
++    stream->state = H2_STREAM_ST_OPEN;
++    
++    h2_stream_set_add(session->streams, stream);
++    if (stream->id > session->max_stream_received) {
++        session->max_stream_received = stream->id;
      }
      
--    ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c,
--                  APLOGNO(02918) 
--                  "h2_session: stream(%ld-%d): unable to create",
++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
++                  "h2_session: stream(%ld-%d): opened",
                    session->id, stream_id);
--    return NGHTTP2_ERR_INVALID_STREAM_ID;
++    
++    return 0;
++}
++
++/**
++ * Determine the importance of streams when scheduling tasks.
++ * - if both stream depend on the same one, compare weights
++ * - if one stream is closer to the root, prioritize that one
++ * - if both are on the same level, use the weight of their root
++ *   level ancestors
++ */
++static int spri_cmp(int sid1, nghttp2_stream *s1, 
++                    int sid2, nghttp2_stream *s2, h2_session *session)
++{
++    nghttp2_stream *p1, *p2;
++    
++    p1 = nghttp2_stream_get_parent(s1);
++    p2 = nghttp2_stream_get_parent(s2);
++    
++    if (p1 == p2) {
++        int32_t w1, w2;
++        
++        w1 = nghttp2_stream_get_weight(s1);
++        w2 = nghttp2_stream_get_weight(s2);
++        return w2 - w1;
++    }
++    else if (!p1) {
++        /* stream 1 closer to root */
++        return -1;
++    }
++    else if (!p2) {
++        /* stream 2 closer to root */
++        return 1;
++    }
++    return spri_cmp(sid1, p1, sid2, p2, session);
++}
++
++static int stream_pri_cmp(int sid1, int sid2, void *ctx)
++{
++    h2_session *session = ctx;
++    nghttp2_stream *s1, *s2;
++    
++    s1 = nghttp2_session_find_stream(session->ngh2, sid1);
++    s2 = nghttp2_session_find_stream(session->ngh2, sid2);
++
++    if (s1 == s2) {
++        return 0;
++    }
++    else if (!s1) {
++        return 1;
++    }
++    else if (!s2) {
++        return -1;
++    }
++    return spri_cmp(sid1, s1, sid2, s2, session);
  }
  
  static apr_status_t stream_end_headers(h2_session *session,
                                         h2_stream *stream, int eos)
  {
      (void)session;
--    return h2_stream_write_eoh(stream, eos);
++    return h2_stream_schedule(stream, eos, stream_pri_cmp, session);
  }
  
--static apr_status_t send_data(h2_session *session, const char *data, 
--                              apr_size_t length);
--
  /*
   * Callback when nghttp2 wants to send bytes back to the client.
   */
@@@ -99,10 -100,10 +154,11 @@@ static ssize_t send_cb(nghttp2_session 
                         int flags, void *userp)
  {
      h2_session *session = (h2_session *)userp;
--    apr_status_t status = send_data(session, (const char *)data, length);
++    apr_status_t status;
      
      (void)ngh2;
      (void)flags;
++    status = h2_conn_io_write(&session->io, (const char *)data, length);
      if (status == APR_SUCCESS) {
          return length;
      }
@@@ -168,7 -169,7 +224,7 @@@ static int on_data_chunk_recv_cb(nghttp
                    session->id, stream_id, (int)len);
      if (status != APR_SUCCESS) {
          rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
-                                        H2_STREAM_RST(stream, NGHTTP2_INTERNAL_ERROR));
 -                                       NGHTTP2_INTERNAL_ERROR);
++                                       H2_STREAM_RST(stream, H2_ERR_INTERNAL_ERROR));
          if (nghttp2_is_fatal(rv)) {
              return NGHTTP2_ERR_CALLBACK_FAILURE;
          }
@@@ -186,6 -187,6 +242,20 @@@ static int before_frame_send_cb(nghttp2
      if (session->aborted) {
          return NGHTTP2_ERR_CALLBACK_FAILURE;
      }
++    /* Set the need to flush output when we have added one of the 
++     * following frame types */
++    switch (frame->hd.type) {
++        case NGHTTP2_RST_STREAM:
++        case NGHTTP2_WINDOW_UPDATE:
++        case NGHTTP2_PUSH_PROMISE:
++        case NGHTTP2_PING:
++        case NGHTTP2_GOAWAY:
++            session->flush = 1;
++            break;
++        default:
++            break;
++
++    }
      if (APLOGctrace2(session->c)) {
          char buffer[256];
          frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
@@@ -201,9 -202,9 +271,14 @@@ static int on_frame_send_cb(nghttp2_ses
                              void *userp)
  {
      h2_session *session = (h2_session *)userp;
--    (void)ngh2; (void)frame;
--    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
--                  "h2_session(%ld): on_frame_send", session->id);
++    (void)ngh2;
++    if (APLOGctrace2(session->c)) {
++        char buffer[256];
++        frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
++                      "h2_session(%ld): on_frame_send %s", 
++                      session->id, buffer);
++    }
      return 0;
  }
  
@@@ -241,12 -242,11 +316,13 @@@ static apr_status_t stream_destroy(h2_s
          ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
                        "h2_stream(%ld-%d): closing with err=%d %s", 
                        session->id, (int)stream->id, (int)error_code,
--                      nghttp2_strerror(error_code));
++                      h2_h2_err_description(error_code));
 +        h2_stream_rst(stream, error_code);
      }
      
--    h2_stream_set_remove(session->streams, stream);
--    return h2_mplx_cleanup_stream(session->mplx, stream);
++    return h2_conn_io_writeb(&session->io,
++                             h2_bucket_eos_create(session->c->bucket_alloc, 
++                                                  stream));
  }
  
  static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
@@@ -387,6 -387,6 +463,7 @@@ static int on_frame_recv_cb(nghttp2_ses
              break;
          }
          case NGHTTP2_PRIORITY: {
++            session->reprioritize = 1;
              ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
                            "h2_session:  stream(%ld-%d): PRIORITY frame "
                            " weight=%d, dependsOn=%d, exclusive=%d", 
      return 0;
  }
  
--static apr_status_t send_data(h2_session *session, const char *data, 
--                              apr_size_t length)
--{
--    return h2_conn_io_write(&session->io, data, length);
--}
--
  static apr_status_t pass_data(void *ctx, 
                                const char *data, apr_size_t length)
  {
--    return send_data((h2_session*)ctx, data, length);
++    return h2_conn_io_write(&((h2_session*)ctx)->io, data, length);
  }
  
++
++static char immortal_zeros[H2_MAX_PADLEN];
++
  static int on_send_data_cb(nghttp2_session *ngh2, 
                             nghttp2_frame *frame, 
                             const uint8_t *framehd, 
          return NGHTTP2_ERR_CALLBACK_FAILURE;
      }
      
--    status = send_data(session, (const char *)framehd, 9);
--    if (status == APR_SUCCESS) {
++    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
++                  "h2_stream(%ld-%d): send_data_cb for %ld bytes",
++                  session->id, (int)stream_id, (long)length);
++                  
++    if (h2_conn_io_is_buffered(&session->io)) {
++        status = h2_conn_io_write(&session->io, (const char *)framehd, 9);
++        if (status == APR_SUCCESS) {
++            if (padlen) {
++                status = h2_conn_io_write(&session->io, (const char *)&padlen, 1);
++            }
++            
++            if (status == APR_SUCCESS) {
++                apr_size_t len = length;
++                status = h2_stream_readx(stream, pass_data, session, 
++                                         &len, &eos);
++                if (status == APR_SUCCESS && len != length) {
++                    status = APR_EINVAL;
++                }
++            }
++            
++            if (status == APR_SUCCESS && padlen) {
++                if (padlen) {
++                    status = h2_conn_io_write(&session->io, immortal_zeros, padlen);
++                }
++            }
++        }
++    }
++    else {
++        apr_bucket *b;
++        char *header = apr_pcalloc(stream->pool, 10);
++        memcpy(header, (const char *)framehd, 9);
          if (padlen) {
--            status = send_data(session, (const char *)&padlen, 1);
++            header[9] = (char)padlen;
          }
--
++        b = apr_bucket_pool_create(header, padlen? 10 : 9, 
++                                   stream->pool, session->c->bucket_alloc);
++        status = h2_conn_io_writeb(&session->io, b);
++        
          if (status == APR_SUCCESS) {
              apr_size_t len = length;
--            status = h2_stream_readx(stream, pass_data, session, 
--                                     &len, &eos);
++            status = h2_stream_read_to(stream, session->io.output, &len, &eos);
++            session->io.unflushed = 1;
              if (status == APR_SUCCESS && len != length) {
                  status = APR_EINVAL;
              }
          }
--        
++            
          if (status == APR_SUCCESS && padlen) {
--            if (padlen) {
--                char pad[256];
--                memset(pad, 0, padlen);
--                status = send_data(session, pad, padlen);
--            }
++            b = apr_bucket_immortal_create(immortal_zeros, padlen, 
++                                           session->c->bucket_alloc);
++            status = h2_conn_io_writeb(&session->io, b);
          }
      }
      
++    
      if (status == APR_SUCCESS) {
++        stream->data_frames_sent++;
++        h2_conn_io_consider_flush(&session->io);
          return 0;
      }
 -    else if (status != APR_EOF) {
 -        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
 +    else {
 +        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
                        APLOGNO(02925) 
                        "h2_stream(%ld-%d): failed send_data_cb",
                        session->id, (int)stream_id);
      return h2_session_status_from_apr_status(status);
  }
  
++static ssize_t on_data_source_read_length_cb(nghttp2_session *session, 
++                                             uint8_t frame_type, int32_t stream_id, 
++                                             int32_t session_remote_window_size, 
++                                             int32_t stream_remote_window_size, 
++                                             uint32_t remote_max_frame_size, 
++                                             void *user_data)
++{
++    /* DATA frames add 9 bytes header plus 1 byte for padlen and additional 
++     * padlen bytes. Keep below TLS maximum record size.
++     * TODO: respect pad bytes when we have that feature.
++     */
++    return (16*1024 - 10);
++}
  
  #define NGH2_SET_CALLBACK(callbacks, name, fn)\
  nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
@@@ -543,6 -544,6 +663,7 @@@ static apr_status_t init_callbacks(conn
      NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
      NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
      NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
++    NGH2_SET_CALLBACK(*pcb, data_source_read_length, on_data_source_read_length_cb);
      
      return APR_SUCCESS;
  }
@@@ -660,13 -661,13 +781,11 @@@ void h2_session_destroy(h2_session *ses
          nghttp2_session_del(session->ngh2);
          session->ngh2 = NULL;
      }
--    h2_conn_io_destroy(&session->io);
--    
--    if (session->iowait) {
--        apr_thread_cond_destroy(session->iowait);
--        session->iowait = NULL;
--    }
--    
++}
++
++void h2_session_cleanup(h2_session *session)
++{
++    h2_session_destroy(session);
      if (session->pool) {
          apr_pool_destroy(session->pool);
      }
@@@ -677,14 -678,14 +796,18 @@@ static apr_status_t h2_session_abort_in
      AP_DEBUG_ASSERT(session);
      if (!session->aborted) {
          session->aborted = 1;
--        if (session->ngh2) {
--            
--            if (!reason) {
++        
++        if (session->ngh2) {            
++            if (NGHTTP2_ERR_EOF == reason) {
++                /* This is our way of indication that the connection is
++                 * gone. No use to send any GOAWAY frames. */
++                nghttp2_session_terminate_session(session->ngh2, reason);
++            }
++            else if (!reason) {
                  nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
                                        session->max_stream_received, 
                                        reason, NULL, 0);
                  nghttp2_session_send(session->ngh2);
--                h2_conn_io_flush(&session->io);
              }
              else {
                  const char *err = nghttp2_strerror(reason);
                                "session(%ld): aborting session, reason=%d %s",
                                session->id, reason, err);
                  
--                if (NGHTTP2_ERR_EOF == reason) {
--                    /* This is our way of indication that the connection is
--                     * gone. No use to send any GOAWAY frames. */
--                    nghttp2_session_terminate_session(session->ngh2, reason);
--                }
--                else {
--                    /* The connection might still be there and we shut down
--                     * with GOAWAY and reason information. */
--                     nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
--                                           session->max_stream_received, 
--                                           reason, (const uint8_t *)err, 
--                                           strlen(err));
--                     nghttp2_session_send(session->ngh2);
--                     h2_conn_io_flush(&session->io);
--                }
++                /* The connection might still be there and we shut down
++                 * with GOAWAY and reason information. */
++                nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
++                                      session->max_stream_received, 
++                                      reason, (const uint8_t *)err, 
++                                      strlen(err));
++                nghttp2_session_send(session->ngh2);
              }
++            h2_conn_io_flush(&session->io);
          }
          h2_mplx_abort(session->mplx);
      }
@@@ -854,7 -853,7 +970,7 @@@ static int resume_on_data(void *ctx, h2
      AP_DEBUG_ASSERT(stream);
      
      if (h2_stream_is_suspended(stream)) {
--        if (h2_mplx_out_has_data_for(stream->m, stream->id)) {
++        if (h2_mplx_out_has_data_for(stream->session->mplx, stream->id)) {
              int rv;
              h2_stream_set_suspended(stream, 0);
              ++rctx->resume_count;
@@@ -893,6 -892,6 +1009,12 @@@ static void update_window(void *ctx, in
      nghttp2_session_consume(session->ngh2, stream_id, bytes_read);
  }
  
++static apr_status_t h2_session_flush(h2_session *session) 
++{
++    session->flush = 0;
++    return h2_conn_io_flush(&session->io);
++}
++
  static apr_status_t h2_session_update_windows(h2_session *session)
  {
      return h2_mplx_in_update_windows(session->mplx, update_window, session);
@@@ -902,16 -901,16 +1024,17 @@@ apr_status_t h2_session_write(h2_sessio
  {
      apr_status_t status = APR_EAGAIN;
      h2_stream *stream = NULL;
--    int flush_output = 0;
      
      AP_DEBUG_ASSERT(session);
      
++    if (session->reprioritize) {
++        h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session);
++        session->reprioritize = 0;
++    }
++    
      /* Check that any pending window updates are sent. */
      status = h2_session_update_windows(session);
--    if (status == APR_SUCCESS) {
--        flush_output = 1;
--    }
-     else if (!APR_STATUS_IS_EAGAIN(status)) {
 -    else if (status != APR_EAGAIN) {
++    if (status != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(status)) {
          return status;
      }
      
                  status = APR_ECONNABORTED;
              }
          }
--        flush_output = 1;
      }
      
      /* If we have responses ready, submit them now. */
 -    while ((stream = h2_mplx_next_submit(session->mplx, 
 -                                         session->streams)) != NULL) {
 +    while (!session->aborted 
 +           && (stream = h2_mplx_next_submit(session->mplx, session->streams)) != NULL) {
          status = h2_session_handle_response(session, stream);
--        flush_output = 1;
      }
      
 -    if (h2_session_resume_streams_with_data(session) > 0) {
 -        flush_output = 1;
 +    if (!session->aborted && h2_session_resume_streams_with_data(session) > 0) {
-         flush_output = 1;
      }
      
-     if (!session->aborted && !flush_output 
-         && timeout > 0 && !h2_session_want_write(session)) {
 -    if (!flush_output && timeout > 0 && !h2_session_want_write(session)) {
++    if (!session->aborted && !session->flush && timeout > 0 
++        && !h2_session_want_write(session)) {
++        h2_session_flush(session);
          status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait);
  
          if (status != APR_TIMEUP
              && h2_session_resume_streams_with_data(session) > 0) {
--            flush_output = 1;
          }
          else {
              /* nothing happened to ongoing streams, do some house-keeping */
                  status = APR_ECONNABORTED;
              }
          }
--        flush_output = 1;
      }
      
--    if (flush_output) {
--        h2_conn_io_flush(&session->io);
++    if (session->flush) {
++        h2_session_flush(session);
      }
      
      return status;
@@@ -1015,17 -1013,17 +1134,28 @@@ static apr_status_t session_receive(con
  apr_status_t h2_session_read(h2_session *session, apr_read_type_e block)
  {
      AP_DEBUG_ASSERT(session);
++    if (block == APR_BLOCK_READ) {
++        /* before we do a blocking read, make sure that all our output
++         * is send out. Otherwise we might deadlock. */
++        h2_session_flush(session);
++    }
      return h2_conn_io_read(&session->io, block, session_receive, session);
  }
  
  apr_status_t h2_session_close(h2_session *session)
  {
      AP_DEBUG_ASSERT(session);
--    return session->aborted? APR_SUCCESS : h2_conn_io_flush(&session->io);
++    if (!session->aborted) {
++        h2_session_abort_int(session, 0);
++    }
++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0,session->c,
++                  "h2_session: closing, writing eoc");
++    h2_conn_io_writeb(&session->io,
++                      h2_bucket_eoc_create(session->c->bucket_alloc, 
++                                           session));
++    return h2_conn_io_flush(&session->io);
  }
  
--/* The session wants to send more DATA for the given stream.
-- */
  static ssize_t stream_data_cb(nghttp2_session *ng2s,
                                int32_t stream_id,
                                uint8_t *buf,
      h2_stream *stream;
      AP_DEBUG_ASSERT(session);
      
++    /* The session wants to send more DATA for the stream. We need
++     * to find out how much of the requested length we can send without
++     * blocking.
++     * Indicate EOS when we encounter it or DEFERRED if the stream
++     * should be suspended.
++     * TODO: for handling of TRAILERS,  the EOF indication needs
++     * to be aware of that.
++     */
++ 
      (void)ng2s;
      (void)buf;
      (void)source;
      stream = h2_stream_set_get(session->streams, stream_id);
      if (!stream) {
--        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
++        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
                        APLOGNO(02937) 
                        "h2_stream(%ld-%d): data requested but stream not found",
                        session->id, (int)stream_id);
@@@ -1154,8 -1148,7 +1293,8 @@@ apr_status_t h2_session_handle_response
      }
      else {
          rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
 -                                       stream->id, NGHTTP2_PROTOCOL_ERROR);
 +                                       stream->id, 
-                                        H2_STREAM_RST(stream, NGHTTP2_PROTOCOL_ERROR));
++                                       H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR));
      }
      
      if (nghttp2_is_fatal(rv)) {
      return status;
  }
  
++apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream)
++{
++    apr_pool_t *pool = h2_stream_detach_pool(stream);
++
++    h2_mplx_stream_done(session->mplx, stream->id, stream->rst_error);
++    h2_stream_set_remove(session->streams, stream->id);
++    h2_stream_destroy(stream);
++    
++    if (pool) {
++        apr_pool_clear(pool);
++        if (session->spare) {
++            apr_pool_destroy(session->spare);
++        }
++        session->spare = pool;
++    }
++    return APR_SUCCESS;
++}
++
  int h2_session_is_done(h2_session *session)
  {
      AP_DEBUG_ASSERT(session);
index ddf29a06df1316910655df13b1ab6a958886d6b2,12768a90fb374e6e157a112fcf4889be98b650bb..a1d718a9ca6b8c42e5c1cefd37e480a77e5a47a5
@@@ -58,6 -58,6 +58,9 @@@ struct h2_session 
      request_rec *r;                 /* the request that started this in case
                                       * of 'h2c', NULL otherwise */
      int aborted;                    /* this session is being aborted */
++    int flush;                      /* if != 0, flush output on next occasion */
++    int reprioritize;               /* scheduled streams priority needs to 
++                                     * be re-evaluated */
      apr_size_t frames_received;     /* number of http/2 frames received */
      apr_size_t max_stream_count;    /* max number of open streams */
      apr_size_t max_stream_mem;      /* max buffer memory for a single stream */
@@@ -74,6 -74,6 +77,8 @@@
      int max_stream_received;        /* highest stream id created */
      int max_stream_handled;         /* highest stream id handled successfully */
      
++    apr_pool_t *spare;              /* spare stream pool */
++    
      struct nghttp2_session *ngh2;   /* the nghttp2 session (internal use) */
      struct h2_workers *workers;     /* for executing stream tasks */
  };
@@@ -108,6 -108,6 +113,13 @@@ h2_session *h2_session_rcreate(request_
   */
  void h2_session_destroy(h2_session *session);
  
++/**
++ * Cleanup the session and all objects it still contains. This will not
++ * destroy h2_task instances that have not finished yet. 
++ * @param session the session to destroy
++ */
++void h2_session_cleanup(h2_session *session);
++
  /**
   * Called once at start of session. 
   * Sets up the session and sends the initial SETTINGS frame.
@@@ -147,8 -147,8 +159,7 @@@ apr_status_t h2_session_read(h2_sessio
   * a maximum of timeout micro-seconds and return to the caller. If timeout
   * occurred, APR_TIMEUP will be returned.
   */
--apr_status_t h2_session_write(h2_session *session,
--                              apr_interval_time_t timeout);
++apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout);
  
  /* Start submitting the response to a stream request. This is possible
   * once we have all the response headers. */
@@@ -158,4 -158,6 +169,12 @@@ apr_status_t h2_session_handle_response
  /* Get the h2_stream for the given stream idenrtifier. */
  struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id);
  
 -void h2_session_log_stats(h2_session *session);
++/**
++ * Destroy the stream and release it everywhere. Reclaim all resources.
++ * @param session the session to which the stream belongs
++ * @param stream the stream to destroy
++ */
++apr_status_t h2_session_stream_destroy(h2_session *session, 
++                                       struct h2_stream *stream);
  #endif /* defined(__mod_h2__h2_session__) */
index d5a716f4490b381125a856d3563e6ab13aab4fa5,52781d8474fec50392e95dbf3417dc5eaf722cf1..5a5af6372b7dd1849e4ac50b45bc9a75d2b1d0f4
@@@ -16,8 -16,8 +16,6 @@@
  #include <assert.h>
  #include <stddef.h>
  
--#define APR_POOL_DEBUG  7
--
  #include <httpd.h>
  #include <http_core.h>
  #include <http_connection.h>
@@@ -30,6 -30,6 +28,7 @@@
  #include "h2_mplx.h"
  #include "h2_request.h"
  #include "h2_response.h"
++#include "h2_session.h"
  #include "h2_stream.h"
  #include "h2_task.h"
  #include "h2_ctx.h"
@@@ -46,46 -46,51 +45,47 @@@ static void set_state(h2_stream *stream
      }
  }
  
--h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m)
++h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session)
  {
      h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
      if (stream != NULL) {
          stream->id = id;
          stream->state = H2_STREAM_ST_IDLE;
          stream->pool = pool;
--        stream->m = m;
--        stream->request = h2_request_create(id, pool, m->c->bucket_alloc);
--        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
--                      "h2_stream(%ld-%d): created", m->id, stream->id);
++        stream->session = session;
++        stream->bbout = apr_brigade_create(stream->pool, 
++                                           stream->session->c->bucket_alloc);
++        stream->request = h2_request_create(id, pool, session->c->bucket_alloc);
++        
++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
++                      "h2_stream(%ld-%d): created", session->id, stream->id);
      }
      return stream;
  }
  
--static void h2_stream_cleanup(h2_stream *stream)
++apr_status_t h2_stream_destroy(h2_stream *stream)
  {
++    AP_DEBUG_ASSERT(stream);
++    
++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
++                  "h2_stream(%ld-%d): destroy", stream->session->id, stream->id);
      if (stream->request) {
          h2_request_destroy(stream->request);
          stream->request = NULL;
      }
--}
--
--apr_status_t h2_stream_destroy(h2_stream *stream)
--{
--    AP_DEBUG_ASSERT(stream);
--    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c,
--                  "h2_stream(%ld-%d): destroy", stream->m->id, stream->id);
--    h2_stream_cleanup(stream);
      
--    if (stream->task) {
--        h2_task_destroy(stream->task);
--        stream->task = NULL;
--    }
      if (stream->pool) {
          apr_pool_destroy(stream->pool);
      }
      return APR_SUCCESS;
  }
  
 -void h2_stream_attach_pool(h2_stream *stream, apr_pool_t *pool)
++void h2_stream_cleanup(h2_stream *stream)
+ {
 -    stream->pool = pool;
++    h2_session_stream_destroy(stream->session, stream);
++    /* stream is gone */
+ }
  apr_pool_t *h2_stream_detach_pool(h2_stream *stream)
  {
      apr_pool_t *pool = stream->pool;
      return pool;
  }
  
 -void h2_stream_abort(h2_stream *stream)
 +void h2_stream_rst(h2_stream *stream, int error_code)
  {
 -    AP_DEBUG_ASSERT(stream);
 -    stream->aborted = 1;
 +    stream->rst_error = error_code;
-     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c,
++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
 +                  "h2_stream(%ld-%d): reset, error=%d", 
-                   stream->m->id, stream->id, error_code);
++                  stream->session->id, stream->id, error_code);
  }
  
  apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response,
                                      apr_bucket_brigade *bb)
  {
 +    apr_status_t status = APR_SUCCESS;
 +    
      stream->response = response;
      if (bb && !APR_BRIGADE_EMPTY(bb)) {
--        if (!stream->bbout) {
--            stream->bbout = apr_brigade_create(stream->pool, 
--                                               stream->m->c->bucket_alloc);
--        }
-         status = h2_util_move(stream->bbout, bb, 16 * 1024, NULL,  
 -        return h2_util_move(stream->bbout, bb, 16 * 1024, NULL,  
 -                            "h2_stream_set_response");
++        int move_all = INT_MAX;
++        /* we can move file handles from h2_mplx into this h2_stream as many
++         * as we want, since the lifetimes are the same and we are not freeing
++         * the ones in h2_mplx->io before this stream is done. */
++        status = h2_util_move(stream->bbout, bb, 16 * 1024, &move_all,  
 +                              "h2_stream_set_response");
      }
-     if (APLOGctrace1(stream->m->c)) {
 -    return APR_SUCCESS;
++    if (APLOGctrace1(stream->session->c)) {
 +        apr_size_t len = 0;
 +        int eos = 0;
-         if (stream->bbout) {
-             h2_util_bb_avail(stream->bbout, &len, &eos);
-         }
-         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->m->c,
-                       "h2_stream(%ld-%d): set_response(%s), brigade=%s, "
-                       "len=%ld, eos=%d", 
-                       stream->m->id, stream->id, response->status,
-                       (stream->bbout? "yes" : "no"), (long)len, (int)eos);
++        h2_util_bb_avail(stream->bbout, &len, &eos);
++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
++                      "h2_stream(%ld-%d): set_response(%s), len=%ld, eos=%d", 
++                      stream->session->id, stream->id, response->status,
++                      (long)len, (int)eos);
 +    }
 +    return status;
  }
  
  static int set_closed(h2_stream *stream) 
@@@ -152,15 -141,12 +149,16 @@@ apr_status_t h2_stream_rwrite(h2_strea
  {
      apr_status_t status;
      AP_DEBUG_ASSERT(stream);
 +    if (stream->rst_error) {
 +        return APR_ECONNRESET;
 +    }
      set_state(stream, H2_STREAM_ST_OPEN);
--    status = h2_request_rwrite(stream->request, r, stream->m);
++    status = h2_request_rwrite(stream->request, r, stream->session->mplx);
      return status;
  }
  
--apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos)
++apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
++                                h2_stream_pri_cmp *cmp, void *ctx)
  {
      apr_status_t status;
      AP_DEBUG_ASSERT(stream);
      /* Seeing the end-of-headers, we have everything we need to 
       * start processing it.
       */
--    status = h2_mplx_create_task(stream->m, stream);
--    if (status == APR_SUCCESS) {
--        status = h2_request_end_headers(stream->request, 
--                                        stream->m, stream->task, eos);
--        if (status == APR_SUCCESS) {
--            status = h2_mplx_do_task(stream->m, stream->task);
--        }
--        if (eos) {
--            status = h2_stream_write_eos(stream);
--        }
--        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->m->c,
--                      "h2_mplx(%ld-%d): start stream, task %s %s (%s)",
--                      stream->m->id, stream->id,
--                      stream->request->method, stream->request->path,
--                      stream->request->authority);
--        
++    status = h2_mplx_process(stream->session->mplx, stream->id, 
++                             stream->request, eos, cmp, ctx);
++    if (eos) {
++        set_closed(stream);
      }
++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
++                  "h2_mplx(%ld-%d): start stream, task %s %s (%s)",
++                  stream->session->id, stream->id,
++                  stream->request->method, stream->request->path,
++                  stream->request->authority);
++    
      return status;
  }
  
  apr_status_t h2_stream_write_eos(h2_stream *stream)
  {
      AP_DEBUG_ASSERT(stream);
--    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c,
++    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
                    "h2_stream(%ld-%d): closing input",
--                  stream->m->id, stream->id);
++                  stream->session->id, stream->id);
 +    if (stream->rst_error) {
 +        return APR_ECONNRESET;
 +    }
      if (set_closed(stream)) {
          return h2_request_close(stream->request);
      }
@@@ -224,7 -201,7 +216,7 @@@ apr_status_t h2_stream_write_header(h2_
              return APR_EINVAL;
      }
      return h2_request_write_header(stream->request, name, nlen,
--                                   value, vlen, stream->m);
++                                   value, vlen, stream->session->mplx);
  }
  
  apr_status_t h2_stream_write_data(h2_stream *stream,
@@@ -249,10 -224,7 +241,11 @@@ apr_status_t h2_stream_prep_read(h2_str
      apr_status_t status = APR_SUCCESS;
      const char *src;
      
 -    if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
 +    if (stream->rst_error) {
 +        return APR_ECONNRESET;
 +    }
-     if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
++
++    if (!APR_BRIGADE_EMPTY(stream->bbout)) {
          src = "stream";
          status = h2_util_bb_avail(stream->bbout, plen, peos);
          if (status == APR_SUCCESS && !*peos && !*plen) {
      }
      else {
          src = "mplx";
--        status = h2_mplx_out_readx(stream->m, stream->id, 
++        status = h2_mplx_out_readx(stream->session->mplx, stream->id, 
                                     NULL, NULL, plen, peos);
      }
      if (status == APR_SUCCESS && !*peos && !*plen) {
          status = APR_EAGAIN;
      }
--    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->m->c,
++    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
                    "h2_stream(%ld-%d): prep_read %s, len=%ld eos=%d",
--                  stream->m->id, stream->id, 
--                  src, (long)*plen, *peos);
++                  stream->session->id, stream->id, src, (long)*plen, *peos);
      return status;
  }
  
@@@ -279,16 -251,13 +271,73 @@@ apr_status_t h2_stream_readx(h2_stream 
                               h2_io_data_cb *cb, void *ctx,
                               apr_size_t *plen, int *peos)
  {
 -    if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
 -        return h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos);
++    apr_status_t status = APR_SUCCESS;
++    const char *src;
++    
 +    if (stream->rst_error) {
 +        return APR_ECONNRESET;
 +    }
-     if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
-         return h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos);
++    *peos = 0;
++    if (!APR_BRIGADE_EMPTY(stream->bbout)) {
++        apr_size_t origlen = *plen;
++        
++        src = "stream";
++        status = h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos);
++        if (status == APR_SUCCESS && !*peos && !*plen) {
++            apr_brigade_cleanup(stream->bbout);
++            *plen = origlen;
++            return h2_stream_readx(stream, cb, ctx, plen, peos);
++        }
++    }
++    else {
++        src = "mplx";
++        status = h2_mplx_out_readx(stream->session->mplx, stream->id, 
++                                   cb, ctx, plen, peos);
++    }
++    
++    if (status == APR_SUCCESS && !*peos && !*plen) {
++        status = APR_EAGAIN;
      }
--    return h2_mplx_out_readx(stream->m, stream->id, 
--                             cb, ctx, plen, peos);
++    
++    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
++                  "h2_stream(%ld-%d): readx %s, len=%ld eos=%d",
++                  stream->session->id, stream->id, src, (long)*plen, *peos);
++    return status;
  }
  
++apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, 
++                               apr_size_t *plen, int *peos)
++{
++    apr_status_t status = APR_SUCCESS;
++
++    if (stream->rst_error) {
++        return APR_ECONNRESET;
++    }
++    
++    if (APR_BRIGADE_EMPTY(stream->bbout)) {
++        apr_size_t tlen = *plen;
++        int eos;
++        status = h2_mplx_out_read_to(stream->session->mplx, stream->id, 
++                                     stream->bbout, &tlen, &eos);
++    }
++    
++    if (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(stream->bbout)) {
++        status = h2_transfer_brigade(bb, stream->bbout, stream->pool, 
++                                     plen, peos);
++    }
++    else {
++        *plen = 0;
++        *peos = 0;
++    }
++
++    if (status == APR_SUCCESS && !*peos && !*plen) {
++        status = APR_EAGAIN;
++    }
++    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
++                  "h2_stream(%ld-%d): read_to, len=%ld eos=%d",
++                  stream->session->id, stream->id, (long)*plen, *peos);
++    return status;
++}
  
  void h2_stream_set_suspended(h2_stream *stream, int suspended)
  {
index f885bc439f901a927f68a6b7ca451304eab509f5,0608f2f34006fcfeb28f2624637512b28ce523b4..d09d848a66a3c1b650e16781c418b8dd6c156e6d
@@@ -48,6 -48,6 +48,7 @@@ typedef enum 
  struct h2_mplx;
  struct h2_request;
  struct h2_response;
++struct h2_session;
  struct h2_task;
  
  typedef struct h2_stream h2_stream;
  struct h2_stream {
      int id;                     /* http2 stream id */
      h2_stream_state_t state;    /* http/2 state of this stream */
--    struct h2_mplx *m;          /* the multiplexer to work with */
++    struct h2_session *session; /* the session this stream belongs to */
      
      int aborted;                /* was aborted */
      int suspended;              /* DATA sending has been suspended */
++    int rst_error;              /* stream error for RST_STREAM */
      
      apr_pool_t *pool;           /* the memory pool for this stream */
      struct h2_request *request; /* the request made in this stream */
      
--    struct h2_task *task;       /* task created for this stream */
      struct h2_response *response; /* the response, once ready */
++    
      apr_bucket_brigade *bbout;  /* output DATA */
-     int rst_error;              /* stream error for RST_STREAM */
++    apr_size_t data_frames_sent;/* # of DATA frames sent out for this stream */
  };
  
  
 -h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m);
 +#define H2_STREAM_RST(s, def)    (s->rst_error? s->rst_error : (def))
 +
- h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m);
++h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_session *session);
  
  apr_status_t h2_stream_destroy(h2_stream *stream);
  
 -apr_pool_t *h2_stream_detach_pool(h2_stream *stream);
 -void h2_stream_attach_pool(h2_stream *stream, apr_pool_t *pool);
++void h2_stream_cleanup(h2_stream *stream);
 -void h2_stream_abort(h2_stream *stream);
 +void h2_stream_rst(h2_stream *streamm, int error_code);
 +
 +apr_pool_t *h2_stream_detach_pool(h2_stream *stream);
  
  apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r);
  
@@@ -88,7 -86,7 +92,8 @@@ apr_status_t h2_stream_write_header(h2_
                                      const char *name, size_t nlen,
                                      const char *value, size_t vlen);
  
--apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos);
++apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
++                                h2_stream_pri_cmp *cmp, void *ctx);
  
  apr_status_t h2_stream_write_data(h2_stream *stream,
                                    const char *data, size_t len);
@@@ -103,6 -101,6 +108,10 @@@ apr_status_t h2_stream_prep_read(h2_str
  apr_status_t h2_stream_readx(h2_stream *stream, h2_io_data_cb *cb, 
                               void *ctx, apr_size_t *plen, int *peos);
  
++apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, 
++                               apr_size_t *plen, int *peos);
++
++
  void h2_stream_set_suspended(h2_stream *stream, int suspended);
  int h2_stream_is_suspended(h2_stream *stream);
  
index dddd2e3990820e0ff6ea4c56181172ad1f651ec2,dddd2e3990820e0ff6ea4c56181172ad1f651ec2..5ef48a13e17d384a40f1f33844da0c1ed7a7ee67
@@@ -54,9 -54,9 +54,7 @@@ void h2_stream_set_destroy(h2_stream_se
  
  static int h2_stream_id_cmp(const void *s1, const void *s2)
  {
--    h2_stream **pstream1 = (h2_stream **)s1;
--    h2_stream **pstream2 = (h2_stream **)s2;
--    return (*pstream1)->id - (*pstream2)->id;
++    return (*((h2_stream **)s1))->id - (*((h2_stream **)s2))->id;
  }
  
  h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id)
@@@ -101,12 -101,12 +99,12 @@@ apr_status_t h2_stream_set_add(h2_strea
      return APR_SUCCESS;
  }
  
--h2_stream *h2_stream_set_remove(h2_stream_set *sp, h2_stream *stream)
++h2_stream *h2_stream_set_remove(h2_stream_set *sp, int stream_id)
  {
      int i;
      for (i = 0; i < sp->list->nelts; ++i) {
          h2_stream *s = H2_STREAM_IDX(sp->list, i);
--        if (s == stream) {
++        if (s->id == stream_id) {
              int n;
              --sp->list->nelts;
              n = sp->list->nelts - i;
index 56075834555ab2674a7ca497235275d2c796f4cd,56075834555ab2674a7ca497235275d2c796f4cd..8bc6409223b186b09b0e1121039c58e9e08e17c1
@@@ -35,7 -35,7 +35,7 @@@ apr_status_t h2_stream_set_add(h2_strea
  
  h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id);
  
--h2_stream *h2_stream_set_remove(h2_stream_set *sp,h2_stream *stream);
++h2_stream *h2_stream_set_remove(h2_stream_set *sp, int stream_id);
  
  void h2_stream_set_remove_all(h2_stream_set *sp);
  
index 23c34490eaa27c4414ee30a9e3111c30df9f9f55,23c34490eaa27c4414ee30a9e3111c30df9f9f55..97f07f891d71eeb1381a5517fbd004034f981f3a
  #include "h2_h2.h"
  #include "h2_switch.h"
  
--/*******************************************************************************
-- * SSL var lookup
-- */
--APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
--                        (apr_pool_t *, server_rec *,
--                         conn_rec *, request_rec *,
--                         char *));
--static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *,
--                                   conn_rec *, request_rec *,
--                                   char *);
--
  /*******************************************************************************
   * Once per lifetime init, retrieve optional functions
   */
@@@ -52,7 -52,7 +41,6 @@@ apr_status_t h2_switch_init(apr_pool_t 
  {
      (void)pool;
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_switch init");
--    opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
  
      return APR_SUCCESS;
  }
@@@ -63,7 -63,7 +51,8 @@@ static int h2_protocol_propose(conn_re
                                 apr_array_header_t *proposals)
  {
      int proposed = 0;
--    const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
++    int is_tls = h2_h2_is_tls(c);
++    const char **protos = is_tls? h2_tls_protos : h2_clear_protos;
      
      (void)s;
      if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
          return DECLINED;
      }
      
++    if (!h2_is_acceptable_connection(c, 0)) {
++        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++                      "protocol propose: connection requirements not met");
++        return DECLINED;
++    }
++    
      if (r) {
--        const char *p;
          /* So far, this indicates an HTTP/1 Upgrade header initiated
           * protocol switch. For that, the HTTP2-Settings header needs
           * to be present and valid for the connection.
           */
++        const char *p;
++        
++        if (!h2_allows_h2_upgrade(c)) {
++            return DECLINED;
++        }
++         
          p = apr_table_get(r->headers_in, "HTTP2-Settings");
          if (!p) {
              ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
index 58b39c53970cccbdd388e242e291667d406959eb,bbea7b20f8aa1ae48309e38cd9a04368b5d703ea..ee78f4347e1b3a5302bbbe2441151405cc51a6f6
@@@ -52,35 -52,35 +52,37 @@@ static apr_status_t h2_filter_stream_in
                                             ap_input_mode_t mode,
                                             apr_read_type_e block,
                                             apr_off_t readbytes) {
--    h2_task_env *env = filter->ctx;
--    AP_DEBUG_ASSERT(env);
--    if (!env->input) {
++    h2_task *task = filter->ctx;
++    AP_DEBUG_ASSERT(task);
++    if (!task->input) {
          return APR_ECONNABORTED;
      }
--    return h2_task_input_read(env->input, filter, brigade,
++    return h2_task_input_read(task->input, filter, brigade,
                                mode, block, readbytes);
  }
  
  static apr_status_t h2_filter_stream_output(ap_filter_t* filter,
                                              apr_bucket_brigade* brigade) {
--    h2_task_env *env = filter->ctx;
--    AP_DEBUG_ASSERT(env);
--    if (!env->output) {
++    h2_task *task = filter->ctx;
++    AP_DEBUG_ASSERT(task);
++    if (!task->output) {
          return APR_ECONNABORTED;
      }
--    return h2_task_output_write(env->output, filter, brigade);
++    return h2_task_output_write(task->output, filter, brigade);
  }
  
  static apr_status_t h2_filter_read_response(ap_filter_t* f,
                                              apr_bucket_brigade* bb) {
--    h2_task_env *env = f->ctx;
--    AP_DEBUG_ASSERT(env);
--    if (!env->output || !env->output->from_h1) {
++    h2_task *task = f->ctx;
++    AP_DEBUG_ASSERT(task);
++    if (!task->output || !task->output->from_h1) {
          return APR_ECONNABORTED;
      }
--    return h2_from_h1_read_response(env->output->from_h1, f, bb);
++    return h2_from_h1_read_response(task->output->from_h1, f, bb);
  }
  
++static apr_status_t h2_task_process_request(h2_task *task);
++
  /*******************************************************************************
   * Register various hooks
   */
@@@ -119,21 -119,21 +121,15 @@@ static int h2_task_pre_conn(conn_rec* c
      
      (void)arg;
      if (h2_ctx_is_task(ctx)) {
--        h2_task_env *env = h2_ctx_get_task(ctx);
--        
--        /* This connection is a pseudo-connection used for a h2_task.
--         * Since we read/write directly from it ourselves, we need
--         * to disable a possible ssl connection filter.
--         */
--        h2_tls_disable(c);
++        h2_task *task = h2_ctx_get_task(ctx);
          
          ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
                        "h2_h2, pre_connection, found stream task");
          
          /* Add our own, network level in- and output filters.
           */
--        ap_add_input_filter("H2_TO_H1", env, NULL, c);
--        ap_add_output_filter("H1_TO_H2", env, NULL, c);
++        ap_add_input_filter("H2_TO_H1", task, NULL, c);
++        ap_add_output_filter("H1_TO_H2", task, NULL, c);
      }
      return OK;
  }
@@@ -143,14 -143,14 +139,14 @@@ static int h2_task_process_conn(conn_re
      h2_ctx *ctx = h2_ctx_get(c);
      
      if (h2_ctx_is_task(ctx)) {
--        if (!ctx->task_env->serialize_headers) {
++        if (!ctx->task->serialize_headers) {
              ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, 
                            "h2_h2, processing request directly");
--            h2_task_process_request(ctx->task_env);
++            h2_task_process_request(ctx->task);
              return DONE;
          }
          ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, 
--                      "h2_task(%s), serialized handling", ctx->task_env->id);
++                      "h2_task(%s), serialized handling", ctx->task->id);
      }
      return DECLINED;
  }
@@@ -170,8 -170,8 +166,6 @@@ h2_task *h2_task_create(long session_id
          return NULL;
      }
      
--    APR_RING_ELEM_INIT(task, link);
--
      task->id = apr_psprintf(stream_pool, "%ld-%d", session_id, stream_id);
      task->stream_id = stream_id;
      task->mplx = mplx;
@@@ -208,121 -208,121 +202,73 @@@ apr_status_t h2_task_do(h2_task *task, 
  {
      apr_status_t status = APR_SUCCESS;
      h2_config *cfg = h2_config_get(task->mplx->c);
--    h2_task_env env; 
      
      AP_DEBUG_ASSERT(task);
      
--    memset(&env, 0, sizeof(env));
--    
--    env.id = task->id;
--    env.stream_id = task->stream_id;
--    env.mplx = task->mplx;
--    task->mplx = NULL;
--    
--    env.input_eos = task->input_eos;
--    env.serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS);
++    task->serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS);
      
      /* Create a subpool from the worker one to be used for all things
--     * with life-time of this task_env execution.
++     * with life-time of this task execution.
       */
--    apr_pool_create(&env.pool, h2_worker_get_pool(worker));
++    apr_pool_create(&task->pool, h2_worker_get_pool(worker));
  
--    /* Link the env to the worker which provides useful things such
++    /* Link the task to the worker which provides useful things such
       * as mutex, a socket etc. */
--    env.io = h2_worker_get_cond(worker);
++    task->io = h2_worker_get_cond(worker);
      
--    /* Clone fields, so that lifetimes become (more) independent. */
--    env.method    = apr_pstrdup(env.pool, task->method);
--    env.scheme    = apr_pstrdup(env.pool, task->scheme);
--    env.authority = apr_pstrdup(env.pool, task->authority);
--    env.path      = apr_pstrdup(env.pool, task->path);
--    env.headers   = apr_table_clone(env.pool, task->headers);
--    
--    /* Setup the pseudo connection to use our own pool and bucket_alloc */
--    env.c = *task->c;
--    task->c = NULL;
--    status = h2_conn_setup(&env, worker);
++    status = h2_conn_setup(task, worker);
      
      /* save in connection that this one is a pseudo connection, prevents
       * other hooks from messing with it. */
--    h2_ctx_create_for(&env.c, &env);
++    h2_ctx_create_for(task->c, task);
  
      if (status == APR_SUCCESS) {
--        env.input = h2_task_input_create(&env, env.pool, 
--                                         env.c.bucket_alloc);
--        env.output = h2_task_output_create(&env, env.pool,
--                                           env.c.bucket_alloc);
--        status = h2_conn_process(&env.c, h2_worker_get_socket(worker));
--        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, &env.c,
--                      "h2_task(%s): processing done", env.id);
++        task->input = h2_task_input_create(task, task->pool, 
++                                           task->c->bucket_alloc);
++        task->output = h2_task_output_create(task, task->pool,
++                                             task->c->bucket_alloc);
++        status = h2_conn_process(task->c, h2_worker_get_socket(worker));
++        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, task->c,
++                      "h2_task(%s): processing done", task->id);
      }
      else {
--        ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c,
--                      APLOGNO(02957) "h2_task(%s): error setting up h2_task_env", 
--                      env.id);
++        ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, task->c,
++                      APLOGNO(02957) "h2_task(%s): error setting up h2_task", 
++                      task->id);
      }
      
--    if (env.input) {
--        h2_task_input_destroy(env.input);
--        env.input = NULL;
++    if (task->input) {
++        h2_task_input_destroy(task->input);
++        task->input = NULL;
      }
      
--    if (env.output) {
--        h2_task_output_close(env.output);
--        h2_task_output_destroy(env.output);
--        env.output = NULL;
++    if (task->output) {
++        h2_task_output_close(task->output);
++        h2_task_output_destroy(task->output);
++        task->output = NULL;
      }
  
--    h2_task_set_finished(task);
--    if (env.io) {
--        apr_thread_cond_signal(env.io);
++    if (task->io) {
++        apr_thread_cond_signal(task->io);
      }
      
--    if (env.pool) {
--        apr_pool_destroy(env.pool);
--        env.pool = NULL;
++    if (task->pool) {
++        apr_pool_destroy(task->pool);
++        task->pool = NULL;
      }
      
--    if (env.c.id) {
--        h2_conn_post(&env.c, worker);
++    if (task->c->id) {
++        h2_conn_post(task->c, worker);
      }
      
--    h2_mplx_task_done(env.mplx, env.stream_id);
++    h2_mplx_task_done(task->mplx, task->stream_id);
      
      return status;
  }
  
--int h2_task_has_started(h2_task *task)
--{
--    AP_DEBUG_ASSERT(task);
--    return apr_atomic_read32(&task->has_started);
--}
--
--void h2_task_set_started(h2_task *task)
- {
-     AP_DEBUG_ASSERT(task);
-     apr_atomic_set32(&task->has_started, 1);
- }
- int h2_task_has_finished(h2_task *task)
- {
-     return apr_atomic_read32(&task->has_finished);
- }
- void h2_task_set_finished(h2_task *task)
- {
-     apr_atomic_set32(&task->has_finished, 1);
- }
- void h2_task_die(h2_task_env *env, int status, request_rec *r)
- {
-     (void)env;
-     ap_die(status, r);
- }
- static request_rec *h2_task_create_request(h2_task_env *env)
++static request_rec *h2_task_create_request(h2_task *task)
  {
 -    AP_DEBUG_ASSERT(task);
 -    apr_atomic_set32(&task->has_started, 1);
 -}
 -
 -int h2_task_has_finished(h2_task *task)
 -{
 -    return apr_atomic_read32(&task->has_finished);
 -}
 -
 -void h2_task_set_finished(h2_task *task)
 -{
 -    apr_atomic_set32(&task->has_finished, 1);
 -}
 -
 -void h2_task_die(h2_task_env *env, int status, request_rec *r)
 -{
 -    (void)env;
 -    ap_die(status, r);
 -}
 -
 -static request_rec *h2_task_create_request(h2_task_env *env)
 -{
--    conn_rec *conn = &env->c;
++    conn_rec *conn = task->c;
      request_rec *r;
      apr_pool_t *p;
      int access_status = HTTP_OK;    
      
      r->allowed_methods = ap_make_method_list(p, 2);
      
--    r->headers_in = apr_table_copy(r->pool, env->headers);
++    r->headers_in = apr_table_copy(r->pool, task->headers);
      r->trailers_in     = apr_table_make(r->pool, 5);
      r->subprocess_env  = apr_table_make(r->pool, 25);
      r->headers_out     = apr_table_make(r->pool, 12);
      
      /* Time to populate r with the data we have. */
      r->request_time = apr_time_now();
 -    r->the_request = apr_psprintf(r->pool, "%s %s HTTP/1.1", 
 -                                  env->method, env->path);
--    r->method = env->method;
++    r->method = task->method;
      /* Provide quick information about the request method as soon as known */
      r->method_number = ap_method_number_of(r->method);
      if (r->method_number == M_GET && r->method[0] == 'H') {
          r->header_only = 1;
      }
  
--    ap_parse_uri(r, env->path);
++    ap_parse_uri(r, task->path);
      r->protocol = (char*)"HTTP/2";
      r->proto_num = HTTP_VERSION(2, 0);
-                                   r->method, env->path, r->protocol);
 +
 +    r->the_request = apr_psprintf(r->pool, "%s %s %s", 
++                                  r->method, task->path, r->protocol);
      
      /* update what we think the virtual host is based on the headers we've
       * now read. may update status.
          /* Request check post hooks failed. An example of this would be a
           * request for a vhost where h2 is disabled --> 421.
           */
--        h2_task_die(env, access_status, r);
++        ap_die(access_status, r);
          ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
          ap_run_log_transaction(r);
          r = NULL;
@@@ -435,13 -434,13 +381,13 @@@ traceout
  }
  
  
--apr_status_t h2_task_process_request(h2_task_env *env)
++static apr_status_t h2_task_process_request(h2_task *task)
  {
--    conn_rec *c = &env->c;
++    conn_rec *c = task->c;
      request_rec *r;
      conn_state_t *cs = c->cs;
  
--    r = h2_task_create_request(env);
++    r = h2_task_create_request(task);
      if (r && (r->status == HTTP_OK)) {
          ap_update_child_status(c->sbh, SERVER_BUSY_READ, r);
          
index b66ce38c251ba611ada400f0e2e33546a0979499,b66ce38c251ba611ada400f0e2e33546a0979499..1877a3920b5c56a6fe6748f0cc92ace0e9a449c2
@@@ -45,16 -45,16 +45,10 @@@ struct h2_worker
  typedef struct h2_task h2_task;
  
  struct h2_task {
--    /** Links to the rest of the tasks */
--    APR_RING_ENTRY(h2_task) link;
--
      const char *id;
      int stream_id;
      struct h2_mplx *mplx;
      
--    volatile apr_uint32_t has_started;
--    volatile apr_uint32_t has_finished;
--    
      const char *method;
      const char *scheme;
      const char *authority;
      apr_table_t *headers;
      int input_eos;
  
--    struct conn_rec *c;
--};
++    int serialize_headers;
  
--typedef struct h2_task_env h2_task_env;
++    struct conn_rec *c;
  
--struct h2_task_env {
--    const char *id;
--    int stream_id;
--    struct h2_mplx *mplx;
--    
      apr_pool_t *pool;              /* pool for task lifetime things */
      apr_bucket_alloc_t *bucket_alloc;
--    
--    const char *method;
--    const char *scheme;
--    const char *authority;
--    const char *path;
--    apr_table_t *headers;
--    int input_eos;
--    
--    int serialize_headers;
--
--    struct conn_rec c;
      struct h2_task_input *input;
      struct h2_task_output *output;
      
      struct apr_thread_cond_t *io;   /* used to wait for events on */
  };
  
--/**
-- * The magic pointer value that indicates the head of a h2_task list
-- * @param  b The task list
-- * @return The magic pointer value
-- */
--#define H2_TASK_LIST_SENTINEL(b)      APR_RING_SENTINEL((b), h2_task, link)
--
--/**
-- * Determine if the task list is empty
-- * @param b The list to check
-- * @return true or false
-- */
--#define H2_TASK_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_task, link)
--
--/**
-- * Return the first task in a list
-- * @param b The list to query
-- * @return The first task in the list
-- */
--#define H2_TASK_LIST_FIRST(b) APR_RING_FIRST(b)
--
--/**
-- * Return the last task in a list
-- * @param b The list to query
-- * @return The last task int he list
-- */
--#define H2_TASK_LIST_LAST(b)  APR_RING_LAST(b)
--
--/**
-- * Insert a single task at the front of a list
-- * @param b The list to add to
-- * @param e The task to insert
-- */
--#define H2_TASK_LIST_INSERT_HEAD(b, e) do {                           \
--    h2_task *ap__b = (e);                                        \
--    APR_RING_INSERT_HEAD((b), ap__b, h2_task, link);  \
--} while (0)
--
--/**
-- * Insert a single task at the end of a list
-- * @param b The list to add to
-- * @param e The task to insert
-- */
--#define H2_TASK_LIST_INSERT_TAIL(b, e) do {                           \
--    h2_task *ap__b = (e);                                     \
--    APR_RING_INSERT_TAIL((b), ap__b, h2_task, link);  \
--} while (0)
--
--/**
-- * Get the next task in the list
-- * @param e The current task
-- * @return The next task
-- */
--#define H2_TASK_NEXT(e)       APR_RING_NEXT((e), link)
--/**
-- * Get the previous task in the list
-- * @param e The current task
-- * @return The previous task
-- */
--#define H2_TASK_PREV(e)       APR_RING_PREV((e), link)
--
--/**
-- * Remove a task from its list
-- * @param e The task to remove
-- */
--#define H2_TASK_REMOVE(e)     APR_RING_REMOVE((e), link)
--
--
  h2_task *h2_task_create(long session_id, int stream_id, 
                          apr_pool_t *pool, struct h2_mplx *mplx,
                          conn_rec *c);
@@@ -174,14 -174,14 +83,7 @@@ void h2_task_set_request(h2_task *task
  
  
  apr_status_t h2_task_do(h2_task *task, struct h2_worker *worker);
--apr_status_t h2_task_process_request(h2_task_env *env);
--
--int h2_task_has_started(h2_task *task);
--void h2_task_set_started(h2_task *task);
--int h2_task_has_finished(h2_task *task);
--void h2_task_set_finished(h2_task *task);
  
  void h2_task_register_hooks(void);
--void h2_task_die(h2_task_env *env, int status, request_rec *r);
  
  #endif /* defined(__mod_h2__h2_task__) */
index 487f7e606920c26fce185bbdf2b357f52f885c5d,487f7e606920c26fce185bbdf2b357f52f885c5d..1eac749f42a9fd6d6ab87ebd8c52ba701881bff5
@@@ -42,28 -42,28 +42,28 @@@ static int ser_header(void *ctx, const 
      return 1;
  }
  
--h2_task_input *h2_task_input_create(h2_task_env *env, apr_pool_t *pool, 
++h2_task_input *h2_task_input_create(h2_task *task, apr_pool_t *pool, 
                                      apr_bucket_alloc_t *bucket_alloc)
  {
      h2_task_input *input = apr_pcalloc(pool, sizeof(h2_task_input));
      if (input) {
--        input->env = env;
++        input->task = task;
          input->bb = NULL;
          
--        if (env->serialize_headers) {
--            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, &env->c,
++        if (task->serialize_headers) {
++            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
                            "h2_task_input(%s): serialize request %s %s", 
--                          env->id, env->method, env->path);
++                          task->id, task->method, task->path);
              input->bb = apr_brigade_create(pool, bucket_alloc);
              apr_brigade_printf(input->bb, NULL, NULL, "%s %s HTTP/1.1\r\n", 
--                               env->method, env->path);
--            apr_table_do(ser_header, input, env->headers, NULL);
++                               task->method, task->path);
++            apr_table_do(ser_header, input, task->headers, NULL);
              apr_brigade_puts(input->bb, NULL, NULL, "\r\n");
--            if (input->env->input_eos) {
++            if (input->task->input_eos) {
                  APR_BRIGADE_INSERT_TAIL(input->bb, apr_bucket_eos_create(bucket_alloc));
              }
          }
--        else if (!input->env->input_eos) {
++        else if (!input->task->input_eos) {
              input->bb = apr_brigade_create(pool, bucket_alloc);
          }
          else {
@@@ -71,7 -71,7 +71,7 @@@
               * create a bucket brigade. */
          }
          
--        if (APLOGcdebug(&env->c)) {
++        if (APLOGcdebug(task->c)) {
              char buffer[1024];
              apr_size_t len = sizeof(buffer)-1;
              if (input->bb) {
@@@ -81,9 -81,9 +81,9 @@@
                  len = 0;
              }
              buffer[len] = 0;
--            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, &env->c,
++            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
                            "h2_task_input(%s): request is: %s", 
--                          env->id, buffer);
++                          task->id, buffer);
          }
      }
      return input;
@@@ -106,17 -106,17 +106,17 @@@ apr_status_t h2_task_input_read(h2_task
      
      ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
                    "h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld", 
--                  input->env->id, block, mode, (long)readbytes);
++                  input->task->id, block, mode, (long)readbytes);
      
      if (is_aborted(f)) {
          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
                        "h2_task_input(%s): is aborted", 
--                      input->env->id);
++                      input->task->id);
          return APR_ECONNABORTED;
      }
      
      if (mode == AP_MODE_INIT) {
--        return APR_SUCCESS;
++        return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
      }
      
      if (input->bb) {
          if (status != APR_SUCCESS) {
              ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, f->c,
                            APLOGNO(02958) "h2_task_input(%s): brigade length fail", 
--                          input->env->id);
++                          input->task->id);
              return status;
          }
      }
      
--    if ((bblen == 0) && input->env->input_eos) {
++    if ((bblen == 0) && input->task->input_eos) {
          return APR_EOF;
      }
      
          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
                        "h2_task_input(%s): get more data from mplx, block=%d, "
                        "readbytes=%ld, queued=%ld",
--                      input->env->id, block, 
++                      input->task->id, block, 
                        (long)readbytes, (long)bblen);
          
          /* Although we sometimes get called with APR_NONBLOCK_READs, 
           we seem to  fill our buffer blocking. Otherwise we get EAGAIN,
           return that to our caller and everyone throws up their hands,
           never calling us again. */
--        status = h2_mplx_in_read(input->env->mplx, APR_BLOCK_READ,
--                                 input->env->stream_id, input->bb, 
--                                 input->env->io);
++        status = h2_mplx_in_read(input->task->mplx, APR_BLOCK_READ,
++                                 input->task->stream_id, input->bb, 
++                                 input->task->io);
          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
                        "h2_task_input(%s): mplx in read returned",
--                      input->env->id);
++                      input->task->id);
          if (status != APR_SUCCESS) {
              return status;
          }
          }
          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
                        "h2_task_input(%s): mplx in read, %ld bytes in brigade",
--                      input->env->id, (long)bblen);
++                      input->task->id, (long)bblen);
      }
      
      ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
                    "h2_task_input(%s): read, mode=%d, block=%d, "
                    "readbytes=%ld, queued=%ld",
--                  input->env->id, mode, block, 
++                  input->task->id, mode, block, 
                    (long)readbytes, (long)bblen);
             
      if (!APR_BRIGADE_EMPTY(input->bb)) {
          if (mode == AP_MODE_EXHAUSTIVE) {
              /* return all we have */
--            return h2_util_move(bb, input->bb, readbytes, 0
++            return h2_util_move(bb, input->bb, readbytes, NULL
                                  "task_input_read(exhaustive)");
          }
          else if (mode == AP_MODE_READBYTES) {
--            return h2_util_move(bb, input->bb, readbytes, 0
++            return h2_util_move(bb, input->bb, readbytes, NULL
                                  "task_input_read(readbytes)");
          }
          else if (mode == AP_MODE_SPECULATIVE) {
                  buffer[len] = 0;
                  ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
                                "h2_task_input(%s): getline: %s",
--                              input->env->id, buffer);
++                              input->task->id, buffer);
              }
              return status;
          }
index 32adc1770df468697210bb4b1c3fdea7defd1eff,32adc1770df468697210bb4b1c3fdea7defd1eff..ed0a99faba239e075e2fa9e1e081629f568eb549
   */
  struct apr_thread_cond_t;
  struct h2_mplx;
--struct h2_task_env;
++struct h2_task;
  
  typedef struct h2_task_input h2_task_input;
  struct h2_task_input {
--    struct h2_task_env *env;
++    struct h2_task *task;
      apr_bucket_brigade *bb;
  };
  
  
--h2_task_input *h2_task_input_create(struct h2_task_env *env, apr_pool_t *pool,
++h2_task_input *h2_task_input_create(struct h2_task *task, apr_pool_t *pool,
                                      apr_bucket_alloc_t *bucket_alloc);
  
  void h2_task_input_destroy(h2_task_input *input);
index 879cb5fa21b6f3358c108cbb06a15a4142a20eea,879cb5fa21b6f3358c108cbb06a15a4142a20eea..053e2d69e4ea6a5f43ee804796979524c245116b
  #include "h2_util.h"
  
  
--h2_task_output *h2_task_output_create(h2_task_env *env, apr_pool_t *pool,
++h2_task_output *h2_task_output_create(h2_task *task, apr_pool_t *pool,
                                        apr_bucket_alloc_t *bucket_alloc)
  {
      h2_task_output *output = apr_pcalloc(pool, sizeof(h2_task_output));
      
      (void)bucket_alloc;
      if (output) {
--        output->env = env;
++        output->task = task;
          output->state = H2_TASK_OUT_INIT;
--        output->from_h1 = h2_from_h1_create(env->stream_id, pool);
++        output->from_h1 = h2_from_h1_create(task->stream_id, pool);
          if (!output->from_h1) {
              return NULL;
          }
@@@ -73,18 -73,18 +73,18 @@@ static apr_status_t open_if_needed(h2_t
                  ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
                                "h2_task_output(%s): write without response "
                                "for %s %s %s",
--                              output->env->id, output->env->method, 
--                              output->env->authority, output->env->path);
++                              output->task->id, output->task->method, 
++                              output->task->authority, output->task->path);
                  f->c->aborted = 1;
              }
--            if (output->env->io) {
--                apr_thread_cond_broadcast(output->env->io);
++            if (output->task->io) {
++                apr_thread_cond_broadcast(output->task->io);
              }
              return APR_ECONNABORTED;
          }
          
--        return h2_mplx_out_open(output->env->mplx, output->env->stream_id, 
--                                response, f, bb, output->env->io);
++        return h2_mplx_out_open(output->task->mplx, output->task->stream_id, 
++                                response, f, bb, output->task->io);
      }
      return APR_EOF;
  }
@@@ -93,7 -93,7 +93,7 @@@ void h2_task_output_close(h2_task_outpu
  {
      open_if_needed(output, NULL, NULL);
      if (output->state != H2_TASK_OUT_DONE) {
--        h2_mplx_out_close(output->env->mplx, output->env->stream_id);
++        h2_mplx_out_close(output->task->mplx, output->task->stream_id);
          output->state = H2_TASK_OUT_DONE;
      }
  }
@@@ -113,7 -113,7 +113,7 @@@ apr_status_t h2_task_output_write(h2_ta
      apr_status_t status;
      if (APR_BRIGADE_EMPTY(bb)) {
          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
--                      "h2_task_output(%s): empty write", output->env->id);
++                      "h2_task_output(%s): empty write", output->task->id);
          return APR_SUCCESS;
      }
      
      if (status != APR_EOF) {
          ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
                        "h2_task_output(%s): opened and passed brigade", 
--                      output->env->id);
++                      output->task->id);
          return status;
      }
      ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
--                  "h2_task_output(%s): write brigade", output->env->id);
--    return h2_mplx_out_write(output->env->mplx, output->env->stream_id, 
--                             f, bb, output->env->io);
++                  "h2_task_output(%s): write brigade", output->task->id);
++    return h2_mplx_out_write(output->task->mplx, output->task->stream_id, 
++                             f, bb, output->task->io);
  }
  
index 86571a1e1071a053d9833f7f9af444b90bc41fd2,86571a1e1071a053d9833f7f9af444b90bc41fd2..79cb6816c7aac31a422d16f2fe6828fd8cce3bae
@@@ -23,7 -23,7 +23,7 @@@
   */
  struct apr_thread_cond_t;
  struct h2_mplx;
--struct h2_task_env;
++struct h2_task;
  struct h2_from_h1;
  
  typedef enum {
  typedef struct h2_task_output h2_task_output;
  
  struct h2_task_output {
--    struct h2_task_env *env;
++    struct h2_task *task;
      h2_task_output_state_t state;
      struct h2_from_h1 *from_h1;
  };
  
--h2_task_output *h2_task_output_create(struct h2_task_env *env, apr_pool_t *pool,
++h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool,
                                        apr_bucket_alloc_t *bucket_alloc);
  
  void h2_task_output_destroy(h2_task_output *output);
index a81cc100fb99fc82461e94c43c5e6c3e29b41040,a81cc100fb99fc82461e94c43c5e6c3e29b41040..1653aa26e285a1aa2f3f6586f210dd66d34afd9b
  #include "h2_task_queue.h"
  
  
--h2_task_queue *h2_tq_create(long id, apr_pool_t *pool)
++static void tq_grow(h2_task_queue *q, int nlen);
++static void tq_swap(h2_task_queue *q, int i, int j);
++static int tq_bubble_up(h2_task_queue *q, int i, int top, 
++                        h2_tq_cmp *cmp, void *ctx);
++static int tq_bubble_down(h2_task_queue *q, int i, int bottom, 
++                          h2_tq_cmp *cmp, void *ctx);
++
++h2_task_queue *h2_tq_create(apr_pool_t *pool, int capacity)
  {
      h2_task_queue *q = apr_pcalloc(pool, sizeof(h2_task_queue));
      if (q) {
--        q->id = id;
--        APR_RING_ELEM_INIT(q, link);
--        APR_RING_INIT(&q->tasks, h2_task, link);
++        q->pool = pool;
++        tq_grow(q, capacity);
++        q->nelts = 0;
      }
      return q;
  }
  
--void h2_tq_destroy(h2_task_queue *q)
++int h2_tq_empty(h2_task_queue *q)
  {
--    while (!H2_TASK_LIST_EMPTY(&q->tasks)) {
--        h2_task *task = H2_TASK_LIST_FIRST(&q->tasks);
--        H2_TASK_REMOVE(task);
++    return q->nelts == 0;
++}
++
++void h2_tq_add(h2_task_queue *q, struct h2_task *task,
++               h2_tq_cmp *cmp, void *ctx)
++{
++    int i;
++    
++    if (q->nelts >= q->nalloc) {
++        tq_grow(q, q->nalloc * 2);
      }
++    
++    i = (q->head + q->nelts) % q->nalloc;
++    q->elts[i] = task;
++    ++q->nelts;
++    
++    /* bubble it to the front of the queue */
++    tq_bubble_up(q, i, q->head, cmp, ctx);
  }
  
--static int in_list(h2_task_queue *q, h2_task *task)
++void h2_tq_sort(h2_task_queue *q, h2_tq_cmp *cmp, void *ctx)
  {
--    h2_task *e;
--    for (e = H2_TASK_LIST_FIRST(&q->tasks); 
--         e != H2_TASK_LIST_SENTINEL(&q->tasks);
--         e = H2_TASK_NEXT(e)) {
--        if (e == task) {
--            return 1;
--        }
++    /* Assume that changes in ordering are minimal. This needs,
++     * best case, q->nelts - 1 comparisions to check that nothing
++     * changed.
++     */
++    if (q->nelts > 0) {
++        int i, ni, prev, last;
++        
++        /* Start at the end of the queue and create a tail of sorted
++         * entries. Make that tail one element longer in each iteration.
++         */
++        last = i = (q->head + q->nelts - 1) % q->nalloc;
++        while (i != q->head) {
++            prev = (q->nalloc + i - 1) % q->nalloc;
++            
++            ni = tq_bubble_up(q, i, prev, cmp, ctx);
++            if (ni == prev) {
++                /* i bubbled one up, bubble the new i down, which
++                 * keeps all tasks below i sorted. */
++                tq_bubble_down(q, i, last, cmp, ctx);
++            }
++            i = prev;
++        };
      }
--    return 0;
  }
  
--int h2_tq_empty(h2_task_queue *q)
++
++h2_task *h2_tq_shift(h2_task_queue *q)
  {
--    return H2_TASK_LIST_EMPTY(&q->tasks);
++    h2_task *t;
++    
++    if (q->nelts <= 0) {
++        return NULL;
++    }
++    
++    t = q->elts[q->head];
++    q->head = (q->head + 1) % q->nalloc;
++    q->nelts--;
++    
++    return t;
++}
++
++static void tq_grow(h2_task_queue *q, int nlen)
++{
++    AP_DEBUG_ASSERT(q->nalloc <= nlen);
++    if (nlen > q->nalloc) {
++        h2_task **nq = apr_pcalloc(q->pool, sizeof(h2_task *) * nlen);
++        if (q->nelts > 0) {
++            int l = ((q->head + q->nelts) % q->nalloc) - q->head;
++            
++            memmove(nq, q->elts + q->head, sizeof(h2_task *) * l);
++            if (l < q->nelts) {
++                /* elts wrapped, append elts in [0, remain] to nq */
++                int remain = q->nelts - l;
++                memmove(nq + l, q->elts, sizeof(h2_task *) * remain);
++            }
++        }
++        q->elts = nq;
++        q->nalloc = nlen;
++        q->head = 0;
++    }
  }
  
--void h2_tq_append(h2_task_queue *q, struct h2_task *task)
++static void tq_swap(h2_task_queue *q, int i, int j)
  {
--    H2_TASK_LIST_INSERT_TAIL(&q->tasks, task);
++    h2_task *t = q->elts[i];
++    q->elts[i] = q->elts[j];
++    q->elts[j] = t;
  }
  
--apr_status_t h2_tq_remove(h2_task_queue *q, struct h2_task *task)
++static int tq_bubble_up(h2_task_queue *q, int i, int top, 
++                        h2_tq_cmp *cmp, void *ctx) 
  {
--    if (in_list(q, task)) {
--        H2_TASK_REMOVE(task);
--        return APR_SUCCESS;
++    int prev;
++    while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top) 
++           && (*cmp)(q->elts[i], q->elts[prev], ctx) < 0) {
++        tq_swap(q, prev, i);
++        i = prev;
      }
--    return APR_NOTFOUND;
++    return i;
  }
  
--h2_task *h2_tq_pop_first(h2_task_queue *q)
++static int tq_bubble_down(h2_task_queue *q, int i, int bottom, 
++                          h2_tq_cmp *cmp, void *ctx)
  {
--    if (!H2_TASK_LIST_EMPTY(&q->tasks)) {
--        h2_task *task = H2_TASK_LIST_FIRST(&q->tasks);
--        H2_TASK_REMOVE(task);
--        return task;
++    int next;
++    while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom) 
++           && (*cmp)(q->elts[i], q->elts[next], ctx) > 0) {
++        tq_swap(q, next, i);
++        i = next;
      }
--    return NULL;
++    return i;
  }
  
  
index d93d74ac5029846e3786bf859edf1c38b8979307,d93d74ac5029846e3786bf859edf1c38b8979307..36fad2c4d8db70cb47cf7eb3adac1216177960cb
  struct h2_task;
  
  /**
-- * A simple ring of rings that keeps a list of h2_tasks and can
-- * be ringed itself, using the APR RING macros.
++ * h2_task_queue keeps a list of sorted h2_task* in ascending order.
   */
  typedef struct h2_task_queue h2_task_queue;
  
  struct h2_task_queue {
--    APR_RING_ENTRY(h2_task_queue) link;
--    APR_RING_HEAD(h2_tasks, h2_task) tasks;
--    long id;
++    struct h2_task **elts;
++    int head;
++    int nelts;
++    int nalloc;
++    apr_pool_t *pool;
  };
  
  /**
-- * Allocate a new queue from the pool and initialize.
-- * @param id the identifier of the queue
-- * @param pool the memory pool
++ * Comparator for two task to determine their order.
++ *
++ * @param t1 task to compare
++ * @param t2 task to compare
++ * @param ctx provided user data
++ * @return value is the same as for strcmp() and has the effect:
++ *    == 0: t1 and t2 are treated equal in ordering
++ *     < 0: t1 should be sorted before t2
++ *     > 0: t2 should be sorted before t1
   */
--h2_task_queue *h2_tq_create(long id, apr_pool_t *pool);
++typedef int h2_tq_cmp(struct h2_task *t1, struct h2_task *t2, void *ctx);
++
  
  /**
-- * Release all queue tasks.
-- * @param q the queue to destroy
++ * Allocate a new queue from the pool and initialize.
++ * @param id the identifier of the queue
++ * @param pool the memory pool
   */
--void h2_tq_destroy(h2_task_queue *q);
++h2_task_queue *h2_tq_create(apr_pool_t *pool, int capacity);
  
  /**
   * Return != 0 iff there are no tasks in the queue.
  int h2_tq_empty(h2_task_queue *q);
  
  /**
-- * Append the task to the end of the queue.
++ * Add the task to the queue. 
++ *
   * @param q the queue to append the task to
-- * @param task the task to append
--  */
--void h2_tq_append(h2_task_queue *q, struct h2_task *task);
--
--/**
-- * Remove a task from the queue. Return APR_SUCCESS if the task
-- * was indeed queued, APR_NOTFOUND otherwise.
-- * @param q the queue to remove from
-- * @param task the task to remove
-- */
--apr_status_t h2_tq_remove(h2_task_queue *q, struct h2_task *task);
--
--/**
-- * Get the first task from the queue or NULL if the queue is empty. The
-- * task will be removed.
-- * @param q the queue to pop the first task from
-- */
--h2_task *h2_tq_pop_first(h2_task_queue *q);
--
--/*******************************************************************************
-- * Queue Manipulation.
-- ******************************************************************************/
--
--/**
-- * The magic pointer value that indicates the head of a h2_task_queue list
-- * @param  b The queue list
-- * @return The magic pointer value
++ * @param task the task to add
++ * @param cmp the comparator for sorting
++ * @param ctx user data for comparator 
   */
--#define H2_TQ_LIST_SENTINEL(b)        APR_RING_SENTINEL((b), h2_task_queue, link)
++void h2_tq_add(h2_task_queue *q, struct h2_task *task,
++               h2_tq_cmp *cmp, void *ctx);
  
  /**
-- * Determine if the queue list is empty
-- * @param b The list to check
-- * @return true or false
-- */
--#define H2_TQ_LIST_EMPTY(b)   APR_RING_EMPTY((b), h2_task_queue, link)
--
--/**
-- * Return the first queue in a list
-- * @param b The list to query
-- * @return The first queue in the list
-- */
--#define H2_TQ_LIST_FIRST(b)   APR_RING_FIRST(b)
--
--/**
-- * Return the last queue in a list
-- * @param b The list to query
-- * @return The last queue int he list
-- */
--#define H2_TQ_LIST_LAST(b)    APR_RING_LAST(b)
--
--/**
-- * Insert a single queue at the front of a list
-- * @param b The list to add to
-- * @param e The queue to insert
-- */
--#define H2_TQ_LIST_INSERT_HEAD(b, e) do {                             \
--h2_task_queue *ap__b = (e);                                        \
--APR_RING_INSERT_HEAD((b), ap__b, h2_task_queue, link);        \
--} while (0)
--
--/**
-- * Insert a single queue at the end of a list
-- * @param b The list to add to
-- * @param e The queue to insert
-- */
--#define H2_TQ_LIST_INSERT_TAIL(b, e) do {                             \
--h2_task_queue *ap__b = (e);                                   \
--APR_RING_INSERT_TAIL((b), ap__b, h2_task_queue, link);        \
--} while (0)
--
--/**
-- * Get the next queue in the list
-- * @param e The current queue
-- * @return The next queue
-- */
--#define H2_TQ_NEXT(e) APR_RING_NEXT((e), link)
--/**
-- * Get the previous queue in the list
-- * @param e The current queue
-- * @return The previous queue
++ * Sort the tasks queue again. Call if the task ordering
++ * has changed.
++ *
++ * @param q the queue to sort
++ * @param cmp the comparator for sorting
++ * @param ctx user data for the comparator 
   */
--#define H2_TQ_PREV(e) APR_RING_PREV((e), link)
++void h2_tq_sort(h2_task_queue *q, h2_tq_cmp *cmp, void *ctx);
  
  /**
-- * Remove a queue from its list
-- * @param e The queue to remove
++ * Get the first task from the queue or NULL if the queue is empty. 
++ * The task will be removed.
++ *
++ * @param q the queue to get the first task from
++ * @return the first task of the queue, NULL if empty
   */
--#define H2_TQ_REMOVE(e)       APR_RING_REMOVE((e), link)
--
--
--#define H2_TQ_EMPTY(e)        H2_TASK_LIST_EMPTY(&(e)->tasks)
++h2_task *h2_tq_shift(h2_task_queue *q);
  
  #endif /* defined(__mod_h2__h2_task_queue__) */
index 8dacfe801fc3727558d2dd653db61f4289c892b9,8dacfe801fc3727558d2dd653db61f4289c892b9..159fde31dd2c56090b4abd0fc4f475d3c9a2e5ac
@@@ -156,7 -156,7 +156,7 @@@ apr_status_t h2_to_h1_add_headers(h2_to
      return APR_SUCCESS;
  }
  
--apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, h2_task *task, int eos)
++apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, int eos)
  {
      ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
                    "h2_to_h1(%ld-%d): end headers", 
          apr_table_mergen(to_h1->headers, "Transfer-Encoding", "chunked");
      }
      
--    h2_task_set_request(task, to_h1->method, 
--                        to_h1->scheme, 
--                        to_h1->authority, 
--                        to_h1->path, 
--                        to_h1->headers, eos);
      to_h1->eoh = 1;
      
--    if (eos) {
--        apr_status_t status = h2_to_h1_close(to_h1);
--        if (status != APR_SUCCESS) {
--            ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, to_h1->m->c,
--                          APLOGNO(02960) 
--                          "h2_to_h1(%ld-%d): end headers, eos=%d", 
--                          to_h1->m->id, to_h1->stream_id, eos);
--        }
--        return status;
--    }
      return APR_SUCCESS;
  }
  
index 74586e2b7ea30424ed785f5c95fa6e565b58dbc9,74586e2b7ea30424ed785f5c95fa6e565b58dbc9..6fc06fbf87a20556ea1dcf88bb475281076356ea
@@@ -68,8 -68,8 +68,7 @@@ apr_status_t h2_to_h1_add_headers(h2_to
  
  /** End the request headers.
   */
--apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, 
--                                  struct h2_task *task, int eos);
++apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, int eos);
  
  /* Add request body data.
   */
index 6f29b461d66a1b1ef531711188f584b82a293990,9d141be93bf160d5d83d8fca278dd69ba990cc10..2713aeb2dabe16e5ceb3bad6fc08679bc9963a9f
@@@ -648,71 -647,3 +648,145 @@@ apr_status_t h2_util_bb_readx(apr_bucke
      return status;
  }
  
 +void h2_util_bb_log(conn_rec *c, int stream_id, int level, 
 +                    const char *tag, apr_bucket_brigade *bb)
 +{
 +    char buffer[16 * 1024];
 +    const char *line = "(null)";
 +    apr_size_t bmax = sizeof(buffer)/sizeof(buffer[0]);
 +    int off = 0;
 +    apr_bucket *b;
 +    
 +    if (bb) {
 +        memset(buffer, 0, bmax--);
 +        for (b = APR_BRIGADE_FIRST(bb); 
 +             bmax && (b != APR_BRIGADE_SENTINEL(bb));
 +             b = APR_BUCKET_NEXT(b)) {
 +            
 +            if (APR_BUCKET_IS_METADATA(b)) {
 +                if (APR_BUCKET_IS_EOS(b)) {
 +                    off += apr_snprintf(buffer+off, bmax-off, "eos ");
 +                }
 +                else if (APR_BUCKET_IS_FLUSH(b)) {
 +                    off += apr_snprintf(buffer+off, bmax-off, "flush ");
 +                }
 +                else if (AP_BUCKET_IS_EOR(b)) {
 +                    off += apr_snprintf(buffer+off, bmax-off, "eor ");
 +                }
 +                else {
 +                    off += apr_snprintf(buffer+off, bmax-off, "meta(unknown) ");
 +                }
 +            }
 +            else {
 +                const char *btype = "data";
 +                if (APR_BUCKET_IS_FILE(b)) {
 +                    btype = "file";
 +                }
 +                else if (APR_BUCKET_IS_PIPE(b)) {
 +                    btype = "pipe";
 +                }
 +                else if (APR_BUCKET_IS_SOCKET(b)) {
 +                    btype = "socket";
 +                }
 +                else if (APR_BUCKET_IS_HEAP(b)) {
 +                    btype = "heap";
 +                }
 +                else if (APR_BUCKET_IS_TRANSIENT(b)) {
 +                    btype = "transient";
 +                }
 +                else if (APR_BUCKET_IS_IMMORTAL(b)) {
 +                    btype = "immortal";
 +                }
++#if APR_HAS_MMAP
 +                else if (APR_BUCKET_IS_MMAP(b)) {
 +                    btype = "mmap";
 +                }
++#endif
 +                else if (APR_BUCKET_IS_POOL(b)) {
 +                    btype = "pool";
 +                }
 +                
 +                off += apr_snprintf(buffer+off, bmax-off, "%s[%ld] ", 
 +                                    btype, 
 +                                    (long)(b->length == ((apr_size_t)-1)? 
 +                                           -1 : b->length));
 +            }
 +        }
 +        line = *buffer? buffer : "(empty)";
 +    }
 +    ap_log_cerror(APLOG_MARK, level, 0, c, "bb_dump(%ld-%d)-%s: %s", 
 +                  c->id, stream_id, tag, line);
 +
 +}
++
++AP_DECLARE(apr_status_t) h2_transfer_brigade(apr_bucket_brigade *to,
++                                             apr_bucket_brigade *from, 
++                                             apr_pool_t *p,
++                                             apr_size_t *plen,
++                                             int *peos)
++{
++    apr_bucket *e;
++    apr_size_t len = 0, remain = *plen;
++    apr_status_t rv;
++
++    *peos = 0;
++    
++    while (!APR_BRIGADE_EMPTY(from)) {
++        e = APR_BRIGADE_FIRST(from);
++        
++        if (APR_BUCKET_IS_METADATA(e)) {
++            if (APR_BUCKET_IS_EOS(e)) {
++                *peos = 1;
++            }
++        }
++        else {        
++            if (remain > 0 && e->length == ((apr_size_t)-1)) {
++                const char *ign;
++                apr_size_t ilen;
++                rv = apr_bucket_read(e, &ign, &ilen, APR_BLOCK_READ);
++                if (rv != APR_SUCCESS) {
++                    return rv;
++                }
++            }
++            
++            if (remain < e->length) {
++                if (remain <= 0) {
++                    return APR_SUCCESS;
++                }
++                apr_bucket_split(e, remain);
++            }
++        }
++        
++        rv = apr_bucket_setaside(e, p);
++        
++        /* If the bucket type does not implement setaside, then
++         * (hopefully) morph it into a bucket type which does, and set
++         * *that* aside... */
++        if (rv == APR_ENOTIMPL) {
++            const char *s;
++            apr_size_t n;
++            
++            rv = apr_bucket_read(e, &s, &n, APR_BLOCK_READ);
++            if (rv == APR_SUCCESS) {
++                rv = apr_bucket_setaside(e, p);
++            }
++        }
++        
++        if (rv != APR_SUCCESS) {
++            /* Return an error but still save the brigade if
++             * ->setaside() is really not implemented. */
++            if (rv != APR_ENOTIMPL) {
++                return rv;
++            }
++        }
++        
++        APR_BUCKET_REMOVE(e);
++        APR_BRIGADE_INSERT_TAIL(to, e);
++        len += e->length;
++        remain -= e->length;
++    }
++    
++    *plen = len;
++    return APR_SUCCESS;
++}
++
index e1a6b3c4d2039e33217826fe9eec346f82b01d08,9a1b5c6d35bf9ae36b271b5c3235d592f77e0837..a488e2a041eeabeb317c91a579446c0baeb62be1
@@@ -133,16 -121,4 +133,31 @@@ apr_status_t h2_util_bb_readx(apr_bucke
                                h2_util_pass_cb *cb, void *ctx, 
                                apr_size_t *plen, int *peos);
  
 +/**
 + * Logs the bucket brigade (which bucket types with what length)
 + * to the log at the given level.
 + * @param c the connection to log for
 + * @param stream_id the stream identifier this brigade belongs to
 + * @param level the log level (as in APLOG_*)
 + * @param tag a short message text about the context
 + * @param bb the brigade to log
 + */
 +void h2_util_bb_log(conn_rec *c, int stream_id, int level, 
 +                    const char *tag, apr_bucket_brigade *bb);
 +
++/**
++ * Transfer buckets from one brigade to another with a limit on the 
++ * maximum amount of bytes transfered.
++ * @param to   brigade to transfer buckets to
++ * @param from brigades to remove buckets from
++ * @param p    pool that buckets should be setaside to
++ * @param plen maximum bytes to transfer, actual bytes transferred
++ * @param peos if an EOS bucket was transferred
++ */
++AP_DECLARE(apr_status_t) h2_transfer_brigade(apr_bucket_brigade *to,
++                                             apr_bucket_brigade *from, 
++                                             apr_pool_t *p,
++                                             apr_size_t *plen,
++                                             int *peos);
++
  #endif /* defined(__mod_h2__h2_util__) */
index 10219daf1918fbe1115ecb43570fb4bb8586c8ac,7a03865c87c2ed20a558d0f231738964ca4a8662..e72f66287399ea83efd60305f0e5aaba19c3770b
@@@ -20,7 -20,7 +20,7 @@@
   * @macro
   * Version number of the h2 module as c string
   */
- #define MOD_HTTP2_VERSION "1.0.2-DEV"
 -#define MOD_HTTP2_VERSION "1.0.0"
++#define MOD_HTTP2_VERSION "1.0.3-DEV"
  
  /**
   * @macro
@@@ -28,7 -28,7 +28,7 @@@
   * release. This is a 24 bit number with 8 bits for major number, 8 bits
   * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
   */
- #define MOD_HTTP2_VERSION_NUM 0x010002
 -#define MOD_HTTP2_VERSION_NUM 0x010000
++#define MOD_HTTP2_VERSION_NUM 0x010003
  
  
  #endif /* mod_h2_h2_version_h */
index c3e139e9553082c4501134cb1aa8edcb7cc8a168,c3e139e9553082c4501134cb1aa8edcb7cc8a168..fbff711df77317b872a8e475987ee30505963a8b
@@@ -105,6 -105,6 +105,14 @@@ SOURCE=./h2_alt_svc.
  # End Source File
  # Begin Source File
  
++SOURCE=./h2_bucket_eoc.c
++# End Source File
++# Begin Source File
++
++SOURCE=./h2_bucket_eos.c
++# End Source File
++# Begin Source File
++
  SOURCE=./h2_config.c
  # End Source File
  # Begin Source File