view src/http/ngx_http_spdy.c @ 6078:7ea6f5140ed9 stable-1.6

SPDY: push pending data while closing a stream as with keepalive. This helps to avoid delays in sending the last chunk of data because of bad interaction between Nagle's algorithm on nginx side and delayed ACK on the client side. Delays could also be caused by TCP_CORK/TCP_NOPUSH if SPDY was working without SSL and sendfile() was used.
author Valentin Bartenev <vbart@nginx.com>
date Fri, 21 Nov 2014 22:51:49 +0300
parents b6240baead00
children
line wrap: on
line source


/*
 * Copyright (C) Nginx, Inc.
 * Copyright (C) Valentin V. Bartenev
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_http_spdy_module.h>

#include <zlib.h>


#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)

#define ngx_str5cmp(m, c0, c1, c2, c3, c4)                                    \
    *(uint32_t *) m == (c3 << 24 | c2 << 16 | c1 << 8 | c0)                   \
        && m[4] == c4

#else

#define ngx_str5cmp(m, c0, c1, c2, c3, c4)                                    \
    m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4

#endif


#if (NGX_HAVE_NONALIGNED)

#define ngx_spdy_frame_parse_uint16(p)  ntohs(*(uint16_t *) (p))
#define ngx_spdy_frame_parse_uint32(p)  ntohl(*(uint32_t *) (p))

#else

#define ngx_spdy_frame_parse_uint16(p) ((p)[0] << 8 | (p)[1])
#define ngx_spdy_frame_parse_uint32(p)                                        \
    ((p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])

#endif

#define ngx_spdy_frame_parse_sid(p)                                           \
    (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff)
#define ngx_spdy_frame_parse_delta(p)                                         \
    (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff)


#define ngx_spdy_ctl_frame_check(h)                                           \
    (((h) & 0xffff0000) == ngx_spdy_ctl_frame_head(0))
#define ngx_spdy_data_frame_check(h)                                          \
    (!((h) & (uint32_t) NGX_SPDY_CTL_BIT << 31))

#define ngx_spdy_ctl_frame_type(h)   ((h) & 0x0000ffff)
#define ngx_spdy_frame_flags(p)      ((p) >> 24)
#define ngx_spdy_frame_length(p)     ((p) & 0x00ffffff)
#define ngx_spdy_frame_id(p)         ((p) & 0x00ffffff)


#define NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE  4096
#define NGX_SPDY_CTL_FRAME_BUFFER_SIZE     16

#define NGX_SPDY_PROTOCOL_ERROR            1
#define NGX_SPDY_INVALID_STREAM            2
#define NGX_SPDY_REFUSED_STREAM            3
#define NGX_SPDY_UNSUPPORTED_VERSION       4
#define NGX_SPDY_CANCEL                    5
#define NGX_SPDY_INTERNAL_ERROR            6
#define NGX_SPDY_FLOW_CONTROL_ERROR        7
#define NGX_SPDY_STREAM_IN_USE             8
#define NGX_SPDY_STREAM_ALREADY_CLOSED     9
/* deprecated                              10 */
#define NGX_SPDY_FRAME_TOO_LARGE           11

#define NGX_SPDY_SETTINGS_MAX_STREAMS      4
#define NGX_SPDY_SETTINGS_INIT_WINDOW      7

#define NGX_SPDY_SETTINGS_FLAG_PERSIST     0x01
#define NGX_SPDY_SETTINGS_FLAG_PERSISTED   0x02

#define NGX_SPDY_MAX_WINDOW                NGX_MAX_INT32_VALUE
#define NGX_SPDY_CONNECTION_WINDOW         65536
#define NGX_SPDY_INIT_STREAM_WINDOW        65536
#define NGX_SPDY_STREAM_WINDOW             NGX_SPDY_MAX_WINDOW

typedef struct {
    ngx_uint_t    hash;
    u_char        len;
    u_char        header[7];
    ngx_int_t   (*handler)(ngx_http_request_t *r);
} ngx_http_spdy_request_header_t;


static void ngx_http_spdy_read_handler(ngx_event_t *rev);
static void ngx_http_spdy_write_handler(ngx_event_t *wev);
static void ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc);

static u_char *ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end);
static u_char *ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler);
static u_char *ngx_http_spdy_state_protocol_error(
    ngx_http_spdy_connection_t *sc);
static u_char *ngx_http_spdy_state_internal_error(
    ngx_http_spdy_connection_t *sc);

static ngx_int_t ngx_http_spdy_send_window_update(
    ngx_http_spdy_connection_t *sc, ngx_uint_t sid, ngx_uint_t delta);
static ngx_int_t ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc,
    ngx_uint_t sid, ngx_uint_t status, ngx_uint_t priority);
static ngx_int_t ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc);
static ngx_int_t ngx_http_spdy_settings_frame_handler(
    ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame);
static ngx_http_spdy_out_frame_t *ngx_http_spdy_get_ctl_frame(
    ngx_http_spdy_connection_t *sc, size_t size, ngx_uint_t priority);
static ngx_int_t ngx_http_spdy_ctl_frame_handler(
    ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame);

static ngx_http_spdy_stream_t *ngx_http_spdy_create_stream(
    ngx_http_spdy_connection_t *sc, ngx_uint_t id, ngx_uint_t priority);
static ngx_http_spdy_stream_t *ngx_http_spdy_get_stream_by_id(
    ngx_http_spdy_connection_t *sc, ngx_uint_t sid);
#define ngx_http_spdy_streams_index_size(sscf)  (sscf->streams_index_mask + 1)
#define ngx_http_spdy_stream_index(sscf, sid)                                 \
    ((sid >> 1) & sscf->streams_index_mask)

static ngx_int_t ngx_http_spdy_parse_header(ngx_http_request_t *r);
static ngx_int_t ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r);

static ngx_int_t ngx_http_spdy_handle_request_header(ngx_http_request_t *r);
static ngx_int_t ngx_http_spdy_parse_method(ngx_http_request_t *r);
static ngx_int_t ngx_http_spdy_parse_scheme(ngx_http_request_t *r);
static ngx_int_t ngx_http_spdy_parse_host(ngx_http_request_t *r);
static ngx_int_t ngx_http_spdy_parse_path(ngx_http_request_t *r);
static ngx_int_t ngx_http_spdy_parse_version(ngx_http_request_t *r);

static ngx_int_t ngx_http_spdy_construct_request_line(ngx_http_request_t *r);
static void ngx_http_spdy_run_request(ngx_http_request_t *r);
static ngx_int_t ngx_http_spdy_init_request_body(ngx_http_request_t *r);

static ngx_int_t ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc,
    ngx_http_spdy_stream_t *stream, ngx_uint_t status);

static void ngx_http_spdy_close_stream_handler(ngx_event_t *ev);

static void ngx_http_spdy_handle_connection_handler(ngx_event_t *rev);
static void ngx_http_spdy_keepalive_handler(ngx_event_t *rev);
static void ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc,
    ngx_int_t rc);

static ngx_int_t ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc,
    ssize_t delta);

static void ngx_http_spdy_pool_cleanup(void *data);

static void *ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size);
static void ngx_http_spdy_zfree(void *opaque, void *address);


static const u_char ngx_http_spdy_dict[] = {
    0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69,   /* - - - - o p t i */
    0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68,   /* o n s - - - - h */
    0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70,   /* e a d - - - - p */
    0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70,   /* o s t - - - - p */
    0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65,   /* u t - - - - d e */
    0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05,   /* l e t e - - - - */
    0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00,   /* t r a c e - - - */
    0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00,   /* - a c c e p t - */
    0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70,   /* - - - a c c e p */
    0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,   /* t - c h a r s e */
    0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63,   /* t - - - - a c c */
    0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,   /* e p t - e n c o */
    0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f,   /* d i n g - - - - */
    0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c,   /* a c c e p t - l */
    0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00,   /* a n g u a g e - */
    0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70,   /* - - - a c c e p */
    0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73,   /* t - r a n g e s */
    0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00,   /* - - - - a g e - */
    0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77,   /* - - - a l l o w */
    0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68,   /* - - - - a u t h */
    0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,   /* o r i z a t i o */
    0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63,   /* n - - - - c a c */
    0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72,   /* h e - c o n t r */
    0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f,   /* o l - - - - c o */
    0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,   /* n n e c t i o n */
    0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74,   /* - - - - c o n t */
    0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65,   /* e n t - b a s e */
    0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74,   /* - - - - c o n t */
    0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,   /* e n t - e n c o */
    0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10,   /* d i n g - - - - */
    0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d,   /* c o n t e n t - */
    0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,   /* l a n g u a g e */
    0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74,   /* - - - - c o n t */
    0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67,   /* e n t - l e n g */
    0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f,   /* t h - - - - c o */
    0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f,   /* n t e n t - l o */
    0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00,   /* c a t i o n - - */
    0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,   /* - - c o n t e n */
    0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00,   /* t - m d 5 - - - */
    0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,   /* - c o n t e n t */
    0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00,   /* - r a n g e - - */
    0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,   /* - - c o n t e n */
    0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00,   /* t - t y p e - - */
    0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00,   /* - - d a t e - - */
    0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00,   /* - - e t a g - - */
    0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,   /* - - e x p e c t */
    0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69,   /* - - - - e x p i */
    0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66,   /* r e s - - - - f */
    0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68,   /* r o m - - - - h */
    0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69,   /* o s t - - - - i */
    0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00,   /* f - m a t c h - */
    0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f,   /* - - - i f - m o */
    0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73,   /* d i f i e d - s */
    0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d,   /* i n c e - - - - */
    0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d,   /* i f - n o n e - */
    0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00,   /* m a t c h - - - */
    0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67,   /* - i f - r a n g */
    0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d,   /* e - - - - i f - */
    0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69,   /* u n m o d i f i */
    0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65,   /* e d - s i n c e */
    0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74,   /* - - - - l a s t */
    0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65,   /* - m o d i f i e */
    0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63,   /* d - - - - l o c */
    0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00,   /* a t i o n - - - */
    0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72,   /* - m a x - f o r */
    0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00,   /* w a r d s - - - */
    0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00,   /* - p r a g m a - */
    0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79,   /* - - - p r o x y */
    0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,   /* - a u t h e n t */
    0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00,   /* i c a t e - - - */
    0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61,   /* - p r o x y - a */
    0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,   /* u t h o r i z a */
    0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05,   /* t i o n - - - - */
    0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00,   /* r a n g e - - - */
    0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72,   /* - r e f e r e r */
    0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72,   /* - - - - r e t r */
    0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00,   /* y - a f t e r - */
    0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65,   /* - - - s e r v e */
    0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00,   /* r - - - - t e - */
    0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c,   /* - - - t r a i l */
    0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72,   /* e r - - - - t r */
    0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65,   /* a n s f e r - e */
    0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00,   /* n c o d i n g - */
    0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61,   /* - - - u p g r a */
    0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73,   /* d e - - - - u s */
    0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74,   /* e r - a g e n t */
    0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79,   /* - - - - v a r y */
    0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00,   /* - - - - v i a - */
    0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69,   /* - - - w a r n i */
    0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77,   /* n g - - - - w w */
    0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,   /* w - a u t h e n */
    0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00,   /* t i c a t e - - */
    0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,   /* - - m e t h o d */
    0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00,   /* - - - - g e t - */
    0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,   /* - - - s t a t u */
    0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30,   /* s - - - - 2 0 0 */
    0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76,   /* - O K - - - - v */
    0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00,   /* e r s i o n - - */
    0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31,   /* - - H T T P - 1 */
    0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72,   /* - 1 - - - - u r */
    0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62,   /* l - - - - p u b */
    0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73,   /* l i c - - - - s */
    0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69,   /* e t - c o o k i */
    0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65,   /* e - - - - k e e */
    0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00,   /* p - a l i v e - */
    0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69,   /* - - - o r i g i */
    0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32,   /* n 1 0 0 1 0 1 2 */
    0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35,   /* 0 1 2 0 2 2 0 5 */
    0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30,   /* 2 0 6 3 0 0 3 0 */
    0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33,   /* 2 3 0 3 3 0 4 3 */
    0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37,   /* 0 5 3 0 6 3 0 7 */
    0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30,   /* 4 0 2 4 0 5 4 0 */
    0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34,   /* 6 4 0 7 4 0 8 4 */
    0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31,   /* 0 9 4 1 0 4 1 1 */
    0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31,   /* 4 1 2 4 1 3 4 1 */
    0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34,   /* 4 4 1 5 4 1 6 4 */
    0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34,   /* 1 7 5 0 2 5 0 4 */
    0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e,   /* 5 0 5 2 0 3 - N */
    0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f,   /* o n - A u t h o */
    0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65,   /* r i t a t i v e */
    0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,   /* - I n f o r m a */
    0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20,   /* t i o n 2 0 4 - */
    0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65,   /* N o - C o n t e */
    0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f,   /* n t 3 0 1 - M o */
    0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d,   /* v e d - P e r m */
    0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34,   /* a n e n t l y 4 */
    0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52,   /* 0 0 - B a d - R */
    0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30,   /* e q u e s t 4 0 */
    0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68,   /* 1 - U n a u t h */
    0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30,   /* o r i z e d 4 0 */
    0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64,   /* 3 - F o r b i d */
    0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e,   /* d e n 4 0 4 - N */
    0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64,   /* o t - F o u n d */
    0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65,   /* 5 0 0 - I n t e */
    0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72,   /* r n a l - S e r */
    0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f,   /* v e r - E r r o */
    0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74,   /* r 5 0 1 - N o t */
    0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65,   /* - I m p l e m e */
    0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20,   /* n t e d 5 0 3 - */
    0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20,   /* S e r v i c e - */
    0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,   /* U n a v a i l a */
    0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46,   /* b l e J a n - F */
    0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41,   /* e b - M a r - A */
    0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a,   /* p r - M a y - J */
    0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41,   /* u n - J u l - A */
    0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20,   /* u g - S e p t - */
    0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20,   /* O c t - N o v - */
    0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30,   /* D e c - 0 0 - 0 */
    0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e,   /* 0 - 0 0 - M o n */
    0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57,   /* - - T u e - - W */
    0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c,   /* e d - - T h u - */
    0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61,   /* - F r i - - S a */
    0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20,   /* t - - S u n - - */
    0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b,   /* G M T c h u n k */
    0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f,   /* e d - t e x t - */
    0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61,   /* h t m l - i m a */
    0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69,   /* g e - p n g - i */
    0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67,   /* m a g e - j p g */
    0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67,   /* - i m a g e - g */
    0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,   /* i f - a p p l i */
    0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,   /* c a t i o n - x */
    0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,   /* m l - a p p l i */
    0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,   /* c a t i o n - x */
    0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c,   /* h t m l - x m l */
    0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c,   /* - t e x t - p l */
    0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74,   /* a i n - t e x t */
    0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72,   /* - j a v a s c r */
    0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c,   /* i p t - p u b l */
    0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,   /* i c p r i v a t */
    0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65,   /* e m a x - a g e */
    0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65,   /* - g z i p - d e */
    0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64,   /* f l a t e - s d */
    0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,   /* c h c h a r s e */
    0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63,   /* t - u t f - 8 c */
    0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69,   /* h a r s e t - i */
    0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d,   /* s o - 8 8 5 9 - */
    0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a,   /* 1 - u t f - - - */
    0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e          /* - e n q - 0 -   */
};


static ngx_http_spdy_request_header_t ngx_http_spdy_request_headers[] = {
    { 0, 6, "method", ngx_http_spdy_parse_method },
    { 0, 6, "scheme", ngx_http_spdy_parse_scheme },
    { 0, 4, "host", ngx_http_spdy_parse_host },
    { 0, 4, "path", ngx_http_spdy_parse_path },
    { 0, 7, "version", ngx_http_spdy_parse_version },
};

#define NGX_SPDY_REQUEST_HEADERS                                              \
    (sizeof(ngx_http_spdy_request_headers)                                    \
     / sizeof(ngx_http_spdy_request_header_t))


void
ngx_http_spdy_init(ngx_event_t *rev)
{
    int                          rc;
    ngx_connection_t            *c;
    ngx_pool_cleanup_t          *cln;
    ngx_http_connection_t       *hc;
    ngx_http_spdy_srv_conf_t    *sscf;
    ngx_http_spdy_main_conf_t   *smcf;
    ngx_http_spdy_connection_t  *sc;

    c = rev->data;
    hc = c->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "init spdy request");

    c->log->action = "processing SPDY";

    smcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_spdy_module);

    if (smcf->recv_buffer == NULL) {
        smcf->recv_buffer = ngx_palloc(ngx_cycle->pool, smcf->recv_buffer_size);
        if (smcf->recv_buffer == NULL) {
            ngx_http_close_connection(c);
            return;
        }
    }

    sc = ngx_pcalloc(c->pool, sizeof(ngx_http_spdy_connection_t));
    if (sc == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    sc->connection = c;
    sc->http_connection = hc;

    sc->send_window = NGX_SPDY_CONNECTION_WINDOW;
    sc->recv_window = NGX_SPDY_CONNECTION_WINDOW;

    sc->init_window = NGX_SPDY_INIT_STREAM_WINDOW;

    sc->handler = ngx_http_spdy_state_head;

    if (hc->proxy_protocol) {
        c->log->action = "reading PROXY protocol";
        sc->handler = ngx_http_spdy_proxy_protocol;
    }

    sc->zstream_in.zalloc = ngx_http_spdy_zalloc;
    sc->zstream_in.zfree = ngx_http_spdy_zfree;
    sc->zstream_in.opaque = sc;

    rc = inflateInit(&sc->zstream_in);
    if (rc != Z_OK) {
        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                      "inflateInit() failed: %d", rc);
        ngx_http_close_connection(c);
        return;
    }

    sc->zstream_out.zalloc = ngx_http_spdy_zalloc;
    sc->zstream_out.zfree = ngx_http_spdy_zfree;
    sc->zstream_out.opaque = sc;

    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_spdy_module);

    rc = deflateInit2(&sc->zstream_out, (int) sscf->headers_comp,
                      Z_DEFLATED, 11, 4, Z_DEFAULT_STRATEGY);

    if (rc != Z_OK) {
        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                      "deflateInit2() failed: %d", rc);
        ngx_http_close_connection(c);
        return;
    }

    rc = deflateSetDictionary(&sc->zstream_out, ngx_http_spdy_dict,
                              sizeof(ngx_http_spdy_dict));
    if (rc != Z_OK) {
        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                      "deflateSetDictionary() failed: %d", rc);
        ngx_http_close_connection(c);
        return;
    }

    sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log);
    if (sc->pool == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_pool_cleanup_file_t));
    if (cln == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    cln->handler = ngx_http_spdy_pool_cleanup;
    cln->data = sc;

    sc->streams_index = ngx_pcalloc(sc->pool,
                                    ngx_http_spdy_streams_index_size(sscf)
                                    * sizeof(ngx_http_spdy_stream_t *));
    if (sc->streams_index == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    if (ngx_http_spdy_send_settings(sc) == NGX_ERROR) {
        ngx_http_close_connection(c);
        return;
    }

    if (ngx_http_spdy_send_window_update(sc, 0, NGX_SPDY_MAX_WINDOW
                                                - sc->recv_window)
        == NGX_ERROR)
    {
        ngx_http_close_connection(c);
        return;
    }

    sc->recv_window = NGX_SPDY_MAX_WINDOW;

    ngx_queue_init(&sc->waiting);
    ngx_queue_init(&sc->posted);

    c->data = sc;

    rev->handler = ngx_http_spdy_read_handler;
    c->write->handler = ngx_http_spdy_write_handler;

    ngx_http_spdy_read_handler(rev);
}


static void
ngx_http_spdy_read_handler(ngx_event_t *rev)
{
    u_char                      *p, *end;
    size_t                       available;
    ssize_t                      n;
    ngx_connection_t            *c;
    ngx_http_spdy_main_conf_t   *smcf;
    ngx_http_spdy_connection_t  *sc;

    c = rev->data;
    sc = c->data;

    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_spdy_finalize_connection(sc, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy read handler");

    sc->blocked = 1;

    smcf = ngx_http_get_module_main_conf(sc->http_connection->conf_ctx,
                                         ngx_http_spdy_module);

    available = smcf->recv_buffer_size - 2 * NGX_SPDY_STATE_BUFFER_SIZE;

    do {
        p = smcf->recv_buffer;

        ngx_memcpy(p, sc->buffer, NGX_SPDY_STATE_BUFFER_SIZE);
        end = p + sc->buffer_used;

        n = c->recv(c, end, available);

        if (n == NGX_AGAIN) {
            break;
        }

        if (n == 0 && (sc->incomplete || sc->processing)) {
            ngx_log_error(NGX_LOG_INFO, c->log, 0,
                          "client closed prematurely connection");
        }

        if (n == 0 || n == NGX_ERROR) {
            ngx_http_spdy_finalize_connection(sc,
                                              NGX_HTTP_CLIENT_CLOSED_REQUEST);
            return;
        }

        end += n;

        sc->buffer_used = 0;
        sc->incomplete = 0;

        do {
            p = sc->handler(sc, p, end);

            if (p == NULL) {
                return;
            }

        } while (p != end);

    } while (rev->ready);

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    if (sc->last_out && ngx_http_spdy_send_output_queue(sc) == NGX_ERROR) {
        ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST);
        return;
    }

    sc->blocked = 0;

    if (sc->processing) {
        if (rev->timer_set) {
            ngx_del_timer(rev);
        }
        return;
    }

    ngx_http_spdy_handle_connection(sc);
}


static void
ngx_http_spdy_write_handler(ngx_event_t *wev)
{
    ngx_int_t                    rc;
    ngx_queue_t                 *q;
    ngx_connection_t            *c;
    ngx_http_spdy_stream_t      *stream;
    ngx_http_spdy_connection_t  *sc;

    c = wev->data;
    sc = c->data;

    if (wev->timedout) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "spdy write event timed out");
        ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST);
        return;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy write handler");

    sc->blocked = 1;

    rc = ngx_http_spdy_send_output_queue(sc);

    if (rc == NGX_ERROR) {
        ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST);
        return;
    }

    while (!ngx_queue_empty(&sc->posted)) {
        q = ngx_queue_head(&sc->posted);

        ngx_queue_remove(q);

        stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue);

        stream->handled = 0;

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "spdy run stream %ui", stream->id);

        wev = stream->request->connection->write;
        wev->handler(wev);
    }

    sc->blocked = 0;

    if (rc == NGX_AGAIN) {
        return;
    }

    ngx_http_spdy_handle_connection(sc);
}


ngx_int_t
ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc)
{
    ngx_chain_t                *cl;
    ngx_event_t                *wev;
    ngx_connection_t           *c;
    ngx_http_core_loc_conf_t   *clcf;
    ngx_http_spdy_out_frame_t  *out, *frame, *fn;

    c = sc->connection;

    if (c->error) {
        return NGX_ERROR;
    }

    wev = c->write;

    if (!wev->ready) {
        return NGX_OK;
    }

    cl = NULL;
    out = NULL;

    for (frame = sc->last_out; frame; frame = fn) {
        frame->last->next = cl;
        cl = frame->first;

        fn = frame->next;
        frame->next = out;
        out = frame;

        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "spdy frame out: %p sid:%ui prio:%ui bl:%d len:%uz",
                       out, out->stream ? out->stream->id : 0, out->priority,
                       out->blocked, out->length);
    }

    cl = c->send_chain(c, cl, 0);

    if (cl == NGX_CHAIN_ERROR) {
        c->error = 1;

        if (!sc->blocked) {
            ngx_post_event(wev, &ngx_posted_events);
        }

        return NGX_ERROR;
    }

    clcf = ngx_http_get_module_loc_conf(sc->http_connection->conf_ctx,
                                        ngx_http_core_module);

    if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
        return NGX_ERROR; /* FIXME */
    }

    if (cl) {
        ngx_add_timer(wev, clcf->send_timeout);

    } else {
        if (wev->timer_set) {
            ngx_del_timer(wev);
        }
    }

    for ( /* void */ ; out; out = fn) {
        fn = out->next;

        if (out->handler(sc, out) != NGX_OK) {
            out->blocked = 1;
            out->priority = NGX_SPDY_HIGHEST_PRIORITY;
            break;
        }

        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "spdy frame sent: %p sid:%ui bl:%d len:%uz",
                       out, out->stream ? out->stream->id : 0,
                       out->blocked, out->length);
    }

    frame = NULL;

    for ( /* void */ ; out; out = fn) {
        fn = out->next;
        out->next = frame;
        frame = out;
    }

    sc->last_out = frame;

    return NGX_OK;
}


static void
ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc)
{
    ngx_connection_t          *c;
    ngx_http_spdy_srv_conf_t  *sscf;

    if (sc->last_out || sc->processing) {
        return;
    }

    c = sc->connection;

    if (c->error) {
        ngx_http_close_connection(c);
        return;
    }

    if (c->buffered) {
        return;
    }

    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
                                        ngx_http_spdy_module);
    if (sc->incomplete) {
        ngx_add_timer(c->read, sscf->recv_timeout);
        return;
    }

    if (ngx_terminate || ngx_exiting) {
        ngx_http_close_connection(c);
        return;
    }

    ngx_destroy_pool(sc->pool);

    sc->pool = NULL;
    sc->free_ctl_frames = NULL;
    sc->free_fake_connections = NULL;

#if (NGX_HTTP_SSL)
    if (c->ssl) {
        ngx_ssl_free_buffer(c);
    }
#endif

    c->destroyed = 1;
    c->idle = 1;
    ngx_reusable_connection(c, 1);

    c->write->handler = ngx_http_empty_handler;
    c->read->handler = ngx_http_spdy_keepalive_handler;

    if (c->write->timer_set) {
        ngx_del_timer(c->write);
    }

    ngx_add_timer(c->read, sscf->keepalive_timeout);
}


static u_char *
ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    pos = ngx_proxy_protocol_parse(sc->connection, pos, end);

    if (pos == NULL) {
        return ngx_http_spdy_state_protocol_error(sc);
    }

    sc->connection->log->action = "processing SPDY";

    return ngx_http_spdy_state_complete(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    uint32_t    head, flen;
    ngx_uint_t  type;

    if (end - pos < NGX_SPDY_FRAME_HEADER_SIZE) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_head);
    }

    head = ngx_spdy_frame_parse_uint32(pos);

    pos += sizeof(uint32_t);

    flen = ngx_spdy_frame_parse_uint32(pos);

    sc->flags = ngx_spdy_frame_flags(flen);
    sc->length = ngx_spdy_frame_length(flen);

    pos += sizeof(uint32_t);

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy process frame head:%08XD f:%Xd l:%uz",
                   head, sc->flags, sc->length);

    if (ngx_spdy_ctl_frame_check(head)) {
        type = ngx_spdy_ctl_frame_type(head);

        switch (type) {

        case NGX_SPDY_SYN_STREAM:
            return ngx_http_spdy_state_syn_stream(sc, pos, end);

        case NGX_SPDY_SYN_REPLY:
            return ngx_http_spdy_state_protocol_error(sc);

        case NGX_SPDY_RST_STREAM:
            return ngx_http_spdy_state_rst_stream(sc, pos, end);

        case NGX_SPDY_SETTINGS:
            return ngx_http_spdy_state_settings(sc, pos, end);

        case NGX_SPDY_PING:
            return ngx_http_spdy_state_ping(sc, pos, end);

        case NGX_SPDY_GOAWAY:
            return ngx_http_spdy_state_skip(sc, pos, end); /* TODO */

        case NGX_SPDY_HEADERS:
            return ngx_http_spdy_state_protocol_error(sc);

        case NGX_SPDY_WINDOW_UPDATE:
            return ngx_http_spdy_state_window_update(sc, pos, end);

        default:
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                           "spdy control frame with unknown type %ui", type);
            return ngx_http_spdy_state_skip(sc, pos, end);
        }
    }

    if (ngx_spdy_data_frame_check(head)) {
        sc->stream = ngx_http_spdy_get_stream_by_id(sc, head);
        return ngx_http_spdy_state_data(sc, pos, end);
    }


    /* TODO version & type check */
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy unknown frame");

    return ngx_http_spdy_state_protocol_error(sc);
}


static u_char *
ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    ngx_uint_t                 sid, prio;
    ngx_http_spdy_stream_t    *stream;
    ngx_http_spdy_srv_conf_t  *sscf;

    if (end - pos < NGX_SPDY_SYN_STREAM_SIZE) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_syn_stream);
    }

    if (sc->length <= NGX_SPDY_SYN_STREAM_SIZE) {
        /* TODO logging */
        return ngx_http_spdy_state_protocol_error(sc);
    }

    sc->length -= NGX_SPDY_SYN_STREAM_SIZE;

    sid = ngx_spdy_frame_parse_sid(pos);
    prio = pos[8] >> 5;

    pos += NGX_SPDY_SYN_STREAM_SIZE;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy SYN_STREAM frame sid:%ui prio:%ui", sid, prio);

    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
                                        ngx_http_spdy_module);

    if (sc->processing >= sscf->concurrent_streams) {

        ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                      "spdy concurrent streams exceeded %ui", sc->processing);

        if (ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM,
                                          prio)
            != NGX_OK)
        {
            return ngx_http_spdy_state_internal_error(sc);
        }

        return ngx_http_spdy_state_headers_skip(sc, pos, end);
    }

    stream = ngx_http_spdy_create_stream(sc, sid, prio);
    if (stream == NULL) {
        return ngx_http_spdy_state_internal_error(sc);
    }

    stream->in_closed = (sc->flags & NGX_SPDY_FLAG_FIN) ? 1 : 0;

    stream->request->request_length = NGX_SPDY_FRAME_HEADER_SIZE
                                      + NGX_SPDY_SYN_STREAM_SIZE
                                      + sc->length;

    sc->stream = stream;

    sc->last_sid = sid;

    return ngx_http_spdy_state_headers(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    int                  z;
    size_t               size;
    ngx_buf_t           *buf;
    ngx_int_t            rc;
    ngx_uint_t           complete;
    ngx_http_request_t  *r;

    size = end - pos;

    if (size == 0) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_headers);
    }

    if (size >= sc->length) {
        size = sc->length;
        complete = 1;

    } else {
        complete = 0;
    }

    r = sc->stream->request;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "spdy process HEADERS %uz of %uz", size, sc->length);

    buf = r->header_in;

    sc->zstream_in.next_in = pos;
    sc->zstream_in.avail_in = size;
    sc->zstream_in.next_out = buf->last;

    /* one byte is reserved for null-termination of the last header value */
    sc->zstream_in.avail_out = buf->end - buf->last - 1;

    z = inflate(&sc->zstream_in, Z_NO_FLUSH);

    if (z == Z_NEED_DICT) {
        z = inflateSetDictionary(&sc->zstream_in, ngx_http_spdy_dict,
                                 sizeof(ngx_http_spdy_dict));
        if (z != Z_OK) {
            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "spdy inflateSetDictionary() failed: %d", z);
            ngx_http_spdy_close_stream(sc->stream, 0);
            return ngx_http_spdy_state_protocol_error(sc);
        }

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "spdy inflateSetDictionary(): %d", z);

        z = sc->zstream_in.avail_in ? inflate(&sc->zstream_in, Z_NO_FLUSH)
                                    : Z_OK;
    }

    if (z != Z_OK) {
        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                      "spdy inflate() failed: %d", z);
        ngx_http_spdy_close_stream(sc->stream, 0);
        return ngx_http_spdy_state_protocol_error(sc);
    }

    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
                   sc->zstream_in.next_in, sc->zstream_in.next_out,
                   sc->zstream_in.avail_in, sc->zstream_in.avail_out,
                   z);

    sc->length -= sc->zstream_in.next_in - pos;
    pos = sc->zstream_in.next_in;

    buf->last = sc->zstream_in.next_out;

    if (r->headers_in.headers.part.elts == NULL) {

        if (buf->last - buf->pos < NGX_SPDY_NV_NUM_SIZE) {

            if (complete) {
                ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                              "client sent SYN_STREAM frame "
                              "with invalid HEADERS block");
                ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_BAD_REQUEST);
                return ngx_http_spdy_state_protocol_error(sc);
            }

            return ngx_http_spdy_state_save(sc, pos, end,
                                            ngx_http_spdy_state_headers);
        }

        sc->entries = ngx_spdy_frame_parse_uint32(buf->pos);

        buf->pos += NGX_SPDY_NV_NUM_SIZE;

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "spdy HEADERS block consists of %ui entries",
                       sc->entries);

        if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
                          sizeof(ngx_table_elt_t))
            != NGX_OK)
        {
            ngx_http_spdy_close_stream(sc->stream,
                                       NGX_HTTP_INTERNAL_SERVER_ERROR);
            return ngx_http_spdy_state_headers_error(sc, pos, end);
        }

        if (ngx_array_init(&r->headers_in.cookies, r->pool, 2,
                           sizeof(ngx_table_elt_t *))
            != NGX_OK)
        {
            ngx_http_spdy_close_stream(sc->stream,
                                       NGX_HTTP_INTERNAL_SERVER_ERROR);
            return ngx_http_spdy_state_headers_error(sc, pos, end);
        }
    }

    while (sc->entries) {

        rc = ngx_http_spdy_parse_header(r);

        switch (rc) {

        case NGX_DONE:
            sc->entries--;

        case NGX_OK:
            break;

        case NGX_AGAIN:

            if (sc->zstream_in.avail_in) {

                rc = ngx_http_spdy_alloc_large_header_buffer(r);

                if (rc == NGX_DECLINED) {
                    /* TODO logging */
                    ngx_http_finalize_request(r,
                                            NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
                    return ngx_http_spdy_state_headers_error(sc, pos, end);
                }

                if (rc != NGX_OK) {
                    ngx_http_spdy_close_stream(sc->stream,
                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
                    return ngx_http_spdy_state_headers_error(sc, pos, end);
                }

                /* null-terminate the last processed header name or value */
                *buf->pos = '\0';

                buf = r->header_in;

                sc->zstream_in.next_out = buf->last;

                /* one byte is reserved for null-termination */
                sc->zstream_in.avail_out = buf->end - buf->last - 1;

                z = inflate(&sc->zstream_in, Z_NO_FLUSH);

                if (z != Z_OK) {
                    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                                  "spdy inflate() failed: %d", z);
                    ngx_http_spdy_close_stream(sc->stream, 0);
                    return ngx_http_spdy_state_protocol_error(sc);
                }

                sc->length -= sc->zstream_in.next_in - pos;
                pos = sc->zstream_in.next_in;

                buf->last = sc->zstream_in.next_out;

                continue;
            }

            if (complete) {
                /* TODO: improve error message */
                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                               "spdy again while last chunk");
                ngx_http_spdy_close_stream(sc->stream, 0);
                return ngx_http_spdy_state_protocol_error(sc);
            }

            return ngx_http_spdy_state_save(sc, pos, end,
                                            ngx_http_spdy_state_headers);

        case NGX_HTTP_PARSE_INVALID_REQUEST:

            /* TODO: improve error message */
            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "client sent invalid header line");

            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);

            return ngx_http_spdy_state_headers_error(sc, pos, end);

        default: /* NGX_HTTP_PARSE_INVALID_HEADER */

            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "client sent invalid HEADERS spdy frame");
            ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_BAD_REQUEST);
            return ngx_http_spdy_state_protocol_error(sc);
        }

        /* a header line has been parsed successfully */

        rc = ngx_http_spdy_handle_request_header(r);

        if (rc != NGX_OK) {
            if (rc == NGX_HTTP_PARSE_INVALID_HEADER) {
                ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                              "client sent invalid HEADERS spdy frame");
                ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_BAD_REQUEST);
                return ngx_http_spdy_state_protocol_error(sc);
            }

            if (rc == NGX_HTTP_PARSE_INVALID_REQUEST) {
                ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
            }

            return ngx_http_spdy_state_headers_error(sc, pos, end);
        }
    }

    if (buf->pos != buf->last || sc->zstream_in.avail_in) {
        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                      "client sent SYN_STREAM frame "
                      "with invalid HEADERS block");
        ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_BAD_REQUEST);
        return ngx_http_spdy_state_protocol_error(sc);
    }

    if (!complete) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_headers);
    }

    /* null-terminate the last header value */
    *buf->pos = '\0';

    ngx_http_spdy_run_request(r);

    return ngx_http_spdy_state_complete(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    if (sc->connection->error) {
        return ngx_http_spdy_state_internal_error(sc);
    }

    return ngx_http_spdy_state_headers_skip(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    int     n;
    size_t  size;
    u_char  buffer[NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE];

    if (sc->length == 0) {
        return ngx_http_spdy_state_complete(sc, pos, end);
    }

    size = end - pos;

    if (size == 0) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_headers_skip);
    }

    sc->zstream_in.next_in = pos;
    sc->zstream_in.avail_in = (size < sc->length) ? size : sc->length;

    while (sc->zstream_in.avail_in) {
        sc->zstream_in.next_out = buffer;
        sc->zstream_in.avail_out = NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE;

        n = inflate(&sc->zstream_in, Z_NO_FLUSH);

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                       "spdy inflate(): %d", n);

        if (n != Z_OK) {
            /* TODO: logging */
            return ngx_http_spdy_state_protocol_error(sc);
        }
    }

    pos = sc->zstream_in.next_in;

    if (size < sc->length) {
        sc->length -= size;
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_headers_skip);
    }

    return ngx_http_spdy_state_complete(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    size_t                   delta;
    ngx_uint_t               sid;
    ngx_event_t             *wev;
    ngx_queue_t             *q;
    ngx_http_spdy_stream_t  *stream;

    if (end - pos < NGX_SPDY_WINDOW_UPDATE_SIZE) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_window_update);
    }

    if (sc->length != NGX_SPDY_WINDOW_UPDATE_SIZE) {
        ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                      "client sent WINDOW_UPDATE frame "
                      "with incorrect length %uz", sc->length);

        return ngx_http_spdy_state_protocol_error(sc);
    }

    sid = ngx_spdy_frame_parse_sid(pos);

    pos += NGX_SPDY_SID_SIZE;

    delta = ngx_spdy_frame_parse_delta(pos);

    pos += NGX_SPDY_DELTA_SIZE;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy WINDOW_UPDATE sid:%ui delta:%ui", sid, delta);

    if (sid) {
        stream = ngx_http_spdy_get_stream_by_id(sc, sid);

        if (stream == NULL) {
            ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                          "client sent WINDOW_UPDATE frame "
                          "for unknown stream %ui", sid);

            if (ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_INVALID_STREAM,
                                              NGX_SPDY_LOWEST_PRIORITY)
                == NGX_ERROR)
            {
                return ngx_http_spdy_state_internal_error(sc);
            }

            return ngx_http_spdy_state_complete(sc, pos, end);
        }

        if (stream->send_window > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) {

            ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                          "client violated flow control for stream %ui: "
                          "received WINDOW_UPDATE frame with delta %uz "
                          "that is not allowed for window %z",
                          sid, delta, stream->send_window);

            if (ngx_http_spdy_terminate_stream(sc, stream,
                                               NGX_SPDY_FLOW_CONTROL_ERROR)
                == NGX_ERROR)
            {
                return ngx_http_spdy_state_internal_error(sc);
            }

            return ngx_http_spdy_state_complete(sc, pos, end);
        }

        stream->send_window += delta;

        if (stream->exhausted) {
            stream->exhausted = 0;

            wev = stream->request->connection->write;

            if (!wev->timer_set) {
                wev->delayed = 0;
                wev->handler(wev);
            }
        }

    } else {
        sc->send_window += delta;

        if (sc->send_window > NGX_SPDY_MAX_WINDOW) {
            ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                          "client violated connection flow control: "
                          "received WINDOW_UPDATE frame with delta %uz "
                          "that is not allowed for window %uz",
                          delta, sc->send_window);

            return ngx_http_spdy_state_protocol_error(sc);
        }

        while (!ngx_queue_empty(&sc->waiting)) {
            q = ngx_queue_head(&sc->waiting);

            ngx_queue_remove(q);

            stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue);

            stream->handled = 0;

            wev = stream->request->connection->write;

            if (!wev->timer_set) {
                wev->delayed = 0;
                wev->handler(wev);

                if (sc->send_window == 0) {
                    break;
                }
            }
        }
    }

    return ngx_http_spdy_state_complete(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    ngx_http_spdy_stream_t  *stream;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy DATA frame");

    if (sc->length > sc->recv_window) {
        ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                      "client violated connection flow control: length of "
                      "received DATA frame %uz, while available window %uz",
                      sc->length, sc->recv_window);

        return ngx_http_spdy_state_protocol_error(sc);
    }

    sc->recv_window -= sc->length;

    if (sc->recv_window < NGX_SPDY_MAX_WINDOW / 4) {

        if (ngx_http_spdy_send_window_update(sc, 0,
                                             NGX_SPDY_MAX_WINDOW
                                             - sc->recv_window)
            == NGX_ERROR)
        {
            return ngx_http_spdy_state_internal_error(sc);
        }

        sc->recv_window = NGX_SPDY_MAX_WINDOW;
    }

    stream = sc->stream;

    if (stream == NULL) {
        return ngx_http_spdy_state_skip(sc, pos, end);
    }

    if (sc->length > stream->recv_window) {
        ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                      "client violated flow control for stream %ui: length of "
                      "received DATA frame %uz, while available window %uz",
                      stream->id, sc->length, stream->recv_window);

        if (ngx_http_spdy_terminate_stream(sc, stream,
                                           NGX_SPDY_FLOW_CONTROL_ERROR)
            == NGX_ERROR)
        {
            return ngx_http_spdy_state_internal_error(sc);
        }

        return ngx_http_spdy_state_skip(sc, pos, end);
    }

    stream->recv_window -= sc->length;

    if (stream->recv_window < NGX_SPDY_STREAM_WINDOW / 4) {

        if (ngx_http_spdy_send_window_update(sc, stream->id,
                                             NGX_SPDY_STREAM_WINDOW
                                             - stream->recv_window)
            == NGX_ERROR)
        {
            return ngx_http_spdy_state_internal_error(sc);
        }

        stream->recv_window = NGX_SPDY_STREAM_WINDOW;
    }

    if (stream->in_closed) {
        ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                      "client sent DATA frame for half closed stream %ui",
                      stream->id);

        if (ngx_http_spdy_terminate_stream(sc, stream,
                                           NGX_SPDY_STREAM_ALREADY_CLOSED)
            == NGX_ERROR)
        {
            return ngx_http_spdy_state_internal_error(sc);
        }

        return ngx_http_spdy_state_skip(sc, pos, end);
    }

    return ngx_http_spdy_state_read_data(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    size_t                     size;
    ssize_t                    n;
    ngx_buf_t                 *buf;
    ngx_int_t                  rc;
    ngx_temp_file_t           *tf;
    ngx_http_request_t        *r;
    ngx_http_spdy_stream_t    *stream;
    ngx_http_request_body_t   *rb;
    ngx_http_core_loc_conf_t  *clcf;

    stream = sc->stream;

    if (stream == NULL) {
        return ngx_http_spdy_state_skip(sc, pos, end);
    }

    if (stream->skip_data) {

        if (sc->flags & NGX_SPDY_FLAG_FIN) {
            stream->in_closed = 1;
        }

        /* TODO log and accounting */
        return ngx_http_spdy_state_skip(sc, pos, end);
    }

    size = end - pos;

    if (size > sc->length) {
        size = sc->length;
    }

    r = stream->request;

    if (r->request_body == NULL
        && ngx_http_spdy_init_request_body(r) != NGX_OK)
    {
        stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR;
        return ngx_http_spdy_state_skip(sc, pos, end);
    }

    rb = r->request_body;
    tf = rb->temp_file;
    buf = rb->buf;

    if (size) {
        rb->rest += size;

        if (r->headers_in.content_length_n != -1
            && r->headers_in.content_length_n < rb->rest)
        {
            /* TODO logging */
            stream->skip_data = NGX_SPDY_DATA_ERROR;
            goto error;

        } else {
            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

            if (clcf->client_max_body_size
                && clcf->client_max_body_size < rb->rest)
            {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                              "client intended to send too large chunked "
                              "body: %O bytes",
                              rb->rest);

                stream->skip_data = NGX_SPDY_DATA_ERROR;
                goto error;
            }
        }

        sc->length -= size;

        if (tf) {
            buf->start = pos;
            buf->pos = pos;

            pos += size;

            buf->end = pos;
            buf->last = pos;

            n = ngx_write_chain_to_temp_file(tf, rb->bufs);

            /* TODO: n == 0 or not complete and level event */

            if (n == NGX_ERROR) {
                stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR;
                goto error;
            }

            tf->offset += n;

        } else {
            buf->last = ngx_cpymem(buf->last, pos, size);
            pos += size;
        }

        r->request_length += size;
    }

    if (sc->length) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_read_data);
    }

    if (sc->flags & NGX_SPDY_FLAG_FIN) {

        stream->in_closed = 1;

        if (r->headers_in.content_length_n < 0) {
            r->headers_in.content_length_n = rb->rest;

        } else if (r->headers_in.content_length_n != rb->rest) {
            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "client prematurely closed stream: "
                          "%O of %O bytes of request body received",
                          rb->rest, r->headers_in.content_length_n);

            stream->skip_data = NGX_SPDY_DATA_ERROR;
            goto error;
        }

        if (tf) {
            ngx_memzero(buf, sizeof(ngx_buf_t));

            buf->in_file = 1;
            buf->file_last = tf->file.offset;
            buf->file = &tf->file;

            rb->buf = NULL;
        }

        if (rb->post_handler) {
            r->read_event_handler = ngx_http_block_reading;
            rb->post_handler(r);
        }
    }

    return ngx_http_spdy_state_complete(sc, pos, end);

error:

    if (rb->post_handler) {

        if (stream->skip_data == NGX_SPDY_DATA_ERROR) {
            rc = (r->headers_in.content_length_n == -1)
                 ? NGX_HTTP_REQUEST_ENTITY_TOO_LARGE
                 : NGX_HTTP_BAD_REQUEST;

        } else {
            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_finalize_request(r, rc);
    }

    return ngx_http_spdy_state_skip(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    ngx_uint_t               sid, status;
    ngx_event_t             *ev;
    ngx_connection_t        *fc;
    ngx_http_spdy_stream_t  *stream;

    if (end - pos < NGX_SPDY_RST_STREAM_SIZE) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_rst_stream);
    }

    if (sc->length != NGX_SPDY_RST_STREAM_SIZE) {
        ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                      "client sent RST_STREAM frame with incorrect length %uz",
                      sc->length);

        return ngx_http_spdy_state_protocol_error(sc);
    }

    sid = ngx_spdy_frame_parse_sid(pos);

    pos += NGX_SPDY_SID_SIZE;

    status = ngx_spdy_frame_parse_uint32(pos);

    pos += sizeof(uint32_t);

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy RST_STREAM sid:%ui st:%ui", sid, status);

    stream = ngx_http_spdy_get_stream_by_id(sc, sid);
    if (stream == NULL) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                       "unknown stream, probably it has been closed already");
        return ngx_http_spdy_state_complete(sc, pos, end);
    }

    stream->in_closed = 1;
    stream->out_closed = 1;

    fc = stream->request->connection;
    fc->error = 1;

    switch (status) {

    case NGX_SPDY_CANCEL:
        ngx_log_error(NGX_LOG_INFO, fc->log, 0,
                      "client canceled stream %ui", sid);
        break;

    case NGX_SPDY_INTERNAL_ERROR:
        ngx_log_error(NGX_LOG_INFO, fc->log, 0,
                      "client terminated stream %ui because of internal error",
                      sid);
        break;

    default:
        ngx_log_error(NGX_LOG_INFO, fc->log, 0,
                      "client terminated stream %ui with status %ui",
                      sid, status);
        break;
    }

    ev = fc->read;
    ev->handler(ev);

    return ngx_http_spdy_state_complete(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    u_char                     *p;
    ngx_buf_t                  *buf;
    ngx_http_spdy_out_frame_t  *frame;

    if (end - pos < NGX_SPDY_PING_SIZE) {
        return ngx_http_spdy_state_save(sc, pos, end,
                                        ngx_http_spdy_state_ping);
    }

    if (sc->length != NGX_SPDY_PING_SIZE) {
        /* TODO logging */
        return ngx_http_spdy_state_protocol_error(sc);
    }

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy PING frame");

    frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_PING_SIZE,
                                        NGX_SPDY_HIGHEST_PRIORITY);
    if (frame == NULL) {
        return ngx_http_spdy_state_internal_error(sc);
    }

    buf = frame->first->buf;

    p = buf->pos;

    p = ngx_spdy_frame_write_head(p, NGX_SPDY_PING);
    p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_PING_SIZE);

    p = ngx_cpymem(p, pos, NGX_SPDY_PING_SIZE);

    buf->last = p;

    ngx_http_spdy_queue_frame(sc, frame);

    pos += NGX_SPDY_PING_SIZE;

    return ngx_http_spdy_state_complete(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    size_t  size;

    size = end - pos;

    if (size < sc->length) {
        sc->length -= size;
        return ngx_http_spdy_state_save(sc, end, end,
                                        ngx_http_spdy_state_skip);
    }

    return ngx_http_spdy_state_complete(sc, pos + sc->length, end);
}


static u_char *
ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    ngx_uint_t  fid, val;

    if (sc->entries == 0) {

        if (end - pos < NGX_SPDY_SETTINGS_NUM_SIZE) {
            return ngx_http_spdy_state_save(sc, pos, end,
                                            ngx_http_spdy_state_settings);
        }

        sc->entries = ngx_spdy_frame_parse_uint32(pos);

        pos += NGX_SPDY_SETTINGS_NUM_SIZE;
        sc->length -= NGX_SPDY_SETTINGS_NUM_SIZE;

        if (sc->length < sc->entries * NGX_SPDY_SETTINGS_PAIR_SIZE) {
            /* TODO logging */
            return ngx_http_spdy_state_protocol_error(sc);
        }

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                       "spdy SETTINGS frame consists of %ui entries",
                       sc->entries);
    }

    while (sc->entries) {
        if (end - pos < NGX_SPDY_SETTINGS_PAIR_SIZE) {
            return ngx_http_spdy_state_save(sc, pos, end,
                                            ngx_http_spdy_state_settings);
        }

        sc->entries--;
        sc->length -= NGX_SPDY_SETTINGS_PAIR_SIZE;

        fid = ngx_spdy_frame_parse_uint32(pos);

        pos += NGX_SPDY_SETTINGS_FID_SIZE;

        val = ngx_spdy_frame_parse_uint32(pos);

        pos += NGX_SPDY_SETTINGS_VAL_SIZE;

        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                       "spdy SETTINGS entry fl:%ui id:%ui val:%ui",
                       ngx_spdy_frame_flags(fid), ngx_spdy_frame_id(fid), val);

        if (ngx_spdy_frame_flags(fid) == NGX_SPDY_SETTINGS_FLAG_PERSISTED) {
            continue;
        }

        switch (ngx_spdy_frame_id(fid)) {

        case NGX_SPDY_SETTINGS_INIT_WINDOW:

            if (val > NGX_SPDY_MAX_WINDOW) {
                ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
                              "client sent SETTINGS frame with "
                              "incorrect INIT_WINDOW value: %ui", val);

                return ngx_http_spdy_state_protocol_error(sc);
            }

            if (ngx_http_spdy_adjust_windows(sc, val - sc->init_window)
                != NGX_OK)
            {
                return ngx_http_spdy_state_internal_error(sc);
            }

            sc->init_window = val;

            continue;
        }
    }

    return ngx_http_spdy_state_complete(sc, pos, end);
}


static u_char *
ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, u_char *pos,
    u_char *end)
{
    sc->handler = ngx_http_spdy_state_head;
    return pos;
}


static u_char *
ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc,
    u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler)
{
    size_t  size;

    size = end - pos;

    if (size > NGX_SPDY_STATE_BUFFER_SIZE) {
        ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0,
                      "spdy state buffer overflow: "
                      "%uz bytes required", size);
        return ngx_http_spdy_state_internal_error(sc);
    }

    ngx_memcpy(sc->buffer, pos, NGX_SPDY_STATE_BUFFER_SIZE);

    sc->buffer_used = size;
    sc->handler = handler;
    sc->incomplete = 1;

    return end;
}


static u_char *
ngx_http_spdy_state_protocol_error(ngx_http_spdy_connection_t *sc)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy state protocol error");

    /* TODO */
    ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST);
    return NULL;
}


static u_char *
ngx_http_spdy_state_internal_error(ngx_http_spdy_connection_t *sc)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy state internal error");

    /* TODO */
    ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR);
    return NULL;
}


static ngx_int_t
ngx_http_spdy_send_window_update(ngx_http_spdy_connection_t *sc, ngx_uint_t sid,
    ngx_uint_t delta)
{
    u_char                     *p;
    ngx_buf_t                  *buf;
    ngx_http_spdy_out_frame_t  *frame;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy write WINDOW_UPDATE sid:%ui delta:%ui", sid, delta);

    frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_WINDOW_UPDATE_SIZE,
                                        NGX_SPDY_HIGHEST_PRIORITY);
    if (frame == NULL) {
        return NGX_ERROR;
    }

    buf = frame->first->buf;

    p = buf->pos;

    p = ngx_spdy_frame_write_head(p, NGX_SPDY_WINDOW_UPDATE);
    p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_WINDOW_UPDATE_SIZE);

    p = ngx_spdy_frame_write_sid(p, sid);
    p = ngx_spdy_frame_aligned_write_uint32(p, delta);

    buf->last = p;

    ngx_http_spdy_queue_frame(sc, frame);

    return NGX_OK;
}


static ngx_int_t
ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t sid,
    ngx_uint_t status, ngx_uint_t priority)
{
    u_char                     *p;
    ngx_buf_t                  *buf;
    ngx_http_spdy_out_frame_t  *frame;

    if (sc->connection->error) {
        return NGX_OK;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy write RST_STREAM sid:%ui st:%ui", sid, status);

    frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_RST_STREAM_SIZE,
                                        priority);
    if (frame == NULL) {
        return NGX_ERROR;
    }

    buf = frame->first->buf;

    p = buf->pos;

    p = ngx_spdy_frame_write_head(p, NGX_SPDY_RST_STREAM);
    p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_RST_STREAM_SIZE);

    p = ngx_spdy_frame_write_sid(p, sid);
    p = ngx_spdy_frame_aligned_write_uint32(p, status);

    buf->last = p;

    ngx_http_spdy_queue_frame(sc, frame);

    return NGX_OK;
}


#if 0
static ngx_int_t
ngx_http_spdy_send_goaway(ngx_http_spdy_connection_t *sc)
{
    u_char                     *p;
    ngx_buf_t                  *buf;
    ngx_http_spdy_out_frame_t  *frame;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy create GOAWAY sid:%ui", sc->last_sid);

    frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_GOAWAY_SIZE,
                                        NGX_SPDY_HIGHEST_PRIORITY);
    if (frame == NULL) {
        return NGX_ERROR;
    }

    buf = frame->first->buf;

    p = buf->pos;

    p = ngx_spdy_frame_write_head(p, NGX_SPDY_GOAWAY);
    p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_GOAWAY_SIZE);

    p = ngx_spdy_frame_write_sid(p, sc->last_sid);

    buf->last = p;

    ngx_http_spdy_queue_frame(sc, frame);

    return NGX_OK;
}
#endif


static ngx_int_t
ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc)
{
    u_char                     *p;
    ngx_buf_t                  *buf;
    ngx_chain_t                *cl;
    ngx_http_spdy_srv_conf_t   *sscf;
    ngx_http_spdy_out_frame_t  *frame;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy create SETTINGS frame");

    frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t));
    if (frame == NULL) {
        return NGX_ERROR;
    }

    cl = ngx_alloc_chain_link(sc->pool);
    if (cl == NULL) {
        return NGX_ERROR;
    }

    buf = ngx_create_temp_buf(sc->pool, NGX_SPDY_FRAME_HEADER_SIZE
                                        + NGX_SPDY_SETTINGS_NUM_SIZE
                                        + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE);
    if (buf == NULL) {
        return NGX_ERROR;
    }

    buf->last_buf = 1;

    cl->buf = buf;
    cl->next = NULL;

    frame->first = cl;
    frame->last = cl;
    frame->handler = ngx_http_spdy_settings_frame_handler;
    frame->stream = NULL;
#if (NGX_DEBUG)
    frame->length = NGX_SPDY_SETTINGS_NUM_SIZE
                    + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE;
#endif
    frame->priority = NGX_SPDY_HIGHEST_PRIORITY;
    frame->blocked = 0;

    p = buf->pos;

    p = ngx_spdy_frame_write_head(p, NGX_SPDY_SETTINGS);
    p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_CLEAR_SETTINGS,
                                           NGX_SPDY_SETTINGS_NUM_SIZE
                                           + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE);

    p = ngx_spdy_frame_aligned_write_uint32(p, 2);

    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
                                        ngx_http_spdy_module);

    p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_MAX_STREAMS);
    p = ngx_spdy_frame_aligned_write_uint32(p, sscf->concurrent_streams);

    p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_INIT_WINDOW);
    p = ngx_spdy_frame_aligned_write_uint32(p, NGX_SPDY_STREAM_WINDOW);

    buf->last = p;

    ngx_http_spdy_queue_frame(sc, frame);

    return NGX_OK;
}


ngx_int_t
ngx_http_spdy_settings_frame_handler(ngx_http_spdy_connection_t *sc,
    ngx_http_spdy_out_frame_t *frame)
{
    ngx_buf_t  *buf;

    buf = frame->first->buf;

    if (buf->pos != buf->last) {
        return NGX_AGAIN;
    }

    ngx_free_chain(sc->pool, frame->first);

    return NGX_OK;
}


static ngx_http_spdy_out_frame_t *
ngx_http_spdy_get_ctl_frame(ngx_http_spdy_connection_t *sc, size_t length,
    ngx_uint_t priority)
{
    ngx_chain_t                *cl;
    ngx_http_spdy_out_frame_t  *frame;

    frame = sc->free_ctl_frames;

    if (frame) {
        sc->free_ctl_frames = frame->next;

        cl = frame->first;
        cl->buf->pos = cl->buf->start;

    } else {
        frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t));
        if (frame == NULL) {
            return NULL;
        }

        cl = ngx_alloc_chain_link(sc->pool);
        if (cl == NULL) {
            return NULL;
        }

        cl->buf = ngx_create_temp_buf(sc->pool,
                                      NGX_SPDY_CTL_FRAME_BUFFER_SIZE);
        if (cl->buf == NULL) {
            return NULL;
        }

        cl->buf->last_buf = 1;

        frame->first = cl;
        frame->last = cl;
        frame->handler = ngx_http_spdy_ctl_frame_handler;
        frame->stream = NULL;
    }

#if (NGX_DEBUG)
    if (length > NGX_SPDY_CTL_FRAME_BUFFER_SIZE - NGX_SPDY_FRAME_HEADER_SIZE) {
        ngx_log_error(NGX_LOG_ALERT, sc->pool->log, 0,
                      "requested control frame is too big: %uz", length);
        return NULL;
    }

    frame->length = length;
#endif

    frame->priority = priority;
    frame->blocked = 0;

    return frame;
}


static ngx_int_t
ngx_http_spdy_ctl_frame_handler(ngx_http_spdy_connection_t *sc,
    ngx_http_spdy_out_frame_t *frame)
{
    ngx_buf_t  *buf;

    buf = frame->first->buf;

    if (buf->pos != buf->last) {
        return NGX_AGAIN;
    }

    frame->next = sc->free_ctl_frames;
    sc->free_ctl_frames = frame;

    return NGX_OK;
}


static ngx_http_spdy_stream_t *
ngx_http_spdy_create_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t id,
    ngx_uint_t priority)
{
    ngx_log_t                 *log;
    ngx_uint_t                 index;
    ngx_event_t               *rev, *wev;
    ngx_connection_t          *fc;
    ngx_http_log_ctx_t        *ctx;
    ngx_http_request_t        *r;
    ngx_http_spdy_stream_t    *stream;
    ngx_http_core_srv_conf_t  *cscf;
    ngx_http_spdy_srv_conf_t  *sscf;

    fc = sc->free_fake_connections;

    if (fc) {
        sc->free_fake_connections = fc->data;

        rev = fc->read;
        wev = fc->write;
        log = fc->log;
        ctx = log->data;

    } else {
        fc = ngx_palloc(sc->pool, sizeof(ngx_connection_t));
        if (fc == NULL) {
            return NULL;
        }

        rev = ngx_palloc(sc->pool, sizeof(ngx_event_t));
        if (rev == NULL) {
            return NULL;
        }

        wev = ngx_palloc(sc->pool, sizeof(ngx_event_t));
        if (wev == NULL) {
            return NULL;
        }

        log = ngx_palloc(sc->pool, sizeof(ngx_log_t));
        if (log == NULL) {
            return NULL;
        }

        ctx = ngx_palloc(sc->pool, sizeof(ngx_http_log_ctx_t));
        if (ctx == NULL) {
            return NULL;
        }

        ctx->connection = fc;
        ctx->request = NULL;
    }

    ngx_memcpy(log, sc->connection->log, sizeof(ngx_log_t));

    log->data = ctx;

    ngx_memzero(rev, sizeof(ngx_event_t));

    rev->data = fc;
    rev->ready = 1;
    rev->handler = ngx_http_spdy_close_stream_handler;
    rev->log = log;

    ngx_memcpy(wev, rev, sizeof(ngx_event_t));

    wev->write = 1;

    ngx_memcpy(fc, sc->connection, sizeof(ngx_connection_t));

    fc->data = sc->http_connection;
    fc->read = rev;
    fc->write = wev;
    fc->sent = 0;
    fc->log = log;
    fc->buffered = 0;
    fc->sndlowat = 1;
    fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;

    r = ngx_http_create_request(fc);
    if (r == NULL) {
        return NULL;
    }

    r->valid_location = 1;

    fc->data = r;
    sc->connection->requests++;

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

    r->header_in = ngx_create_temp_buf(r->pool,
                                       cscf->client_header_buffer_size);
    if (r->header_in == NULL) {
        ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NULL;
    }

    r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;

    stream = ngx_pcalloc(r->pool, sizeof(ngx_http_spdy_stream_t));
    if (stream == NULL) {
        ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NULL;
    }

    r->spdy_stream = stream;

    stream->id = id;
    stream->request = r;
    stream->connection = sc;

    stream->send_window = sc->init_window;
    stream->recv_window = NGX_SPDY_STREAM_WINDOW;

    stream->priority = priority;

    sscf = ngx_http_get_module_srv_conf(r, ngx_http_spdy_module);

    index = ngx_http_spdy_stream_index(sscf, id);

    stream->index = sc->streams_index[index];
    sc->streams_index[index] = stream;

    sc->processing++;

    return stream;
}


static ngx_http_spdy_stream_t *
ngx_http_spdy_get_stream_by_id(ngx_http_spdy_connection_t *sc,
    ngx_uint_t sid)
{
    ngx_http_spdy_stream_t    *stream;
    ngx_http_spdy_srv_conf_t  *sscf;

    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
                                        ngx_http_spdy_module);

    stream = sc->streams_index[ngx_http_spdy_stream_index(sscf, sid)];

    while (stream) {
        if (stream->id == sid) {
            return stream;
        }

        stream = stream->index;
    }

    return NULL;
}


static ngx_int_t
ngx_http_spdy_parse_header(ngx_http_request_t *r)
{
    u_char                     *p, *end, ch;
    ngx_uint_t                  hash;
    ngx_http_core_srv_conf_t   *cscf;

    enum {
        sw_name_len = 0,
        sw_name,
        sw_value_len,
        sw_value
    } state;

    state = r->state;

    p = r->header_in->pos;
    end = r->header_in->last;

    switch (state) {

    case sw_name_len:

        if (end - p < NGX_SPDY_NV_NLEN_SIZE) {
            return NGX_AGAIN;
        }

        r->lowcase_index = ngx_spdy_frame_parse_uint32(p);

        if (r->lowcase_index == 0) {
            return NGX_HTTP_PARSE_INVALID_HEADER;
        }

        /* null-terminate the previous header value */
        *p = '\0';

        p += NGX_SPDY_NV_NLEN_SIZE;

        r->invalid_header = 0;

        state = sw_name;

        /* fall through */

    case sw_name:

        if ((ngx_uint_t) (end - p) < r->lowcase_index) {
            break;
        }

        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

        r->header_name_start = p;
        r->header_name_end = p + r->lowcase_index;

        if (p[0] == ':') {
            p++;
        }

        hash = 0;

        for ( /* void */ ; p != r->header_name_end; p++) {

            ch = *p;

            hash = ngx_hash(hash, ch);

            if ((ch >= 'a' && ch <= 'z')
                || (ch == '-')
                || (ch >= '0' && ch <= '9')
                || (ch == '_' && cscf->underscores_in_headers))
            {
                continue;
            }

            switch (ch) {
            case '\0':
            case LF:
            case CR:
            case ':':
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            if (ch >= 'A' && ch <= 'Z') {
                return NGX_HTTP_PARSE_INVALID_HEADER;
            }

            r->invalid_header = 1;
        }

        r->header_hash = hash;

        state = sw_value_len;

        /* fall through */

    case sw_value_len:

        if (end - p < NGX_SPDY_NV_VLEN_SIZE) {
            break;
        }

        r->lowcase_index = ngx_spdy_frame_parse_uint32(p);

        /* null-terminate header name */
        *p = '\0';

        p += NGX_SPDY_NV_VLEN_SIZE;

        state = sw_value;

        /* fall through */

    case sw_value:

        if ((ngx_uint_t) (end - p) < r->lowcase_index) {
            break;
        }

        r->header_start = p;

        while (r->lowcase_index--) {
            ch = *p;

            if (ch == '\0') {

                if (p == r->header_start) {
                    return NGX_ERROR;
                }

                r->header_end = p;
                r->header_in->pos = p + 1;

                return NGX_OK;
            }

            if (ch == CR || ch == LF) {
                return NGX_HTTP_PARSE_INVALID_HEADER;
            }

            p++;
        }

        r->header_end = p;
        r->header_in->pos = p;

        r->state = 0;

        return NGX_DONE;
    }

    r->header_in->pos = p;
    r->state = state;

    return NGX_AGAIN;
}


static ngx_int_t
ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r)
{
    u_char                    *old, *new;
    size_t                     rest;
    ngx_buf_t                 *buf;
    ngx_http_spdy_stream_t    *stream;
    ngx_http_core_srv_conf_t  *cscf;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "spdy alloc large header buffer");

    stream = r->spdy_stream;

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

    if (stream->header_buffers
        == (ngx_uint_t) cscf->large_client_header_buffers.num)
    {
        return NGX_DECLINED;
    }

    rest = r->header_in->last - r->header_in->pos;

    if (rest >= cscf->large_client_header_buffers.size) {
        return NGX_DECLINED;
    }

    buf = ngx_create_temp_buf(r->pool, cscf->large_client_header_buffers.size);
    if (buf == NULL) {
        return NGX_ERROR;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "spdy large header alloc: %p %z",
                   buf->pos, buf->end - buf->last);

    old = r->header_in->pos;
    new = buf->pos;

    if (rest) {
        buf->last = ngx_cpymem(new, old, rest);
    }

    r->header_in = buf;

    stream->header_buffers++;

    return NGX_OK;
}


static ngx_int_t
ngx_http_spdy_handle_request_header(ngx_http_request_t *r)
{
    ngx_uint_t                       i;
    ngx_table_elt_t                 *h;
    ngx_http_core_srv_conf_t        *cscf;
    ngx_http_spdy_request_header_t  *sh;

    if (r->invalid_header) {
        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

        if (cscf->ignore_invalid_headers) {
            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "client sent invalid header: \"%*s\"",
                          r->header_end - r->header_name_start,
                          r->header_name_start);
            return NGX_OK;
        }

    }

    if (r->header_name_start[0] == ':') {
        r->header_name_start++;

        for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) {
            sh = &ngx_http_spdy_request_headers[i];

            if (sh->hash != r->header_hash
                || sh->len != r->header_name_end - r->header_name_start
                || ngx_strncmp(sh->header, r->header_name_start, sh->len) != 0)
            {
                continue;
            }

            return sh->handler(r);
        }

        return NGX_HTTP_PARSE_INVALID_REQUEST;
    }

    h = ngx_list_push(&r->headers_in.headers);
    if (h == NULL) {
        ngx_http_spdy_close_stream(r->spdy_stream,
                                   NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_ERROR;
    }

    h->hash = r->header_hash;

    h->key.len = r->header_name_end - r->header_name_start;
    h->key.data = r->header_name_start;

    h->value.len = r->header_end - r->header_start;
    h->value.data = r->header_start;

    h->lowcase_key = h->key.data;

    return NGX_OK;
}


void
ngx_http_spdy_request_headers_init(void)
{
    ngx_uint_t                       i;
    ngx_http_spdy_request_header_t  *h;

    for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) {
        h = &ngx_http_spdy_request_headers[i];
        h->hash = ngx_hash_key(h->header, h->len);
    }
}


static ngx_int_t
ngx_http_spdy_parse_method(ngx_http_request_t *r)
{
    size_t         k, len;
    ngx_uint_t     n;
    const u_char  *p, *m;

    /*
     * This array takes less than 256 sequential bytes,
     * and if typical CPU cache line size is 64 bytes,
     * it is prefetched for 4 load operations.
     */
    static const struct {
        u_char            len;
        const u_char      method[11];
        uint32_t          value;
    } tests[] = {
        { 3, "GET",       NGX_HTTP_GET },
        { 4, "POST",      NGX_HTTP_POST },
        { 4, "HEAD",      NGX_HTTP_HEAD },
        { 7, "OPTIONS",   NGX_HTTP_OPTIONS },
        { 8, "PROPFIND",  NGX_HTTP_PROPFIND },
        { 3, "PUT",       NGX_HTTP_PUT },
        { 5, "MKCOL",     NGX_HTTP_MKCOL },
        { 6, "DELETE",    NGX_HTTP_DELETE },
        { 4, "COPY",      NGX_HTTP_COPY },
        { 4, "MOVE",      NGX_HTTP_MOVE },
        { 9, "PROPPATCH", NGX_HTTP_PROPPATCH },
        { 4, "LOCK",      NGX_HTTP_LOCK },
        { 6, "UNLOCK",    NGX_HTTP_UNLOCK },
        { 5, "PATCH",     NGX_HTTP_PATCH },
        { 5, "TRACE",     NGX_HTTP_TRACE }
    }, *test;

    if (r->method_name.len) {
        return NGX_HTTP_PARSE_INVALID_HEADER;
    }

    len = r->header_end - r->header_start;

    r->method_name.len = len;
    r->method_name.data = r->header_start;

    test = tests;
    n = sizeof(tests) / sizeof(tests[0]);

    do {
        if (len == test->len) {
            p = r->method_name.data;
            m = test->method;
            k = len;

            do {
                if (*p++ != *m++) {
                    goto next;
                }
            } while (--k);

            r->method = test->value;
            return NGX_OK;
        }

    next:
        test++;

    } while (--n);

    p = r->method_name.data;

    do {
        if ((*p < 'A' || *p > 'Z') && *p != '_') {
            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "client sent invalid method");
            return NGX_HTTP_PARSE_INVALID_REQUEST;
        }

        p++;

    } while (--len);

    return NGX_OK;
}


static ngx_int_t
ngx_http_spdy_parse_scheme(ngx_http_request_t *r)
{
    if (r->schema_start) {
        return NGX_HTTP_PARSE_INVALID_HEADER;
    }

    r->schema_start = r->header_start;
    r->schema_end = r->header_end;

    return NGX_OK;
}


static ngx_int_t
ngx_http_spdy_parse_host(ngx_http_request_t *r)
{
    ngx_table_elt_t  *h;

    if (r->headers_in.host) {
        return NGX_HTTP_PARSE_INVALID_HEADER;
    }

    h = ngx_list_push(&r->headers_in.headers);
    if (h == NULL) {
        ngx_http_spdy_close_stream(r->spdy_stream,
                                   NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_ERROR;
    }

    r->headers_in.host = h;

    h->hash = r->header_hash;

    h->key.len = r->header_name_end - r->header_name_start;
    h->key.data = r->header_name_start;

    h->value.len = r->header_end - r->header_start;
    h->value.data = r->header_start;

    h->lowcase_key = h->key.data;

    return NGX_OK;
}


static ngx_int_t
ngx_http_spdy_parse_path(ngx_http_request_t *r)
{
    if (r->unparsed_uri.len) {
        return NGX_HTTP_PARSE_INVALID_HEADER;
    }

    r->uri_start = r->header_start;
    r->uri_end = r->header_end;

    if (ngx_http_parse_uri(r) != NGX_OK) {
        return NGX_HTTP_PARSE_INVALID_REQUEST;
    }

    if (ngx_http_process_request_uri(r) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}


static ngx_int_t
ngx_http_spdy_parse_version(ngx_http_request_t *r)
{
    u_char  *p, ch;

    if (r->http_protocol.len) {
        return NGX_HTTP_PARSE_INVALID_HEADER;
    }

    p = r->header_start;

    if (r->header_end - p < 8 || !(ngx_str5cmp(p, 'H', 'T', 'T', 'P', '/'))) {
        return NGX_HTTP_PARSE_INVALID_REQUEST;
    }

    ch = *(p + 5);

    if (ch < '1' || ch > '9') {
        return NGX_HTTP_PARSE_INVALID_REQUEST;
    }

    r->http_major = ch - '0';

    for (p += 6; p != r->header_end - 2; p++) {

        ch = *p;

        if (ch == '.') {
            break;
        }

        if (ch < '0' || ch > '9') {
            return NGX_HTTP_PARSE_INVALID_REQUEST;
        }

        r->http_major = r->http_major * 10 + ch - '0';
    }

    if (*p != '.') {
        return NGX_HTTP_PARSE_INVALID_REQUEST;
    }

    ch = *(p + 1);

    if (ch < '0' || ch > '9') {
        return NGX_HTTP_PARSE_INVALID_REQUEST;
    }

    r->http_minor = ch - '0';

    for (p += 2; p != r->header_end; p++) {

        ch = *p;

        if (ch < '0' || ch > '9') {
            return NGX_HTTP_PARSE_INVALID_REQUEST;
        }

        r->http_minor = r->http_minor * 10 + ch - '0';
    }

    r->http_protocol.len = r->header_end - r->header_start;
    r->http_protocol.data = r->header_start;
    r->http_version = r->http_major * 1000 + r->http_minor;

    return NGX_OK;
}


static ngx_int_t
ngx_http_spdy_construct_request_line(ngx_http_request_t *r)
{
    u_char  *p;

    if (r->method_name.len == 0
        || r->unparsed_uri.len == 0
        || r->http_protocol.len == 0)
    {
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }

    r->request_line.len = r->method_name.len + 1
                          + r->unparsed_uri.len + 1
                          + r->http_protocol.len;

    p = ngx_pnalloc(r->pool, r->request_line.len + 1);
    if (p == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_ERROR;
    }

    r->request_line.data = p;

    p = ngx_cpymem(p, r->method_name.data, r->method_name.len);

    *p++ = ' ';

    p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len);

    *p++ = ' ';

    ngx_memcpy(p, r->http_protocol.data, r->http_protocol.len + 1);

    /* some modules expect the space character after method name */
    r->method_name.data = r->request_line.data;

    return NGX_OK;
}


static void
ngx_http_spdy_run_request(ngx_http_request_t *r)
{
    ngx_uint_t                  i;
    ngx_list_part_t            *part;
    ngx_table_elt_t            *h;
    ngx_http_header_t          *hh;
    ngx_http_core_main_conf_t  *cmcf;

    if (ngx_http_spdy_construct_request_line(r) != NGX_OK) {
        return;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "spdy http request line: \"%V\"", &r->request_line);

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    part = &r->headers_in.headers.part;
    h = part->elts;

    for (i = 0 ;; i++) {

        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            h = part->elts;
            i = 0;
        }

        hh = ngx_hash_find(&cmcf->headers_in_hash, h[i].hash,
                           h[i].lowcase_key, h[i].key.len);

        if (hh && hh->handler(r, &h[i], hh->offset) != NGX_OK) {
            return;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http header: \"%V: %V\"", &h[i].key, &h[i].value);
    }

    r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;

    if (ngx_http_process_request_header(r) != NGX_OK) {
        return;
    }

    if (r->headers_in.content_length_n > 0 && r->spdy_stream->in_closed) {
        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                      "client prematurely closed stream");

        r->spdy_stream->skip_data = NGX_SPDY_DATA_ERROR;

        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return;
    }

    ngx_http_process_request(r);
}


static ngx_int_t
ngx_http_spdy_init_request_body(ngx_http_request_t *r)
{
    ngx_buf_t                 *buf;
    ngx_temp_file_t           *tf;
    ngx_http_request_body_t   *rb;
    ngx_http_core_loc_conf_t  *clcf;

    rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
    if (rb == NULL) {
        return NGX_ERROR;
    }

    r->request_body = rb;

    if (r->spdy_stream->in_closed) {
        return NGX_OK;
    }

    rb->rest = r->headers_in.content_length_n;

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    if (r->request_body_in_file_only
        || rb->rest > (off_t) clcf->client_body_buffer_size
        || rb->rest < 0)
    {
        tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
        if (tf == NULL) {
            return NGX_ERROR;
        }

        tf->file.fd = NGX_INVALID_FILE;
        tf->file.log = r->connection->log;
        tf->path = clcf->client_body_temp_path;
        tf->pool = r->pool;
        tf->warn = "a client request body is buffered to a temporary file";
        tf->log_level = r->request_body_file_log_level;
        tf->persistent = r->request_body_in_persistent_file;
        tf->clean = r->request_body_in_clean_file;

        if (r->request_body_file_group_access) {
            tf->access = 0660;
        }

        rb->temp_file = tf;

        if (r->spdy_stream->in_closed
            && ngx_create_temp_file(&tf->file, tf->path, tf->pool,
                                    tf->persistent, tf->clean, tf->access)
               != NGX_OK)
        {
            return NGX_ERROR;
        }

        buf = ngx_calloc_buf(r->pool);
        if (buf == NULL) {
            return NGX_ERROR;
        }

    } else {

        if (rb->rest == 0) {
            return NGX_OK;
        }

        buf = ngx_create_temp_buf(r->pool, (size_t) rb->rest);
        if (buf == NULL) {
            return NGX_ERROR;
        }
    }

    rb->buf = buf;

    rb->bufs = ngx_alloc_chain_link(r->pool);
    if (rb->bufs == NULL) {
        return NGX_ERROR;
    }

    rb->bufs->buf = buf;
    rb->bufs->next = NULL;

    rb->rest = 0;

    return NGX_OK;
}


ngx_int_t
ngx_http_spdy_read_request_body(ngx_http_request_t *r,
    ngx_http_client_body_handler_pt post_handler)
{
    ngx_http_spdy_stream_t  *stream;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "spdy read request body");

    stream = r->spdy_stream;

    switch (stream->skip_data) {

    case NGX_SPDY_DATA_DISCARD:
        post_handler(r);
        return NGX_OK;

    case NGX_SPDY_DATA_ERROR:
        if (r->headers_in.content_length_n == -1) {
            return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
        } else {
            return NGX_HTTP_BAD_REQUEST;
        }

    case NGX_SPDY_DATA_INTERNAL_ERROR:
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (!r->request_body && ngx_http_spdy_init_request_body(r) != NGX_OK) {
        stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR;
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (stream->in_closed) {
        post_handler(r);
        return NGX_OK;
    }

    r->request_body->post_handler = post_handler;

    r->read_event_handler = ngx_http_test_reading;
    r->write_event_handler = ngx_http_request_empty_handler;

    return NGX_AGAIN;
}


static ngx_int_t
ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc,
    ngx_http_spdy_stream_t *stream, ngx_uint_t status)
{
    ngx_event_t       *rev;
    ngx_connection_t  *fc;

    if (ngx_http_spdy_send_rst_stream(sc, stream->id, status,
                                      NGX_SPDY_HIGHEST_PRIORITY)
        == NGX_ERROR)
    {
        return NGX_ERROR;
    }

    stream->out_closed = 1;

    fc = stream->request->connection;
    fc->error = 1;

    rev = fc->read;
    rev->handler(rev);

    return NGX_OK;
}


static void
ngx_http_spdy_close_stream_handler(ngx_event_t *ev)
{
    ngx_connection_t    *fc;
    ngx_http_request_t  *r;

    fc = ev->data;
    r = fc->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "spdy close stream handler");

    ngx_http_spdy_close_stream(r->spdy_stream, 0);
}


void
ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc)
{
    int                           tcp_nodelay;
    ngx_event_t                  *ev;
    ngx_connection_t             *c, *fc;
    ngx_http_core_loc_conf_t     *clcf;
    ngx_http_spdy_stream_t      **index, *s;
    ngx_http_spdy_srv_conf_t     *sscf;
    ngx_http_spdy_connection_t   *sc;

    sc = stream->connection;

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy close stream %ui, queued %ui, processing %ui",
                   stream->id, stream->queued, sc->processing);

    fc = stream->request->connection;

    if (stream->queued) {
        fc->write->handler = ngx_http_spdy_close_stream_handler;
        return;
    }

    if (!stream->out_closed) {
        if (ngx_http_spdy_send_rst_stream(sc, stream->id,
                                          NGX_SPDY_INTERNAL_ERROR,
                                          stream->priority)
            != NGX_OK)
        {
            sc->connection->error = 1;
        }

    } else {
        c = sc->connection;

        if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
            if (ngx_tcp_push(c->fd) == -1) {
                ngx_connection_error(c, ngx_socket_errno,
                                     ngx_tcp_push_n " failed");
                c->error = 1;
                tcp_nodelay = 0;

            } else {
                c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
                tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0;
            }

        } else {
            tcp_nodelay = 1;
        }

        clcf = ngx_http_get_module_loc_conf(stream->request,
                                            ngx_http_core_module);

        if (tcp_nodelay
            && clcf->tcp_nodelay
            && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET)
        {
            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay");

            if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
                           (const void *) &tcp_nodelay, sizeof(int))
                == -1)
            {
#if (NGX_SOLARIS)
                /* Solaris returns EINVAL if a socket has been shut down */
                c->log_error = NGX_ERROR_IGNORE_EINVAL;
#endif

                ngx_connection_error(c, ngx_socket_errno,
                                     "setsockopt(TCP_NODELAY) failed");

                c->log_error = NGX_ERROR_INFO;
                c->error = 1;

            } else {
                c->tcp_nodelay = NGX_TCP_NODELAY_SET;
            }
        }
    }

    if (sc->stream == stream) {
        sc->stream = NULL;
    }

    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
                                        ngx_http_spdy_module);

    index = sc->streams_index + ngx_http_spdy_stream_index(sscf, stream->id);

    for ( ;; ) {
        s = *index;

        if (s == NULL) {
            break;
        }

        if (s == stream) {
            *index = s->index;
            break;
        }

        index = &s->index;
    }

    ngx_http_free_request(stream->request, rc);

    ev = fc->read;

    if (ev->active || ev->disabled) {
        ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0,
                      "spdy fake read event was activated");
    }

    if (ev->timer_set) {
        ngx_del_timer(ev);
    }

    if (ev->prev) {
        ngx_delete_posted_event(ev);
    }

    ev = fc->write;

    if (ev->active || ev->disabled) {
        ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0,
                      "spdy fake write event was activated");
    }

    if (ev->timer_set) {
        ngx_del_timer(ev);
    }

    if (ev->prev) {
        ngx_delete_posted_event(ev);
    }

    fc->data = sc->free_fake_connections;
    sc->free_fake_connections = fc;

    sc->processing--;

    if (sc->processing || sc->blocked) {
        return;
    }

    ev = sc->connection->read;

    ev->handler = ngx_http_spdy_handle_connection_handler;
    ngx_post_event(ev, &ngx_posted_events);
}


static void
ngx_http_spdy_handle_connection_handler(ngx_event_t *rev)
{
    ngx_connection_t  *c;

    rev->handler = ngx_http_spdy_read_handler;

    if (rev->ready) {
        ngx_http_spdy_read_handler(rev);
        return;
    }

    c = rev->data;

    ngx_http_spdy_handle_connection(c->data);
}


static void
ngx_http_spdy_keepalive_handler(ngx_event_t *rev)
{
    ngx_connection_t            *c;
    ngx_http_spdy_srv_conf_t    *sscf;
    ngx_http_spdy_connection_t  *sc;

    c = rev->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy keepalive handler");

    if (rev->timedout || c->close) {
        ngx_http_close_connection(c);
        return;
    }

#if (NGX_HAVE_KQUEUE)

    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
        if (rev->pending_eof) {
            c->log->handler = NULL;
            ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
                          "kevent() reported that client %V closed "
                          "keepalive connection", &c->addr_text);
#if (NGX_HTTP_SSL)
            if (c->ssl) {
                c->ssl->no_send_shutdown = 1;
            }
#endif
            ngx_http_close_connection(c);
            return;
        }
    }

#endif

    c->destroyed = 0;
    c->idle = 0;
    ngx_reusable_connection(c, 0);

    sc = c->data;

    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
                                        ngx_http_spdy_module);

    sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log);
    if (sc->pool == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    sc->streams_index = ngx_pcalloc(sc->pool,
                                    ngx_http_spdy_streams_index_size(sscf)
                                    * sizeof(ngx_http_spdy_stream_t *));
    if (sc->streams_index == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    c->write->handler = ngx_http_spdy_write_handler;

    rev->handler = ngx_http_spdy_read_handler;
    ngx_http_spdy_read_handler(rev);
}


static void
ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc,
    ngx_int_t rc)
{
    ngx_uint_t                 i, size;
    ngx_event_t               *ev;
    ngx_connection_t          *c, *fc;
    ngx_http_request_t        *r;
    ngx_http_spdy_stream_t    *stream;
    ngx_http_spdy_srv_conf_t  *sscf;

    c = sc->connection;

    if (!sc->processing) {
        ngx_http_close_connection(c);
        return;
    }

    c->error = 1;
    c->read->handler = ngx_http_empty_handler;
    c->write->handler = ngx_http_empty_handler;

    sc->last_out = NULL;

    sc->blocked = 1;

    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
                                        ngx_http_spdy_module);

    size = ngx_http_spdy_streams_index_size(sscf);

    for (i = 0; i < size; i++) {
        stream = sc->streams_index[i];

        while (stream) {
            stream->handled = 0;

            r = stream->request;
            fc = r->connection;

            fc->error = 1;

            if (stream->queued) {
                stream->queued = 0;

                ev = fc->write;
                ev->delayed = 0;

            } else {
                ev = fc->read;
            }

            stream = stream->index;

            ev->eof = 1;
            ev->handler(ev);
        }
    }

    sc->blocked = 0;

    if (sc->processing) {
        return;
    }

    ngx_http_close_connection(c);
}


static ngx_int_t
ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, ssize_t delta)
{
    ngx_uint_t                 i, size;
    ngx_event_t               *wev;
    ngx_http_spdy_stream_t    *stream, *sn;
    ngx_http_spdy_srv_conf_t  *sscf;

    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
                                        ngx_http_spdy_module);

    size = ngx_http_spdy_streams_index_size(sscf);

    for (i = 0; i < size; i++) {

        for (stream = sc->streams_index[i]; stream; stream = sn) {
            sn = stream->index;

            if (delta > 0
                && stream->send_window
                      > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta))
            {
                if (ngx_http_spdy_terminate_stream(sc, stream,
                                                   NGX_SPDY_FLOW_CONTROL_ERROR)
                    == NGX_ERROR)
                {
                    return NGX_ERROR;
                }

                continue;
            }

            stream->send_window += delta;

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                           "spdy:%ui adjust window:%z",
                           stream->id, stream->send_window);

            if (stream->send_window > 0 && stream->exhausted) {
                stream->exhausted = 0;

                wev = stream->request->connection->write;

                if (!wev->timer_set) {
                    wev->delayed = 0;
                    wev->handler(wev);
                }
            }
        }
    }

    return NGX_OK;
}


static void
ngx_http_spdy_pool_cleanup(void *data)
{
    ngx_http_spdy_connection_t  *sc = data;

    if (sc->pool) {
        ngx_destroy_pool(sc->pool);
    }
}


static void *
ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size)
{
    ngx_http_spdy_connection_t *sc = opaque;

    return ngx_palloc(sc->connection->pool, items * size);
}


static void
ngx_http_spdy_zfree(void *opaque, void *address)
{
#if 0
    ngx_http_spdy_connection_t *sc = opaque;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
                   "spdy zfree: %p", address);
#endif
}