    const CImg<T>& _display(CImgDisplay &disp, const char *const title, const bool display_info,
                            unsigned int *const XYZ, const bool exit_on_anykey,
                            const bool exit_on_singleclick) const {
      unsigned int oldw = 0, oldh = 0, _XYZ[3] = { 0 }, key = 0;
      int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1,
        old_mouse_x = -1, old_mouse_y = -1;

      if (!disp) {
        disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1);
        if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum);
        else disp.set_title("%s",title);
      } else if (title) disp.set_title("%s",title);
      disp.show().flush();

      const CImg<char> dtitle = CImg<char>::string(disp.title());
      if (display_info) print(dtitle);

      CImg<T> zoom;
      for (bool reset_view = true, resize_disp = false, is_first_select = true; !key && !disp.is_closed(); ) {
        if (reset_view) {
          if (XYZ) { _XYZ[0] = XYZ[0]; _XYZ[1] = XYZ[1]; _XYZ[2] = XYZ[2]; }
          else {
            _XYZ[0] = (unsigned int)(x0 + x1 + 1)/2;
            _XYZ[1] = (unsigned int)(y0 + y1 + 1)/2;
            _XYZ[2] = (unsigned int)(z0 + z1 + 1)/2;
          }
          x0 = 0; y0 = 0; z0 = 0; x1 = width() - 1; y1 = height() - 1; z1 = depth() - 1;
          disp.resize(cimg_fitscreen(_width,_height,_depth),false);
          oldw = disp._width; oldh = disp._height;
          resize_disp = true;
          reset_view = false;
        }
        if (!x0 && !y0 && !z0 && x1==width() - 1 && y1==height() - 1 && z1==depth() - 1) {
          if (is_empty()) zoom.assign(1,1,1,1,(T)0); else zoom.assign();
        } else zoom = get_crop(x0,y0,z0,x1,y1,z1);

        const CImg<T>& visu = zoom?zoom:*this;
        const unsigned int
          dx = 1U + x1 - x0, dy = 1U + y1 - y0, dz = 1U + z1 - z0,
          tw = dx + (dz>1?dz:0U), th = dy + (dz>1?dz:0U);
        if (!is_empty() && !disp.is_fullscreen() && resize_disp) {
          const float
            ttw = (float)tw*disp.width()/oldw, tth = (float)th*disp.height()/oldh,
            dM = std::max(ttw,tth), diM = (float)std::max(disp.width(),disp.height());
          const unsigned int
            imgw = (unsigned int)(ttw*diM/dM), imgh = (unsigned int)(tth*diM/dM);
          disp.set_fullscreen(false).resize(cimg_fitscreen(imgw,imgh,1),false);
          resize_disp = false;
        }
        oldw = tw; oldh = th;

        bool
          go_up = false, go_down = false, go_left = false, go_right = false,
          go_inc = false, go_dec = false, go_in = false, go_out = false,
          go_in_center = false;

        disp.set_title("%s",dtitle._data);
        if (_width>1 && visu._width==1) disp.set_title("%s | x=%u",disp._title,x0);
        if (_height>1 && visu._height==1) disp.set_title("%s | y=%u",disp._title,y0);
        if (_depth>1 && visu._depth==1) disp.set_title("%s | z=%u",disp._title,z0);

        disp._mouse_x = old_mouse_x; disp._mouse_y = old_mouse_y;
        CImg<intT> selection = visu._select(disp,0,2,_XYZ,x0,y0,z0,true,is_first_select,_depth>1,true);
        old_mouse_x = disp._mouse_x; old_mouse_y = disp._mouse_y;
        is_first_select = false;

        if (disp.wheel()) {
          if ((disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) &&
              (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT())) {
            go_left = !(go_right = disp.wheel()>0);
          } else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) {
            go_down = !(go_up = disp.wheel()>0);
          } else if (depth()==1 || disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
            go_out = !(go_in = disp.wheel()>0); go_in_center = false;
          }
          disp.set_wheel();
        }

        const int
          sx0 = selection(0), sy0 = selection(1), sz0 = selection(2),
          sx1 = selection(3), sy1 = selection(4), sz1 = selection(5);
        if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) {
          x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1;
          x0+=sx0; y0+=sy0; z0+=sz0;
          if ((sx0==sx1 && sy0==sy1) || (_depth>1 && sx0==sx1 && sz0==sz1) || (_depth>1 && sy0==sy1 && sz0==sz1)) {
            if (exit_on_singleclick && (!zoom || is_empty())) break; else reset_view = true;
          }
          resize_disp = true;
        } else switch (key = disp.key()) {
#if cimg_OS!=2
          case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT :
#endif
          case 0 : case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : key = 0; break;
          case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) {
              // Special mode: play stack of frames
              const unsigned int
                w1 = visu._width*disp.width()/(visu._width + (visu._depth>1?visu._depth:0)),
                h1 = visu._height*disp.height()/(visu._height + (visu._depth>1?visu._depth:0));
              float frame_timing = 5;
              bool is_stopped = false;
              disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0;
              for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) {
                if (disp.is_resized()) disp.resize(false);
                if (!timer) {
                  visu.get_slice((int)_XYZ[2]).display(disp.set_title("%s | z=%d",dtitle.data(),_XYZ[2]));
                  (++_XYZ[2])%=visu._depth;
                }
                if (!is_stopped) { if (++timer>(unsigned int)frame_timing) timer = 0; } else timer = ~0U;
                if (disp.wheel()) { frame_timing-=disp.wheel()/3.f; disp.set_wheel(); }
                switch (key = disp.key()) {
#if cimg_OS!=2
                case cimg::keyCTRLRIGHT :
#endif
                case cimg::keyCTRLLEFT : key = 0; break;
                case cimg::keyPAGEUP : frame_timing-=0.3f; key = 0; break;
                case cimg::keyPAGEDOWN : frame_timing+=0.3f; key = 0; break;
                case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break;
                case cimg::keyARROWLEFT : case cimg::keyARROWUP : is_stopped = true; timer = 0; key = 0; break;
                case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN : is_stopped = true;
                  (_XYZ[2]+=visu._depth - 2)%=visu._depth; timer = 0; key = 0; break;
                case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
                    disp.set_fullscreen(false).
                      resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
                             CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false);
                    disp.set_key(key,false); key = 0;
                  } break;
                case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
                    disp.set_fullscreen(false).
                      resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0;
                  } break;
                case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
                    disp.set_fullscreen(false).
                      resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0;
                  } break;
                case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
                    disp.resize(disp.screen_width(),disp.screen_height(),false).
                      toggle_fullscreen().set_key(key,false); key = 0;
                  } break;
                }
                frame_timing = frame_timing<1?1:(frame_timing>39?39:frame_timing);
                disp.wait(20);
              }
              const unsigned int
                w2 = (visu._width + (visu._depth>1?visu._depth:0))*disp.width()/visu._width,
                h2 = (visu._height + (visu._depth>1?visu._depth:0))*disp.height()/visu._height;
              disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(dtitle.data()).set_key().set_button().set_wheel();
              key = 0;
            } break;
          case cimg::keyHOME : reset_view = resize_disp = true; key = 0; break;
          case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break;
          case cimg::keyPADSUB : go_out = true; key = 0; break;
          case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break;
          case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break;
          case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break;
          case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break;
          case cimg::keyPAD7 : go_up = go_left = true; key = 0; break;
          case cimg::keyPAD9 : go_up = go_right = true; key = 0; break;
          case cimg::keyPAD1 : go_down = go_left = true; key = 0; break;
          case cimg::keyPAD3 : go_down = go_right = true; key = 0; break;
          case cimg::keyPAGEUP : go_inc = true; key = 0; break;
          case cimg::keyPAGEDOWN : go_dec = true; key = 0; break;
          }
        if (go_in) {
          const int
            mx = go_in_center?disp.width()/2:disp.mouse_x(),
            my = go_in_center?disp.height()/2:disp.mouse_y(),
            mX = mx*(width() + (depth()>1?depth():0))/disp.width(),
            mY = my*(height() + (depth()>1?depth():0))/disp.height();
          int X = (int)_XYZ[0], Y = (int)_XYZ[1], Z = (int)_XYZ[2];
          if (mX<width() && mY<height())  {
            X = x0 + mX*(1 + x1 - x0)/width(); Y = y0 + mY*(1 + y1 - y0)/height();
          }
          if (mX<width() && mY>=height()) {
            X = x0 + mX*(1 + x1 - x0)/width(); Z = z0 + (mY - height())*(1 + z1 - z0)/depth();
          }
          if (mX>=width() && mY<height()) {
            Y = y0 + mY*(1 + y1 - y0)/height(); Z = z0 + (mX - width())*(1 + z1 - z0)/depth();
          }
          if (x1 - x0>4) { x0 = X - 3*(X - x0)/4; x1 = X + 3*(x1 - X)/4; }
          if (y1 - y0>4) { y0 = Y - 3*(Y - y0)/4; y1 = Y + 3*(y1 - Y)/4; }
          if (z1 - z0>4) { z0 = Z - 3*(Z - z0)/4; z1 = Z + 3*(z1 - Z)/4; }
        }
        if (go_out) {
          const int
            delta_x = (x1 - x0)/8, delta_y = (y1 - y0)/8, delta_z = (z1 - z0)/8,
            ndelta_x = delta_x?delta_x:(_width>1),
            ndelta_y = delta_y?delta_y:(_height>1),
            ndelta_z = delta_z?delta_z:(_depth>1);
          x0-=ndelta_x; y0-=ndelta_y; z0-=ndelta_z;
          x1+=ndelta_x; y1+=ndelta_y; z1+=ndelta_z;
          if (x0<0) { x1-=x0; x0 = 0; if (x1>=width()) x1 = width() - 1; }
          if (y0<0) { y1-=y0; y0 = 0; if (y1>=height()) y1 = height() - 1; }
          if (z0<0) { z1-=z0; z0 = 0; if (z1>=depth()) z1 = depth() - 1; }
          if (x1>=width()) { x0-=(x1 - width() + 1); x1 = width() - 1; if (x0<0) x0 = 0; }
          if (y1>=height()) { y0-=(y1 - height() + 1); y1 = height() - 1; if (y0<0) y0 = 0; }
          if (z1>=depth()) { z0-=(z1 - depth() + 1); z1 = depth() - 1; if (z0<0) z0 = 0; }
          const float
            ratio = (float)(x1-x0)/(y1-y0),
            ratiow = (float)disp._width/disp._height,
            sub = std::min(cimg::abs(ratio - ratiow),cimg::abs(1/ratio-1/ratiow));
          if (sub>0.01) resize_disp = true;
        }
        if (go_left) {
          const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1);
          if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; }
          else { x1-=x0; x0 = 0; }
        }
        if (go_right) {
          const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1);
          if (x1+ndelta<width()) { x0+=ndelta; x1+=ndelta; }
          else { x0+=(width() - 1 - x1); x1 = width() - 1; }
        }
        if (go_up) {
          const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1);
          if (y0 - ndelta>=0) { y0-=ndelta; y1-=ndelta; }
          else { y1-=y0; y0 = 0; }
        }
        if (go_down) {
          const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1);
          if (y1+ndelta<height()) { y0+=ndelta; y1+=ndelta; }
          else { y0+=(height() - 1 - y1); y1 = height() - 1; }
        }
        if (go_inc) {
          const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1);
          if (z0 - ndelta>=0) { z0-=ndelta; z1-=ndelta; }
          else { z1-=z0; z0 = 0; }
        }
        if (go_dec) {
          const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1);
          if (z1+ndelta<depth()) { z0+=ndelta; z1+=ndelta; }
          else { z0+=(depth() - 1 - z1); z1 = depth() - 1; }
        }
        disp.wait(100);
        if (!exit_on_anykey && key && key!=cimg::keyESC &&
            (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) {
          key = 0;
        }
      }
      disp.set_key(key);
      if (XYZ) { XYZ[0] = _XYZ[0]; XYZ[1] = _XYZ[1]; XYZ[2] = _XYZ[2]; }
      return *this;


    //! Convert pixel values from HSL to RGB color spaces.
    CImg<T>& HSLtoRGB() {
      if (_spectrum!=3)
        throw CImgInstanceException(_cimg_instance
                                    "HSLtoRGB(): Instance is not a HSL image.",
                                    cimg_instance);

      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
      const longT whd = (longT)width()*height()*depth();
      cimg_pragma_openmp(parallel for cimg_openmp_if_size(whd,256))
      for (longT N = 0; N<whd; ++N) {
        const Tfloat
          H = cimg::mod((Tfloat)p1[N]/60,(Tfloat)6),
          S = (Tfloat)p2[N],
          L = (Tfloat)p3[N],
          C = (1 - cimg::abs(2*L - 1))*S,
          X = C*(1 - cimg::abs(cimg::mod(H,(Tfloat)2) - 1)),
          m = L - C/2;
        Tfloat R, G, B;
        switch ((int)H) {
        case 0 : R = C; G = X; B = 0; break;
        case 1 : R = X; G = C; B = 0; break;
        case 2 : R = 0; G = C; B = X; break;
        case 3 : R = 0; G = X; B = C; break;
        case 4 : R = X; G = 0; B = C; break;
        default : R = C; G = 0; B = X;
        }
        p1[N] = (T)((R + m)*255);
        p2[N] = (T)((G + m)*255);
        p3[N] = (T)((B + m)*255);
      }
      return *this;


static int cx24116_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
{
	struct cx24116_state *state = fe->demodulator_priv;

	dprintk("%s()\n", __func__);

	*ucblocks = (cx24116_readreg(state, CX24116_REG_UCB8) << 8) |
		cx24116_readreg(state, CX24116_REG_UCB0);

	return 0;
}


static int cx24116_sleep(struct dvb_frontend *fe)
{
	struct cx24116_state *state = fe->demodulator_priv;
	struct cx24116_cmd cmd;
	int ret;

	dprintk("%s()\n", __func__);

	/* Firmware CMD 36: Power config */
	cmd.args[0x00] = CMD_TUNERSLEEP;
	cmd.args[0x01] = 1;
	cmd.len = 0x02;
	ret = cx24116_cmd_execute(fe, &cmd);
	if (ret != 0)
		return ret;

	/* Power off (Shutdown clocks) */
	cx24116_writereg(state, 0xea, 0xff);
	cx24116_writereg(state, 0xe1, 1);
	cx24116_writereg(state, 0xe0, 1);

	return 0;
}


static int cx24116_readreg(struct cx24116_state *state, u8 reg)
{
	int ret;
	u8 b0[] = { reg };
	u8 b1[] = { 0 };
	struct i2c_msg msg[] = {
		{ .addr = state->config->demod_address, .flags = 0,
			.buf = b0, .len = 1 },
		{ .addr = state->config->demod_address, .flags = I2C_M_RD,
			.buf = b1, .len = 1 }
	};

	ret = i2c_transfer(state->i2c, msg, 2);

	if (ret != 2) {
		printk(KERN_ERR "%s: reg=0x%x (error=%d)\n",
			__func__, reg, ret);
		return ret;
	}

	if (debug > 1)
		printk(KERN_INFO "cx24116: read reg 0x%02x, value 0x%02x\n",
			reg, b1[0]);

	return b1[0];
}


static int cx24116_set_inversion(struct cx24116_state *state,
	fe_spectral_inversion_t inversion)
{
	dprintk("%s(%d)\n", __func__, inversion);

	switch (inversion) {
	case INVERSION_OFF:
		state->dnxt.inversion_val = 0x00;
		break;
	case INVERSION_ON:
		state->dnxt.inversion_val = 0x04;
		break;
	case INVERSION_AUTO:
		state->dnxt.inversion_val = 0x0C;
		break;
	default:
		return -EINVAL;
	}

	state->dnxt.inversion = inversion;

	return 0;
}


static int cx24116_get_algo(struct dvb_frontend *fe)
{
	return DVBFE_ALGO_HW;
}


void prepare_redirect_response(Stream *stream, Http2Handler *hd,
                               const StringRef &path, int status) {
  auto scheme = stream->header.scheme;

  auto authority = stream->header.authority;
  if (authority.empty()) {
    authority = stream->header.host;
  }

  auto location = concat_string_ref(
      stream->balloc, scheme, StringRef::from_lit("://"), authority, path);

  auto headers = HeaderRefs{{StringRef::from_lit("location"), location}};

  auto sessions = hd->get_sessions();
  auto status_page = sessions->get_server()->get_status_page(status);

  hd->submit_response(StringRef{status_page->status}, stream->stream_id,
                      headers, nullptr);
}


SSL_CTX *get_ssl_ctx() const { return ssl_ctx_; }


void Http2Handler::add_stream(int32_t stream_id,
                              std::unique_ptr<Stream> stream) {
  id2stream_[stream_id] = std::move(stream);
}


int ClientHandler::tls_handshake() {
  ev_timer_again(conn_.loop, &conn_.rt);

  ERR_clear_error();

  auto rv = conn_.tls_handshake();

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

  if (rv < 0) {
    return -1;
  }

  if (LOG_ENABLED(INFO)) {
    CLOG(INFO, this) << "SSL/TLS handshake completed";
  }

  if (validate_next_proto() != 0) {
    return -1;
  }

  read_ = &ClientHandler::read_tls;
  write_ = &ClientHandler::write_tls;

  return 0;
}


void ClientHandler::signal_write() { conn_.wlimit.startw(); }


int64_t Http2Handler::session_id() const { return session_id_; }


Sessions *Http2Handler::get_sessions() const { return sessions_; }


void ClientHandler::start_immediate_shutdown() {
  ev_timer_start(conn_.loop, &reneg_shutdown_timer_);
}


int hd_on_frame_send_callback(nghttp2_session *session,
                              const nghttp2_frame *frame, void *user_data) {
  auto hd = static_cast<Http2Handler *>(user_data);

  if (hd->get_config()->verbose) {
    print_session_id(hd->session_id());
    verbose_on_frame_send_callback(session, frame, user_data);
  }

  switch (frame->hd.type) {
  case NGHTTP2_DATA:
  case NGHTTP2_HEADERS: {
    auto stream = hd->get_stream(frame->hd.stream_id);

    if (!stream) {
      return 0;
    }

    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
      remove_stream_write_timeout(stream);
    } else if (std::min(nghttp2_session_get_stream_remote_window_size(
                            session, frame->hd.stream_id),
                        nghttp2_session_get_remote_window_size(session)) <= 0) {
      // If stream is blocked by flow control, enable write timeout.
      add_stream_read_timeout_if_pending(stream);
      add_stream_write_timeout(stream);
    } else {
      add_stream_read_timeout_if_pending(stream);
      remove_stream_write_timeout(stream);
    }

    break;
  }
  case NGHTTP2_SETTINGS: {
    if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
      return 0;
    }

    hd->start_settings_timer();

    break;
  }
  case NGHTTP2_PUSH_PROMISE: {
    auto promised_stream_id = frame->push_promise.promised_stream_id;
    auto promised_stream = hd->get_stream(promised_stream_id);
    auto stream = hd->get_stream(frame->hd.stream_id);

    if (!stream || !promised_stream) {
      return 0;
    }

    add_stream_read_timeout_if_pending(stream);
    add_stream_write_timeout(stream);

    prepare_response(promised_stream, hd, /*allow_push */ false);
  }
  }
  return 0;
}


Http2Handler::WriteBuf *Http2Handler::get_wb() { return &wb_; }


int Http2Handler::submit_non_final_response(const std::string &status,
                                            int32_t stream_id) {
  auto nva = make_array(http2::make_nv_ls(":status", status));
  return nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, stream_id, nullptr,
                                nva.data(), nva.size(), nullptr);
}


  SSL *ssl_session_new(int fd) {
    SSL *ssl = SSL_new(ssl_ctx_);
    if (!ssl) {
      std::cerr << "SSL_new() failed" << std::endl;
      return nullptr;
    }
    if (SSL_set_fd(ssl, fd) == 0) {
      std::cerr << "SSL_set_fd() failed" << std::endl;
      SSL_free(ssl);
      return nullptr;
    }
    return ssl;
  }


  FileEntry *get_cached_fd(const std::string &path) {
    auto range = fd_cache_.equal_range(path);
    if (range.first == range.second) {
      return nullptr;
    }

    auto now = ev_now(loop_);

    for (auto it = range.first; it != range.second;) {
      auto &ent = (*it).second;
      if (ent->stale) {
        ++it;
        continue;
      }
      if (need_validation_file_entry(ent.get(), now) &&
          !validate_file_entry(ent.get(), now)) {
        if (ent->usecount == 0) {
          fd_cache_lru_.remove(ent.get());
          close(ent->fd);
          it = fd_cache_.erase(it);
          continue;
        }
        ++it;
        continue;
      }

      fd_cache_lru_.remove(ent.get());
      fd_cache_lru_.append(ent.get());

      ++ent->usecount;
      return ent.get();
    }
    return nullptr;
  }


void test_nghttp2_session_pack_headers_with_padding(void) {
  nghttp2_session *session, *sv_session;
  accumulator acc;
  my_user_data ud;
  nghttp2_session_callbacks callbacks;

  memset(&callbacks, 0, sizeof(callbacks));
  callbacks.send_callback = accumulator_send_callback;
  callbacks.on_frame_send_callback = on_frame_send_callback;
  callbacks.select_padding_callback = select_padding_callback;
  callbacks.on_frame_recv_callback = on_frame_recv_callback;

  acc.length = 0;
  ud.acc = &acc;

  nghttp2_session_client_new(&session, &callbacks, &ud);
  nghttp2_session_server_new(&sv_session, &callbacks, &ud);

  ud.padlen = 163;

  CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv),
                                        NULL, NULL));
  CU_ASSERT(0 == nghttp2_session_send(session));

  CU_ASSERT(acc.length < NGHTTP2_MAX_PAYLOADLEN);
  ud.frame_recv_cb_called = 0;
  CU_ASSERT((ssize_t)acc.length ==
            nghttp2_session_mem_recv(sv_session, acc.buf, acc.length));
  CU_ASSERT(1 == ud.frame_recv_cb_called);
  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(sv_session));

  nghttp2_session_del(sv_session);
  nghttp2_session_del(session);
}


int nghttp2_session_server_new3(nghttp2_session **session_ptr,
                                const nghttp2_session_callbacks *callbacks,
                                void *user_data, const nghttp2_option *option,
                                nghttp2_mem *mem) {
  int rv;
  nghttp2_session *session;

  rv = session_new(&session, callbacks, user_data, 1, option, mem);

  if (rv != 0) {
    return rv;
  }
  /* IDs for use in client */
  session->next_stream_id = 2;

  *session_ptr = session;

  return 0;
}


session_append_inflight_settings(nghttp2_session *session,
                                 nghttp2_inflight_settings *settings) {
  nghttp2_inflight_settings **i;

  for (i = &session->inflight_settings_head; *i; i = &(*i)->next)
    ;

  *i = settings;
}


void test_nghttp2_session_on_window_update_received(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  my_user_data user_data;
  nghttp2_frame frame;
  nghttp2_stream *stream;
  nghttp2_outbound_item *data_item;
  nghttp2_mem *mem;

  mem = nghttp2_mem_default();

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.on_frame_recv_callback = on_frame_recv_callback;
  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
  user_data.frame_recv_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;

  nghttp2_session_client_new(&session, &callbacks, &user_data);

  stream = open_sent_stream(session, 1);

  data_item = create_data_ob_item(mem);

  CU_ASSERT(0 == nghttp2_stream_attach_item(stream, data_item));

  nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1,
                                   16 * 1024);

  CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
  CU_ASSERT(1 == user_data.frame_recv_cb_called);
  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 16 * 1024 ==
            stream->remote_window_size);

  CU_ASSERT(0 == nghttp2_stream_defer_item(
                     stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL));

  CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
  CU_ASSERT(2 == user_data.frame_recv_cb_called);
  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 16 * 1024 * 2 ==
            stream->remote_window_size);
  CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL));

  nghttp2_frame_window_update_free(&frame.window_update);

  /* Receiving WINDOW_UPDATE on reserved (remote) stream is a
     connection error */
  open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);

  nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 2,
                                   4096);

  CU_ASSERT(!(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND));
  CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
  CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND);

  nghttp2_frame_window_update_free(&frame.window_update);

  nghttp2_session_del(session);

  /* Receiving WINDOW_UPDATE on reserved (local) stream is allowed */
  nghttp2_session_server_new(&session, &callbacks, &user_data);

  stream = open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED);

  nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 2,
                                   4096);

  CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
  CU_ASSERT(!(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND));

  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 4096 == stream->remote_window_size);

  nghttp2_frame_window_update_free(&frame.window_update);

  nghttp2_session_del(session);
}


static int session_prep_frame(nghttp2_session *session,
                              nghttp2_outbound_item *item) {
  int rv;
  nghttp2_frame *frame;
  nghttp2_mem *mem;

  mem = &session->mem;
  frame = &item->frame;

  switch (frame->hd.type) {
  case NGHTTP2_DATA: {
    size_t next_readmax;
    nghttp2_stream *stream;

    stream = nghttp2_session_get_stream(session, frame->hd.stream_id);

    if (stream) {
      assert(stream->item == item);
    }

    rv = nghttp2_session_predicate_data_send(session, stream);
    if (rv != 0) {
      // If stream was already closed, nghttp2_session_get_stream()
      // returns NULL, but item is still attached to the stream.
      // Search stream including closed again.
      stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
      if (stream) {
        int rv2;

        rv2 = nghttp2_stream_detach_item(stream);

        if (nghttp2_is_fatal(rv2)) {
          return rv2;
        }
      }

      return rv;
    }
    /* Assuming stream is not NULL */
    assert(stream);
    next_readmax = nghttp2_session_next_data_read(session, stream);

    if (next_readmax == 0) {

      /* This must be true since we only pop DATA frame item from
         queue when session->remote_window_size > 0 */
      assert(session->remote_window_size > 0);

      rv = nghttp2_stream_defer_item(stream,
                                     NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);

      if (nghttp2_is_fatal(rv)) {
        return rv;
      }

      session->aob.item = NULL;
      active_outbound_item_reset(&session->aob, mem);
      return NGHTTP2_ERR_DEFERRED;
    }

    rv = nghttp2_session_pack_data(session, &session->aob.framebufs,
                                   next_readmax, frame, &item->aux_data.data,
                                   stream);
    if (rv == NGHTTP2_ERR_PAUSE) {
      return rv;
    }
    if (rv == NGHTTP2_ERR_DEFERRED) {
      rv = nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER);

      if (nghttp2_is_fatal(rv)) {
        return rv;
      }

      session->aob.item = NULL;
      active_outbound_item_reset(&session->aob, mem);
      return NGHTTP2_ERR_DEFERRED;
    }
    if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
      rv = nghttp2_stream_detach_item(stream);

      if (nghttp2_is_fatal(rv)) {
        return rv;
      }

      rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id,
                                          NGHTTP2_INTERNAL_ERROR);
      if (nghttp2_is_fatal(rv)) {
        return rv;
      }
      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
    }
    if (rv != 0) {
      int rv2;

      rv2 = nghttp2_stream_detach_item(stream);

      if (nghttp2_is_fatal(rv2)) {
        return rv2;
      }

      return rv;
    }
    return 0;
  }
  case NGHTTP2_HEADERS: {
    nghttp2_headers_aux_data *aux_data;
    size_t estimated_payloadlen;

    aux_data = &item->aux_data.headers;

    if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
      /* initial HEADERS, which opens stream */
      nghttp2_stream *stream;

      stream = nghttp2_session_open_stream(
          session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE,
          &frame->headers.pri_spec, NGHTTP2_STREAM_INITIAL,
          aux_data->stream_user_data);

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

      /* We don't call nghttp2_session_adjust_closed_stream() here,
         since we don't keep closed stream in client side */

      rv = session_predicate_request_headers_send(session, item);
      if (rv != 0) {
        return rv;
      }

      if (session_enforce_http_messaging(session)) {
        nghttp2_http_record_request_method(stream, frame);
      }
    } else {
      nghttp2_stream *stream;

      stream = nghttp2_session_get_stream(session, frame->hd.stream_id);

      if (stream && stream->state == NGHTTP2_STREAM_RESERVED) {
        rv = session_predicate_push_response_headers_send(session, stream);
        if (rv == 0) {
          frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;

          if (aux_data->stream_user_data) {
            stream->stream_user_data = aux_data->stream_user_data;
          }
        }
      } else if (session_predicate_response_headers_send(session, stream) ==
                 0) {
        frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
        rv = 0;
      } else {
        frame->headers.cat = NGHTTP2_HCAT_HEADERS;

        rv = session_predicate_headers_send(session, stream);
      }

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

    estimated_payloadlen = session_estimate_headers_payload(
        session, frame->headers.nva, frame->headers.nvlen,
        NGHTTP2_PRIORITY_SPECLEN);

    if (estimated_payloadlen > session->max_send_header_block_length) {
      return NGHTTP2_ERR_FRAME_SIZE_ERROR;
    }

    rv = nghttp2_frame_pack_headers(&session->aob.framebufs, &frame->headers,
                                    &session->hd_deflater);

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

    DEBUGF("send: before padding, HEADERS serialized in %zd bytes\n",
           nghttp2_bufs_len(&session->aob.framebufs));

    rv = session_headers_add_pad(session, frame);

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

    DEBUGF("send: HEADERS finally serialized in %zd bytes\n",
           nghttp2_bufs_len(&session->aob.framebufs));

    if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
      assert(session->last_sent_stream_id < frame->hd.stream_id);
      session->last_sent_stream_id = frame->hd.stream_id;
    }

    return 0;
  }
  case NGHTTP2_PRIORITY: {
    if (session_is_closing(session)) {
      return NGHTTP2_ERR_SESSION_CLOSING;
    }
    /* PRIORITY frame can be sent at any time and to any stream
       ID. */
    nghttp2_frame_pack_priority(&session->aob.framebufs, &frame->priority);

    /* Peer can send PRIORITY frame against idle stream to create
       "anchor" in dependency tree.  Only client can do this in
       nghttp2.  In nghttp2, only server retains non-active (closed
       or idle) streams in memory, so we don't open stream here. */
    return 0;
  }
  case NGHTTP2_RST_STREAM:
    if (session_is_closing(session)) {
      return NGHTTP2_ERR_SESSION_CLOSING;
    }
    nghttp2_frame_pack_rst_stream(&session->aob.framebufs, &frame->rst_stream);
    return 0;
  case NGHTTP2_SETTINGS: {
    if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
      assert(session->obq_flood_counter_ > 0);
      --session->obq_flood_counter_;
      /* When session is about to close, don't send SETTINGS ACK.
         We are required to send SETTINGS without ACK though; for
         example, we have to send SETTINGS as a part of connection
         preface. */
      if (session_is_closing(session)) {
        return NGHTTP2_ERR_SESSION_CLOSING;
      }
    }

    rv = nghttp2_frame_pack_settings(&session->aob.framebufs, &frame->settings);
    if (rv != 0) {
      return rv;
    }
    return 0;
  }
  case NGHTTP2_PUSH_PROMISE: {
    nghttp2_stream *stream;
    size_t estimated_payloadlen;

    /* stream could be NULL if associated stream was already
       closed. */
    stream = nghttp2_session_get_stream(session, frame->hd.stream_id);

    /* predicate should fail if stream is NULL. */
    rv = session_predicate_push_promise_send(session, stream);
    if (rv != 0) {
      return rv;
    }

    assert(stream);

    estimated_payloadlen = session_estimate_headers_payload(
        session, frame->push_promise.nva, frame->push_promise.nvlen, 0);

    if (estimated_payloadlen > session->max_send_header_block_length) {
      return NGHTTP2_ERR_FRAME_SIZE_ERROR;
    }

    rv = nghttp2_frame_pack_push_promise(
        &session->aob.framebufs, &frame->push_promise, &session->hd_deflater);
    if (rv != 0) {
      return rv;
    }
    rv = session_headers_add_pad(session, frame);
    if (rv != 0) {
      return rv;
    }

    assert(session->last_sent_stream_id + 2 <=
           frame->push_promise.promised_stream_id);
    session->last_sent_stream_id = frame->push_promise.promised_stream_id;

    return 0;
  }
  case NGHTTP2_PING:
    if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
      assert(session->obq_flood_counter_ > 0);
      --session->obq_flood_counter_;
    }
    /* PING frame is allowed to be sent unless termination GOAWAY is
       sent */
    if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) {
      return NGHTTP2_ERR_SESSION_CLOSING;
    }
    nghttp2_frame_pack_ping(&session->aob.framebufs, &frame->ping);
    return 0;
  case NGHTTP2_GOAWAY:
    rv = nghttp2_frame_pack_goaway(&session->aob.framebufs, &frame->goaway);
    if (rv != 0) {
      return rv;
    }
    session->local_last_stream_id = frame->goaway.last_stream_id;

    return 0;
  case NGHTTP2_WINDOW_UPDATE:
    rv = session_predicate_window_update_send(session, frame->hd.stream_id);
    if (rv != 0) {
      return rv;
    }
    nghttp2_frame_pack_window_update(&session->aob.framebufs,
                                     &frame->window_update);
    return 0;
  case NGHTTP2_CONTINUATION:
    /* We never handle CONTINUATION here. */
    assert(0);
    return 0;
  default: {
    nghttp2_ext_aux_data *aux_data;

    /* extension frame */

    aux_data = &item->aux_data.ext;

    if (aux_data->builtin == 0) {
      if (session_is_closing(session)) {
        return NGHTTP2_ERR_SESSION_CLOSING;
      }

      return session_pack_extension(session, &session->aob.framebufs, frame);
    }

    switch (frame->hd.type) {
    case NGHTTP2_ALTSVC:
      rv = session_predicate_altsvc_send(session, frame->hd.stream_id);
      if (rv != 0) {
        return rv;
      }

      nghttp2_frame_pack_altsvc(&session->aob.framebufs, &frame->ext);

      return 0;
    case NGHTTP2_ORIGIN:
      rv = session_predicate_origin_send(session);
      if (rv != 0) {
        return rv;
      }

      rv = nghttp2_frame_pack_origin(&session->aob.framebufs, &frame->ext);
      if (rv != 0) {
        return rv;
      }

      return 0;
    default:
      /* Unreachable here */
      assert(0);
      return 0;
    }
  }
  }
}


void test_nghttp2_http_push_promise(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_hd_deflater deflater;
  nghttp2_mem *mem;
  nghttp2_bufs bufs;
  ssize_t rv;
  nghttp2_stream *stream;
  const nghttp2_nv bad_reqnv[] = {MAKE_NV(":method", "GET")};
  nghttp2_outbound_item *item;

  mem = nghttp2_mem_default();
  frame_pack_bufs_init(&bufs);

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.send_callback = null_send_callback;

  /* good PUSH_PROMISE case */
  nghttp2_session_client_new(&session, &callbacks, NULL);

  nghttp2_hd_deflate_init(&deflater, mem);

  open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING);

  rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2,
                         reqnv, ARRLEN(reqnv), mem);
  CU_ASSERT(0 == rv);

  rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
                                nghttp2_buf_len(&bufs.head->buf));

  CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);

  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));

  stream = nghttp2_session_get_stream(session, 2);
  CU_ASSERT(NULL != stream);

  nghttp2_bufs_reset(&bufs);

  rv = pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv,
                    ARRLEN(resnv), mem);

  CU_ASSERT(0 == rv);

  rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
                                nghttp2_buf_len(&bufs.head->buf));

  CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);

  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));

  CU_ASSERT(200 == stream->status_code);

  nghttp2_bufs_reset(&bufs);

  /* PUSH_PROMISE lacks mandatory header */
  rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 4,
                         bad_reqnv, ARRLEN(bad_reqnv), mem);

  CU_ASSERT(0 == rv);

  rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos,
                                nghttp2_buf_len(&bufs.head->buf));

  CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv);

  item = nghttp2_session_get_next_ob_item(session);

  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
  CU_ASSERT(4 == item->frame.hd.stream_id);

  nghttp2_bufs_reset(&bufs);

  nghttp2_hd_deflate_free(&deflater);
  nghttp2_session_del(session);
  nghttp2_bufs_free(&bufs);
}


int nghttp2_session_adjust_idle_stream(nghttp2_session *session) {
  size_t max;
  int rv;

  /* Make minimum number of idle streams 16, and maximum 100, which
     are arbitrary chosen numbers. */
  max = nghttp2_min(
      100, nghttp2_max(
               16, nghttp2_min(session->local_settings.max_concurrent_streams,
                               session->pending_local_max_concurrent_stream)));

  DEBUGF("stream: adjusting kept idle streams num_idle_streams=%zu, max=%zu\n",
         session->num_idle_streams, max);

  while (session->num_idle_streams > max) {
    nghttp2_stream *head;
    nghttp2_stream *next;

    head = session->idle_stream_head;
    assert(head);

    next = head->closed_next;

    rv = nghttp2_session_destroy_stream(session, head);
    if (rv != 0) {
      return rv;
    }

    /* head is now destroyed */

    session->idle_stream_head = next;

    if (session->idle_stream_head) {
      session->idle_stream_head->closed_prev = NULL;
    } else {
      session->idle_stream_tail = NULL;
    }

    --session->num_idle_streams;
  }

  return 0;
}


void test_nghttp2_submit_window_update(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  my_user_data ud;
  nghttp2_outbound_item *item;
  nghttp2_stream *stream;

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.send_callback = null_send_callback;

  nghttp2_session_client_new(&session, &callbacks, &ud);
  stream = open_recv_stream(session, 2);
  stream->recv_window_size = 4096;

  CU_ASSERT(0 ==
            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 1024));
  item = nghttp2_session_get_next_ob_item(session);
  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
  CU_ASSERT(1024 == item->frame.window_update.window_size_increment);
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(3072 == stream->recv_window_size);

  CU_ASSERT(0 ==
            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 4096));
  item = nghttp2_session_get_next_ob_item(session);
  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
  CU_ASSERT(4096 == item->frame.window_update.window_size_increment);
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(0 == stream->recv_window_size);

  CU_ASSERT(0 ==
            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 4096));
  item = nghttp2_session_get_next_ob_item(session);
  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
  CU_ASSERT(4096 == item->frame.window_update.window_size_increment);
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(0 == stream->recv_window_size);

  CU_ASSERT(0 ==
            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 0));
  /* It is ok if stream is closed or does not exist at the call
     time */
  CU_ASSERT(0 ==
            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 4, 4096));

  nghttp2_session_del(session);
}


static int session_call_on_invalid_header(nghttp2_session *session,
                                          const nghttp2_frame *frame,
                                          const nghttp2_hd_nv *nv) {
  int rv;
  if (session->callbacks.on_invalid_header_callback2) {
    rv = session->callbacks.on_invalid_header_callback2(
        session, frame, nv->name, nv->value, nv->flags, session->user_data);
  } else if (session->callbacks.on_invalid_header_callback) {
    rv = session->callbacks.on_invalid_header_callback(
        session, frame, nv->name->base, nv->name->len, nv->value->base,
        nv->value->len, nv->flags, session->user_data);
  } else {
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
  }

  if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
    return rv;
  }
  if (rv != 0) {
    return NGHTTP2_ERR_CALLBACK_FAILURE;
  }

  return 0;
}


static int session_update_connection_consumed_size(nghttp2_session *session,
                                                   size_t delta_size) {
  return session_update_consumed_size(
      session, &session->consumed_size, &session->recv_window_size,
      session->window_update_queued, 0, delta_size, session->local_window_size);
}


void test_nghttp2_session_stream_dep_add(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_stream *a, *b, *c, *d, *e, *root;

  memset(&callbacks, 0, sizeof(callbacks));

  nghttp2_session_server_new(&session, &callbacks, NULL);

  root = &session->root;

  a = open_stream(session, 1);

  c = open_stream_with_dep(session, 5, a);
  b = open_stream_with_dep(session, 3, a);
  d = open_stream_with_dep(session, 7, c);

  /* a
   * |
   * b--c
   *    |
   *    d
   */

  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight);
  CU_ASSERT(0 == b->sum_dep_weight);
  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
  CU_ASSERT(0 == d->sum_dep_weight);

  check_stream_dep_sib(a, root, b, NULL, NULL);
  check_stream_dep_sib(b, a, NULL, NULL, c);
  check_stream_dep_sib(c, a, d, b, NULL);
  check_stream_dep_sib(d, c, NULL, NULL, NULL);

  CU_ASSERT(a == session->root.dep_next);

  e = open_stream_with_dep_excl(session, 9, a);

  /* a
   * |
   * e
   * |
   * b--c
   *    |
   *    d
   */

  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight);
  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == e->sum_dep_weight);
  CU_ASSERT(0 == b->sum_dep_weight);
  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
  CU_ASSERT(0 == d->sum_dep_weight);

  check_stream_dep_sib(a, root, e, NULL, NULL);
  check_stream_dep_sib(e, a, b, NULL, NULL);
  check_stream_dep_sib(b, e, NULL, NULL, c);
  check_stream_dep_sib(c, e, d, b, NULL);
  check_stream_dep_sib(d, c, NULL, NULL, NULL);

  CU_ASSERT(a == session->root.dep_next);

  nghttp2_session_del(session);
}


static ssize_t temporal_failure_data_source_read_callback(
    nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
    uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
  (void)session;
  (void)stream_id;
  (void)buf;
  (void)len;
  (void)data_flags;
  (void)source;
  (void)user_data;

  return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}


void test_nghttp2_session_keep_closed_stream(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  const size_t max_concurrent_streams = 5;
  nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
                               (uint32_t)max_concurrent_streams};
  size_t i;

  memset(&callbacks, 0, sizeof(callbacks));
  callbacks.send_callback = null_send_callback;

  nghttp2_session_server_new(&session, &callbacks, NULL);

  nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);

  for (i = 0; i < max_concurrent_streams; ++i) {
    open_recv_stream(session, (int32_t)i * 2 + 1);
  }

  CU_ASSERT(0 == session->num_closed_streams);

  nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);

  CU_ASSERT(1 == session->num_closed_streams);
  CU_ASSERT(1 == session->closed_stream_tail->stream_id);
  CU_ASSERT(session->closed_stream_tail == session->closed_stream_head);

  nghttp2_session_close_stream(session, 5, NGHTTP2_NO_ERROR);

  CU_ASSERT(2 == session->num_closed_streams);
  CU_ASSERT(5 == session->closed_stream_tail->stream_id);
  CU_ASSERT(1 == session->closed_stream_head->stream_id);
  CU_ASSERT(session->closed_stream_head ==
            session->closed_stream_tail->closed_prev);
  CU_ASSERT(NULL == session->closed_stream_tail->closed_next);
  CU_ASSERT(session->closed_stream_tail ==
            session->closed_stream_head->closed_next);
  CU_ASSERT(NULL == session->closed_stream_head->closed_prev);

  open_recv_stream(session, 11);
  nghttp2_session_adjust_closed_stream(session);

  CU_ASSERT(1 == session->num_closed_streams);
  CU_ASSERT(5 == session->closed_stream_tail->stream_id);
  CU_ASSERT(session->closed_stream_tail == session->closed_stream_head);
  CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
  CU_ASSERT(NULL == session->closed_stream_head->closed_next);

  open_recv_stream(session, 13);
  nghttp2_session_adjust_closed_stream(session);

  CU_ASSERT(0 == session->num_closed_streams);
  CU_ASSERT(NULL == session->closed_stream_tail);
  CU_ASSERT(NULL == session->closed_stream_head);

  nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR);

  CU_ASSERT(1 == session->num_closed_streams);
  CU_ASSERT(3 == session->closed_stream_head->stream_id);

  /* server initiated stream is not counted to max concurrent limit */
  open_sent_stream(session, 2);
  nghttp2_session_adjust_closed_stream(session);

  CU_ASSERT(1 == session->num_closed_streams);
  CU_ASSERT(3 == session->closed_stream_head->stream_id);

  nghttp2_session_close_stream(session, 2, NGHTTP2_NO_ERROR);

  CU_ASSERT(1 == session->num_closed_streams);
  CU_ASSERT(3 == session->closed_stream_head->stream_id);

  nghttp2_session_del(session);
}


static int pause_on_header_callback(nghttp2_session *session,
                                    const nghttp2_frame *frame,
                                    const uint8_t *name, size_t namelen,
                                    const uint8_t *value, size_t valuelen,
                                    uint8_t flags, void *user_data) {
  on_header_callback(session, frame, name, namelen, value, valuelen, flags,
                     user_data);
  return NGHTTP2_ERR_PAUSE;
}


static int submit_response_on_stream_close(nghttp2_session *session,
                                           int32_t stream_id,
                                           uint32_t error_code,
                                           void *user_data) {
  nghttp2_data_provider data_prd;
  (void)error_code;
  (void)user_data;

  data_prd.read_callback = temporal_failure_data_source_read_callback;

  // Attempt to submit response or data to the stream being closed
  switch (stream_id) {
  case 1:
    CU_ASSERT(0 == nghttp2_submit_response(session, stream_id, resnv,
                                           ARRLEN(resnv), &data_prd));
    break;
  case 3:
    CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, stream_id,
                                       &data_prd));
    break;
  }

  return 0;
}


static int session_process_altsvc_frame(nghttp2_session *session) {
  nghttp2_inbound_frame *iframe = &session->iframe;
  nghttp2_frame *frame = &iframe->frame;

  nghttp2_frame_unpack_altsvc_payload(
      &frame->ext, nghttp2_get_uint16(iframe->sbuf.pos), iframe->lbuf.pos,
      nghttp2_buf_len(&iframe->lbuf));

  /* nghttp2_frame_unpack_altsvc_payload steals buffer from
     iframe->lbuf */
  nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0);

  return nghttp2_session_on_altsvc_received(session, frame);
}


void test_nghttp2_session_repeated_priority_change(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_frame frame;
  nghttp2_priority_spec pri_spec;
  int32_t stream_id, last_stream_id;
  int32_t max_streams = 20;

  memset(&callbacks, 0, sizeof(callbacks));

  nghttp2_session_server_new(&session, &callbacks, NULL);

  session->local_settings.max_concurrent_streams = (uint32_t)max_streams;

  /* 1 -> 0 */
  nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
  nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);

  CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));

  nghttp2_frame_priority_free(&frame.priority);

  last_stream_id = max_streams * 2 + 1;

  for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) {
    /* 1 -> stream_id */
    nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0);
    nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);

    CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));

    nghttp2_frame_priority_free(&frame.priority);
  }

  CU_ASSERT(20 == session->num_idle_streams);
  CU_ASSERT(1 == session->idle_stream_head->stream_id);

  /* 1 -> last_stream_id */
  nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0);
  nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);

  CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));

  nghttp2_frame_priority_free(&frame.priority);

  CU_ASSERT(20 == session->num_idle_streams);
  CU_ASSERT(3 == session->idle_stream_head->stream_id);

  nghttp2_session_del(session);
}


void test_nghttp2_session_open_stream(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_stream *stream;
  nghttp2_priority_spec pri_spec;

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  nghttp2_session_server_new(&session, &callbacks, NULL);

  nghttp2_priority_spec_init(&pri_spec, 0, 245, 0);

  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
                                       &pri_spec, NGHTTP2_STREAM_OPENED, NULL);
  CU_ASSERT(1 == session->num_incoming_streams);
  CU_ASSERT(0 == session->num_outgoing_streams);
  CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
  CU_ASSERT(245 == stream->weight);
  CU_ASSERT(&session->root == stream->dep_prev);
  CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags);

  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
                                       &pri_spec_default,
                                       NGHTTP2_STREAM_OPENING, NULL);
  CU_ASSERT(1 == session->num_incoming_streams);
  CU_ASSERT(1 == session->num_outgoing_streams);
  CU_ASSERT(&session->root == stream->dep_prev);
  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
  CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags);

  stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE,
                                       &pri_spec_default,
                                       NGHTTP2_STREAM_RESERVED, NULL);
  CU_ASSERT(1 == session->num_incoming_streams);
  CU_ASSERT(1 == session->num_outgoing_streams);
  CU_ASSERT(&session->root == stream->dep_prev);
  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
  CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags);

  nghttp2_priority_spec_init(&pri_spec, 1, 17, 1);

  stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE,
                                       &pri_spec, NGHTTP2_STREAM_OPENED, NULL);
  CU_ASSERT(17 == stream->weight);
  CU_ASSERT(1 == stream->dep_prev->stream_id);

  /* Dependency to idle stream */
  nghttp2_priority_spec_init(&pri_spec, 1000000007, 240, 1);

  stream = nghttp2_session_open_stream(session, 5, NGHTTP2_STREAM_FLAG_NONE,
                                       &pri_spec, NGHTTP2_STREAM_OPENED, NULL);
  CU_ASSERT(240 == stream->weight);
  CU_ASSERT(1000000007 == stream->dep_prev->stream_id);

  stream = nghttp2_session_get_stream_raw(session, 1000000007);

  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
  CU_ASSERT(&session->root == stream->dep_prev);

  /* Dependency to closed stream which is not in dependency tree */
  session->last_recv_stream_id = 7;

  nghttp2_priority_spec_init(&pri_spec, 7, 10, 0);

  stream = nghttp2_session_open_stream(session, 9, NGHTTP2_FLAG_NONE, &pri_spec,
                                       NGHTTP2_STREAM_OPENED, NULL);

  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
  CU_ASSERT(&session->root == stream->dep_prev);

  nghttp2_session_del(session);

  nghttp2_session_client_new(&session, &callbacks, NULL);
  stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE,
                                       &pri_spec_default,
                                       NGHTTP2_STREAM_RESERVED, NULL);
  CU_ASSERT(0 == session->num_incoming_streams);
  CU_ASSERT(0 == session->num_outgoing_streams);
  CU_ASSERT(&session->root == stream->dep_prev);
  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
  CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags);

  nghttp2_session_del(session);
}


void test_nghttp2_session_reset_pending_headers(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_stream *stream;
  int32_t stream_id;
  my_user_data ud;

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.send_callback = null_send_callback;
  callbacks.on_frame_send_callback = on_frame_send_callback;
  callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
  callbacks.on_stream_close_callback = on_stream_close_callback;

  nghttp2_session_client_new(&session, &callbacks, &ud);

  stream_id = nghttp2_submit_request(session, NULL, NULL, 0, NULL, NULL);
  CU_ASSERT(stream_id >= 1);

  nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
                            NGHTTP2_CANCEL);

  session->remote_settings.max_concurrent_streams = 0;

  /* RST_STREAM cancels pending HEADERS and is not actually sent. */
  ud.frame_send_cb_called = 0;
  CU_ASSERT(0 == nghttp2_session_send(session));

  CU_ASSERT(0 == ud.frame_send_cb_called);

  stream = nghttp2_session_get_stream(session, stream_id);

  CU_ASSERT(NULL == stream);

  /* See HEADERS is not sent.  on_stream_close is called just like
     transmission failure. */
  session->remote_settings.max_concurrent_streams = 1;

  ud.frame_not_send_cb_called = 0;
  ud.stream_close_error_code = 0;
  CU_ASSERT(0 == nghttp2_session_send(session));

  CU_ASSERT(1 == ud.frame_not_send_cb_called);
  CU_ASSERT(NGHTTP2_HEADERS == ud.not_sent_frame_type);
  CU_ASSERT(NGHTTP2_CANCEL == ud.stream_close_error_code);

  stream = nghttp2_session_get_stream(session, stream_id);

  CU_ASSERT(NULL == stream);

  nghttp2_session_del(session);
}


void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) {
  option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_PING_ACK;
  option->no_auto_ping_ack = val;
}


static int on_frame_recv_callback(nghttp2_session *session,
                                  const nghttp2_frame *frame, void *user_data) {
  my_user_data *ud = (my_user_data *)user_data;
  (void)session;

  ++ud->frame_recv_cb_called;
  ud->recv_frame_type = frame->hd.type;
  ud->recv_frame_hd = frame->hd;

  return 0;
}


static void ob_q_free(nghttp2_outbound_queue *q, nghttp2_mem *mem) {
  nghttp2_outbound_item *item, *next;
  for (item = q->head; item;) {
    next = item->qnext;
    nghttp2_outbound_item_free(item, mem);
    nghttp2_mem_free(mem, item);
    item = next;
  }
}


static int cancel_on_extension_chunk_recv_callback(nghttp2_session *session,
                                                   const nghttp2_frame_hd *hd,
                                                   const uint8_t *data,
                                                   size_t len,
                                                   void *user_data) {
  (void)session;
  (void)hd;
  (void)data;
  (void)len;
  (void)user_data;

  return NGHTTP2_ERR_CANCEL;
}


int nghttp2_session_check_server_session(nghttp2_session *session) {
  return session->server;
}


static int on_begin_frame_callback(nghttp2_session *session,
                                   const nghttp2_frame_hd *hd,
                                   void *user_data) {
  my_user_data *ud = (my_user_data *)user_data;
  (void)session;
  (void)hd;

  ++ud->begin_frame_cb_called;
  return 0;
}


void test_nghttp2_session_flooding(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_bufs bufs;
  nghttp2_buf *buf;
  nghttp2_frame frame;
  nghttp2_mem *mem;
  size_t i;

  mem = nghttp2_mem_default();

  frame_pack_bufs_init(&bufs);

  memset(&callbacks, 0, sizeof(callbacks));

  /* PING ACK */
  nghttp2_session_server_new(&session, &callbacks, NULL);

  nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL);
  nghttp2_frame_pack_ping(&bufs, &frame.ping);
  nghttp2_frame_ping_free(&frame.ping);

  buf = &bufs.head->buf;

  for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) {
    CU_ASSERT(
        (ssize_t)nghttp2_buf_len(buf) ==
        nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
  }

  CU_ASSERT(NGHTTP2_ERR_FLOODED ==
            nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));

  nghttp2_session_del(session);

  /* SETTINGS ACK */
  nghttp2_bufs_reset(&bufs);

  nghttp2_session_server_new(&session, &callbacks, NULL);

  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0);
  nghttp2_frame_pack_settings(&bufs, &frame.settings);
  nghttp2_frame_settings_free(&frame.settings, mem);

  buf = &bufs.head->buf;

  for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) {
    CU_ASSERT(
        (ssize_t)nghttp2_buf_len(buf) ==
        nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
  }

  CU_ASSERT(NGHTTP2_ERR_FLOODED ==
            nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));

  nghttp2_session_del(session);
  nghttp2_bufs_free(&bufs);
}


static int session_process_priority_frame(nghttp2_session *session) {
  nghttp2_inbound_frame *iframe = &session->iframe;
  nghttp2_frame *frame = &iframe->frame;

  nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos);

  return nghttp2_session_on_priority_received(session, frame);
}


void test_nghttp2_session_on_push_promise_received(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  my_user_data user_data;
  nghttp2_frame frame;
  nghttp2_stream *stream, *promised_stream;
  nghttp2_outbound_item *item;
  nghttp2_nv malformed_nva[] = {MAKE_NV(":path", "\x01")};
  nghttp2_nv *nva;
  size_t nvlen;
  nghttp2_mem *mem;
  nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_ENABLE_PUSH, 0};

  mem = nghttp2_mem_default();
  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.send_callback = null_send_callback;
  callbacks.on_begin_headers_callback = on_begin_headers_callback;
  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;

  nghttp2_session_client_new(&session, &callbacks, &user_data);

  stream = open_sent_stream(session, 1);

  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
                                  1, 2, NULL, 0);

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;

  CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(1 == user_data.begin_headers_cb_called);
  CU_ASSERT(1 == session->num_incoming_reserved_streams);
  promised_stream = nghttp2_session_get_stream(session, 2);
  CU_ASSERT(NGHTTP2_STREAM_RESERVED == promised_stream->state);
  CU_ASSERT(2 == session->last_recv_stream_id);

  /* Attempt to PUSH_PROMISE against half close (remote) */
  nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
  frame.push_promise.promised_stream_id = 4;

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(0 == user_data.begin_headers_cb_called);
  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
  CU_ASSERT(1 == session->num_incoming_reserved_streams);
  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4));
  item = nghttp2_session_get_next_ob_item(session);
  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
  CU_ASSERT(NGHTTP2_STREAM_CLOSED == item->frame.goaway.error_code);
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(4 == session->last_recv_stream_id);

  nghttp2_session_del(session);

  nghttp2_session_client_new(&session, &callbacks, &user_data);

  stream = open_sent_stream(session, 1);

  /* Attempt to PUSH_PROMISE against stream in closing state */
  stream->state = NGHTTP2_STREAM_CLOSING;
  frame.push_promise.promised_stream_id = 6;

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(0 == user_data.begin_headers_cb_called);
  CU_ASSERT(0 == session->num_incoming_reserved_streams);
  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6));
  item = nghttp2_session_get_next_ob_item(session);
  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
  CU_ASSERT(6 == item->frame.hd.stream_id);
  CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
  CU_ASSERT(0 == nghttp2_session_send(session));

  /* Attempt to PUSH_PROMISE against idle stream */
  frame.hd.stream_id = 3;
  frame.push_promise.promised_stream_id = 8;

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(0 == user_data.begin_headers_cb_called);
  CU_ASSERT(0 == session->num_incoming_reserved_streams);
  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
  item = nghttp2_session_get_next_ob_item(session);
  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
  CU_ASSERT(0 == item->frame.hd.stream_id);
  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code);
  CU_ASSERT(0 == nghttp2_session_send(session));

  nghttp2_session_del(session);

  nghttp2_session_client_new(&session, &callbacks, &user_data);

  stream = open_sent_stream(session, 1);

  /* Same ID twice */
  frame.hd.stream_id = 1;
  frame.push_promise.promised_stream_id = 2;

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(1 == user_data.begin_headers_cb_called);
  CU_ASSERT(1 == session->num_incoming_reserved_streams);
  CU_ASSERT(NULL != nghttp2_session_get_stream(session, 2));

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(0 == user_data.begin_headers_cb_called);
  CU_ASSERT(1 == session->num_incoming_reserved_streams);
  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
  item = nghttp2_session_get_next_ob_item(session);
  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code);
  CU_ASSERT(0 == nghttp2_session_send(session));

  /* After GOAWAY, PUSH_PROMISE will be discarded */
  frame.push_promise.promised_stream_id = 10;

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(0 == user_data.begin_headers_cb_called);
  CU_ASSERT(1 == session->num_incoming_reserved_streams);
  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 10));
  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));

  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
  nghttp2_session_del(session);

  nghttp2_session_client_new(&session, &callbacks, &user_data);

  open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);

  /* Attempt to PUSH_PROMISE against reserved (remote) stream */
  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
                                  2, 4, NULL, 0);

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(0 == user_data.begin_headers_cb_called);
  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
  CU_ASSERT(1 == session->num_incoming_reserved_streams);

  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
  nghttp2_session_del(session);

  /* Disable PUSH */
  nghttp2_session_client_new(&session, &callbacks, &user_data);

  open_sent_stream(session, 1);

  session->local_settings.enable_push = 0;

  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
                                  1, 2, NULL, 0);

  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(0 == user_data.begin_headers_cb_called);
  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
  CU_ASSERT(0 == session->num_incoming_reserved_streams);

  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
  nghttp2_session_del(session);

  /* Check malformed headers. We accept malformed headers */
  nghttp2_session_client_new(&session, &callbacks, &user_data);

  open_sent_stream(session, 1);

  nvlen = ARRLEN(malformed_nva);
  nghttp2_nv_array_copy(&nva, malformed_nva, nvlen, mem);
  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
                                  1, 2, nva, nvlen);
  user_data.begin_headers_cb_called = 0;
  user_data.invalid_frame_recv_cb_called = 0;
  CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(1 == user_data.begin_headers_cb_called);
  CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called);

  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
  nghttp2_session_del(session);

  /* If local_settings.enable_push = 0 is pending, but not acked from
     peer, incoming PUSH_PROMISE is rejected */
  nghttp2_session_client_new(&session, &callbacks, &user_data);

  open_sent_stream(session, 1);

  /* Submit settings with ENABLE_PUSH = 0 (thus disabling push) */
  nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);

  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
                                  1, 2, NULL, 0);

  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(0 == session->num_incoming_reserved_streams);

  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
  nghttp2_session_del(session);

  /* Check max_incoming_reserved_streams */
  nghttp2_session_client_new(&session, &callbacks, &user_data);
  session->max_incoming_reserved_streams = 1;

  open_sent_stream(session, 1);
  open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED);

  CU_ASSERT(1 == session->num_incoming_reserved_streams);

  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
                                  1, 4, NULL, 0);

  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
            nghttp2_session_on_push_promise_received(session, &frame));

  CU_ASSERT(1 == session->num_incoming_reserved_streams);

  item = nghttp2_session_get_next_ob_item(session);

  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
  CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);

  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
  nghttp2_session_del(session);
}


static int session_call_on_frame_received(nghttp2_session *session,
                                          nghttp2_frame *frame) {
  int rv;
  if (session->callbacks.on_frame_recv_callback) {
    rv = session->callbacks.on_frame_recv_callback(session, frame,
                                                   session->user_data);
    if (rv != 0) {
      return NGHTTP2_ERR_CALLBACK_FAILURE;
    }
  }
  return 0;
}


void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
                                                    uint32_t val) {
  option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS;
  option->max_reserved_remote_streams = val;
}


void test_nghttp2_session_is_my_stream_id(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  nghttp2_session_server_new(&session, &callbacks, NULL);

  CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 0));
  CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 1));
  CU_ASSERT(1 == nghttp2_session_is_my_stream_id(session, 2));

  nghttp2_session_del(session);

  nghttp2_session_client_new(&session, &callbacks, NULL);

  CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 0));
  CU_ASSERT(1 == nghttp2_session_is_my_stream_id(session, 1));
  CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 2));

  nghttp2_session_del(session);
}


void test_nghttp2_session_on_ctrl_not_send(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  my_user_data user_data;
  nghttp2_stream *stream;

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
  callbacks.send_callback = null_send_callback;
  user_data.frame_not_send_cb_called = 0;
  user_data.not_sent_frame_type = 0;
  user_data.not_sent_error = 0;

  nghttp2_session_server_new(&session, &callbacks, &user_data);
  stream =
      open_recv_stream3(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default,
                        NGHTTP2_STREAM_OPENING, &user_data);

  /* Check response HEADERS */
  /* Send bogus stream ID */
  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 3,
                                        NULL, NULL, 0, NULL));
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(1 == user_data.frame_not_send_cb_called);
  CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type);
  CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == user_data.not_sent_error);

  user_data.frame_not_send_cb_called = 0;
  /* Shutdown transmission */
  stream->shut_flags |= NGHTTP2_SHUT_WR;
  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
                                        NULL, NULL, 0, NULL));
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(1 == user_data.frame_not_send_cb_called);
  CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type);
  CU_ASSERT(NGHTTP2_ERR_STREAM_SHUT_WR == user_data.not_sent_error);

  stream->shut_flags = NGHTTP2_SHUT_NONE;
  user_data.frame_not_send_cb_called = 0;
  /* Queue RST_STREAM */
  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
                                        NULL, NULL, 0, NULL));
  CU_ASSERT(0 == nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1,
                                           NGHTTP2_INTERNAL_ERROR));
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(1 == user_data.frame_not_send_cb_called);
  CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type);
  CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSING == user_data.not_sent_error);

  nghttp2_session_del(session);

  /* Check request HEADERS */
  user_data.frame_not_send_cb_called = 0;
  CU_ASSERT(nghttp2_session_client_new(&session, &callbacks, &user_data) == 0);
  /* Maximum Stream ID is reached */
  session->next_stream_id = (1u << 31) + 1;
  CU_ASSERT(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE ==
            nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, NULL,
                                   NULL, 0, NULL));

  user_data.frame_not_send_cb_called = 0;
  /* GOAWAY received */
  session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
  session->next_stream_id = 9;

  CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1,
                                       NULL, NULL, 0, NULL));
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(1 == user_data.frame_not_send_cb_called);
  CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type);
  CU_ASSERT(NGHTTP2_ERR_START_STREAM_NOT_ALLOWED == user_data.not_sent_error);

  nghttp2_session_del(session);
}


void test_nghttp2_session_stream_attach_item_subtree(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_stream *a, *b, *c, *d, *e, *f;
  nghttp2_outbound_item *da, *db, *dd, *de;
  nghttp2_mem *mem;

  mem = nghttp2_mem_default();

  memset(&callbacks, 0, sizeof(callbacks));

  nghttp2_session_server_new(&session, &callbacks, NULL);

  a = open_stream(session, 1);
  b = open_stream_with_dep(session, 3, a);
  c = open_stream_with_dep(session, 5, a);
  d = open_stream_with_dep(session, 7, c);

  e = open_stream_with_dep_weight(session, 9, 32, &session->root);
  f = open_stream_with_dep(session, 11, e);

  /*
   * a        e
   * |        |
   * c--b     f
   * |
   * d
   */

  de = create_data_ob_item(mem);

  nghttp2_stream_attach_item(e, de);

  db = create_data_ob_item(mem);

  nghttp2_stream_attach_item(b, db);

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(!c->queued);
  CU_ASSERT(!d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
  CU_ASSERT(nghttp2_pq_empty(&b->obq));
  CU_ASSERT(nghttp2_pq_empty(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Insert subtree e under a */

  nghttp2_stream_dep_remove_subtree(e);
  nghttp2_stream_dep_insert_subtree(a, e);

  /*
   * a
   * |
   * e
   * |
   * f--c--b
   *    |
   *    d
   */

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(!c->queued);
  CU_ASSERT(!d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
  CU_ASSERT(nghttp2_pq_empty(&b->obq));
  CU_ASSERT(nghttp2_pq_empty(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Remove subtree b */

  nghttp2_stream_dep_remove_subtree(b);

  CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, b));

  /*
   * a       b
   * |
   * e
   * |
   * f--c
   *    |
   *    d
   */

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(!c->queued);
  CU_ASSERT(!d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
  CU_ASSERT(nghttp2_pq_empty(&b->obq));
  CU_ASSERT(nghttp2_pq_empty(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Remove subtree a, and add it to root again */

  nghttp2_stream_dep_remove_subtree(a);

  CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, a));

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(!c->queued);
  CU_ASSERT(!d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
  CU_ASSERT(nghttp2_pq_empty(&b->obq));
  CU_ASSERT(nghttp2_pq_empty(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Remove subtree c */

  nghttp2_stream_dep_remove_subtree(c);

  CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, c));

  /*
   * a       b     c
   * |             |
   * e             d
   * |
   * f
   */

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(!c->queued);
  CU_ASSERT(!d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
  CU_ASSERT(nghttp2_pq_empty(&b->obq));
  CU_ASSERT(nghttp2_pq_empty(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  dd = create_data_ob_item(mem);

  nghttp2_stream_attach_item(d, dd);

  /* Add subtree c to a */

  nghttp2_stream_dep_remove_subtree(c);
  nghttp2_stream_dep_add_subtree(a, c);

  /*
   * a       b
   * |
   * c--e
   * |  |
   * d  f
   */

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(c->queued);
  CU_ASSERT(d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(2 == nghttp2_pq_size(&a->obq));
  CU_ASSERT(nghttp2_pq_empty(&b->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Insert b under a */

  nghttp2_stream_dep_remove_subtree(b);
  nghttp2_stream_dep_insert_subtree(a, b);

  /*
   * a
   * |
   * b
   * |
   * c--e
   * |  |
   * d  f
   */

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(c->queued);
  CU_ASSERT(d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(1 == nghttp2_pq_size(&a->obq));
  CU_ASSERT(2 == nghttp2_pq_size(&b->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Remove subtree b */

  nghttp2_stream_dep_remove_subtree(b);
  CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, b));

  /*
   * b       a
   * |
   * e--c
   * |  |
   * f  d
   */

  CU_ASSERT(!a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(c->queued);
  CU_ASSERT(d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(nghttp2_pq_empty(&a->obq));
  CU_ASSERT(2 == nghttp2_pq_size(&b->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Remove subtree c, and detach item from b, and then re-add
     subtree c under b */

  nghttp2_stream_dep_remove_subtree(c);
  nghttp2_stream_detach_item(b);
  nghttp2_stream_dep_add_subtree(b, c);

  /*
   * b       a
   * |
   * e--c
   * |  |
   * f  d
   */

  CU_ASSERT(!a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(c->queued);
  CU_ASSERT(d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(nghttp2_pq_empty(&a->obq));
  CU_ASSERT(2 == nghttp2_pq_size(&b->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Attach data to a, and add subtree a under b */

  da = create_data_ob_item(mem);
  nghttp2_stream_attach_item(a, da);
  nghttp2_stream_dep_remove_subtree(a);
  nghttp2_stream_dep_add_subtree(b, a);

  /*
   * b
   * |
   * a--e--c
   *    |  |
   *    f  d
   */

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(c->queued);
  CU_ASSERT(d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(!f->queued);

  CU_ASSERT(nghttp2_pq_empty(&a->obq));
  CU_ASSERT(3 == nghttp2_pq_size(&b->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(nghttp2_pq_empty(&e->obq));
  CU_ASSERT(nghttp2_pq_empty(&f->obq));

  /* Remove subtree c, and add under f */
  nghttp2_stream_dep_remove_subtree(c);
  nghttp2_stream_dep_insert_subtree(f, c);

  /*
   * b
   * |
   * a--e
   *    |
   *    f
   *    |
   *    c
   *    |
   *    d
   */

  CU_ASSERT(a->queued);
  CU_ASSERT(b->queued);
  CU_ASSERT(c->queued);
  CU_ASSERT(d->queued);
  CU_ASSERT(e->queued);
  CU_ASSERT(f->queued);

  CU_ASSERT(nghttp2_pq_empty(&a->obq));
  CU_ASSERT(2 == nghttp2_pq_size(&b->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&c->obq));
  CU_ASSERT(nghttp2_pq_empty(&d->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&e->obq));
  CU_ASSERT(1 == nghttp2_pq_size(&f->obq));

  /* db has been detached */
  nghttp2_outbound_item_free(db, mem);
  free(db);

  nghttp2_session_del(session);
}


static int session_handle_invalid_stream2(nghttp2_session *session,
                                          int32_t stream_id,
                                          nghttp2_frame *frame,
                                          int lib_error_code) {
  int rv;
  rv = nghttp2_session_add_rst_stream(
      session, stream_id, get_error_code_from_lib_error_code(lib_error_code));
  if (rv != 0) {
    return rv;
  }
  if (session->callbacks.on_invalid_frame_recv_callback) {
    if (session->callbacks.on_invalid_frame_recv_callback(
            session, frame, lib_error_code, session->user_data) != 0) {
      return NGHTTP2_ERR_CALLBACK_FAILURE;
    }
  }
  return 0;
}


void test_nghttp2_session_recv_settings_header_table_size(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_frame frame;
  nghttp2_bufs bufs;
  nghttp2_buf *buf;
  ssize_t rv;
  my_user_data ud;
  nghttp2_settings_entry iv[3];
  nghttp2_nv nv = MAKE_NV(":authority", "example.org");
  nghttp2_mem *mem;

  mem = nghttp2_mem_default();
  frame_pack_bufs_init(&bufs);

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.on_frame_recv_callback = on_frame_recv_callback;
  callbacks.send_callback = null_send_callback;

  nghttp2_session_client_new(&session, &callbacks, &ud);

  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
  iv[0].value = 3000;

  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
  iv[1].value = 16384;

  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2),
                              2);

  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);

  CU_ASSERT(0 == rv);
  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);

  nghttp2_frame_settings_free(&frame.settings, mem);

  buf = &bufs.head->buf;
  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));

  ud.frame_recv_cb_called = 0;

  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));

  CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
  CU_ASSERT(1 == ud.frame_recv_cb_called);

  CU_ASSERT(3000 == session->remote_settings.header_table_size);
  CU_ASSERT(16384 == session->remote_settings.initial_window_size);

  nghttp2_bufs_reset(&bufs);

  /* 2 SETTINGS_HEADER_TABLE_SIZE */
  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
  iv[0].value = 3001;

  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
  iv[1].value = 16383;

  iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
  iv[2].value = 3001;

  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3),
                              3);

  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);

  CU_ASSERT(0 == rv);
  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);

  nghttp2_frame_settings_free(&frame.settings, mem);

  buf = &bufs.head->buf;
  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));

  ud.frame_recv_cb_called = 0;

  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));

  CU_ASSERT((ssize_t)(nghttp2_buf_len(buf)) == rv);
  CU_ASSERT(1 == ud.frame_recv_cb_called);

  CU_ASSERT(3001 == session->remote_settings.header_table_size);
  CU_ASSERT(16383 == session->remote_settings.initial_window_size);

  nghttp2_bufs_reset(&bufs);

  /* 2 SETTINGS_HEADER_TABLE_SIZE; first entry clears dynamic header
     table. */

  nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL);
  nghttp2_session_send(session);

  CU_ASSERT(0 < session->hd_deflater.ctx.hd_table.len);

  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
  iv[0].value = 0;

  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
  iv[1].value = 16382;

  iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
  iv[2].value = 4096;

  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3),
                              3);

  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);

  CU_ASSERT(0 == rv);
  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);

  nghttp2_frame_settings_free(&frame.settings, mem);

  buf = &bufs.head->buf;
  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));

  ud.frame_recv_cb_called = 0;

  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));

  CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
  CU_ASSERT(1 == ud.frame_recv_cb_called);

  CU_ASSERT(4096 == session->remote_settings.header_table_size);
  CU_ASSERT(16382 == session->remote_settings.initial_window_size);
  CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len);

  nghttp2_bufs_reset(&bufs);

  /* 2 SETTINGS_HEADER_TABLE_SIZE; second entry clears dynamic header
     table. */

  nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL);
  nghttp2_session_send(session);

  CU_ASSERT(0 < session->hd_deflater.ctx.hd_table.len);

  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
  iv[0].value = 3000;

  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
  iv[1].value = 16381;

  iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
  iv[2].value = 0;

  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3),
                              3);

  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);

  CU_ASSERT(0 == rv);
  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);

  nghttp2_frame_settings_free(&frame.settings, mem);

  buf = &bufs.head->buf;
  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));

  ud.frame_recv_cb_called = 0;

  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));

  CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
  CU_ASSERT(1 == ud.frame_recv_cb_called);

  CU_ASSERT(0 == session->remote_settings.header_table_size);
  CU_ASSERT(16381 == session->remote_settings.initial_window_size);
  CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len);

  nghttp2_bufs_reset(&bufs);

  nghttp2_bufs_free(&bufs);
  nghttp2_session_del(session);
}


void test_nghttp2_submit_invalid_nv(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_nv empty_name_nv[] = {MAKE_NV("Version", "HTTP/1.1"),
                                MAKE_NV("", "empty name")};

  /* Now invalid header name/value pair in HTTP/1.1 is accepted in
     nghttp2 */

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));

  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL));

  /* nghttp2_submit_response */
  CU_ASSERT(0 == nghttp2_submit_response(session, 2, empty_name_nv,
                                         ARRLEN(empty_name_nv), NULL));

  /* nghttp2_submit_push_promise */
  open_recv_stream(session, 1);

  CU_ASSERT(0 < nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1,
                                            empty_name_nv,
                                            ARRLEN(empty_name_nv), NULL));

  nghttp2_session_del(session);

  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));

  /* nghttp2_submit_request */
  CU_ASSERT(0 < nghttp2_submit_request(session, NULL, empty_name_nv,
                                       ARRLEN(empty_name_nv), NULL, NULL));

  /* nghttp2_submit_headers */
  CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL,
                                       empty_name_nv, ARRLEN(empty_name_nv),
                                       NULL));

  nghttp2_session_del(session);
}


void test_nghttp2_session_send_data_callback(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_data_provider data_prd;
  my_user_data ud;
  accumulator acc;
  nghttp2_frame_hd hd;

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
  callbacks.send_callback = accumulator_send_callback;
  callbacks.send_data_callback = send_data_callback;

  data_prd.read_callback = no_copy_data_source_read_callback;

  acc.length = 0;
  ud.acc = &acc;

  ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;

  nghttp2_session_client_new(&session, &callbacks, &ud);

  open_sent_stream(session, 1);

  nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd);

  CU_ASSERT(0 == nghttp2_session_send(session));

  CU_ASSERT((NGHTTP2_FRAME_HDLEN + NGHTTP2_DATA_PAYLOADLEN) * 2 == acc.length);

  nghttp2_frame_unpack_frame_hd(&hd, acc.buf);

  CU_ASSERT(16384 == hd.length);
  CU_ASSERT(NGHTTP2_DATA == hd.type);
  CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);

  nghttp2_frame_unpack_frame_hd(&hd, acc.buf + NGHTTP2_FRAME_HDLEN + hd.length);

  CU_ASSERT(16384 == hd.length);
  CU_ASSERT(NGHTTP2_DATA == hd.type);
  CU_ASSERT(NGHTTP2_FLAG_END_STREAM == hd.flags);

  nghttp2_session_del(session);
}


static int on_begin_headers_callback(nghttp2_session *session,
                                     const nghttp2_frame *frame,
                                     void *user_data) {
  my_user_data *ud = (my_user_data *)user_data;
  (void)session;
  (void)frame;

  ++ud->begin_headers_cb_called;
  return 0;
}


void test_nghttp2_session_no_closed_streams(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_option *option;

  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));

  nghttp2_option_new(&option);
  nghttp2_option_set_no_closed_streams(option, 1);

  nghttp2_session_server_new2(&session, &callbacks, NULL, option);

  open_recv_stream(session, 1);

  nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);

  CU_ASSERT(0 == session->num_closed_streams);

  nghttp2_session_del(session);
  nghttp2_option_del(option);
}


void test_nghttp2_session_create_idle_stream(void) {
  nghttp2_session *session;
  nghttp2_session_callbacks callbacks;
  nghttp2_stream *stream2, *stream4, *stream8, *stream10;
  nghttp2_priority_spec pri_spec;
  int rv;
  int i;

  memset(&callbacks, 0, sizeof(callbacks));
  callbacks.send_callback = null_send_callback;

  nghttp2_session_server_new(&session, &callbacks, NULL);

  stream2 = open_sent_stream(session, 2);

  nghttp2_priority_spec_init(&pri_spec, 2, 111, 1);

  rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec);

  CU_ASSERT(0 == rv);

  stream4 = nghttp2_session_get_stream_raw(session, 4);

  CU_ASSERT(4 == stream4->stream_id);
  CU_ASSERT(111 == stream4->weight);
  CU_ASSERT(stream2 == stream4->dep_prev);
  CU_ASSERT(stream4 == stream2->dep_next);

  /* If pri_spec->stream_id does not exist, and it is idle stream, it
     is created too */
  nghttp2_priority_spec_init(&pri_spec, 8, 109, 0);

  rv = nghttp2_session_create_idle_stream(session, 8, &pri_spec);

  CU_ASSERT(0 == rv);

  stream8 = nghttp2_session_get_stream_raw(session, 8);
  stream10 = nghttp2_session_get_stream_raw(session, 10);

  CU_ASSERT(8 == stream8->stream_id);
  CU_ASSERT(109 == stream8->weight);
  CU_ASSERT(10 == stream10->stream_id);
  CU_ASSERT(16 == stream10->weight);
  CU_ASSERT(stream10 == stream8->dep_prev);
  CU_ASSERT(&session->root == stream10->dep_prev);

  /* It is an error to attempt to create already existing idle
     stream */
  rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec);

  CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);

  /* It is an error to depend on itself */
  pri_spec.stream_id = 6;

  rv = nghttp2_session_create_idle_stream(session, 6, &pri_spec);
  CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);

  /* It is an error to create root stream (0) as idle stream */
  rv = nghttp2_session_create_idle_stream(session, 0, &pri_spec);
  CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);

  /* It is an error to create non-idle stream */
  session->next_stream_id = 20;
  pri_spec.stream_id = 2;

  rv = nghttp2_session_create_idle_stream(session, 18, &pri_spec);

  CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);

  nghttp2_session_del(session);

  /* Check that this works in client session too */
  nghttp2_session_client_new(&session, &callbacks, NULL);

  nghttp2_priority_spec_init(&pri_spec, 4, 99, 1);

  rv = nghttp2_session_create_idle_stream(session, 2, &pri_spec);

  CU_ASSERT(0 == rv);

  stream4 = nghttp2_session_get_stream_raw(session, 4);
  stream2 = nghttp2_session_get_stream_raw(session, 2);

  CU_ASSERT(NULL != stream4);
  CU_ASSERT(NULL != stream2);
  CU_ASSERT(&session->root == stream4->dep_prev);
  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream4->weight);
  CU_ASSERT(stream4 == stream2->dep_prev);
  CU_ASSERT(99 == stream2->weight);

  nghttp2_session_del(session);

  /* Check that idle stream is reduced when nghttp2_session_send() is
     called. */
  nghttp2_session_server_new(&session, &callbacks, NULL);

  session->local_settings.max_concurrent_streams = 30;

  nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
  for (i = 0; i < 100; ++i) {
    rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec);

    CU_ASSERT(0 == rv);

    nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0);
  }

  CU_ASSERT(100 == session->num_idle_streams);
  CU_ASSERT(0 == nghttp2_session_send(session));
  CU_ASSERT(30 == session->num_idle_streams);
  CU_ASSERT(141 == session->idle_stream_head->stream_id);

  nghttp2_session_del(session);

  /* Check that idle stream is reduced when nghttp2_session_mem_recv() is
     called. */
  nghttp2_session_client_new(&session, &callbacks, NULL);

  session->local_settings.max_concurrent_streams = 30;

  nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
  for (i = 0; i < 100; ++i) {
    rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec);

    CU_ASSERT(0 == rv);

    nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0);
  }

  CU_ASSERT(100 == session->num_idle_streams);
  CU_ASSERT(0 == nghttp2_session_mem_recv(session, NULL, 0));
  CU_ASSERT(30 == session->num_idle_streams);
  CU_ASSERT(141 == session->idle_stream_head->stream_id);

  nghttp2_session_del(session);
}


static int reset_on_invalid_header_callback(nghttp2_session *session,
                                            const nghttp2_frame *frame,
                                            const uint8_t *name, size_t namelen,
                                            const uint8_t *value,
                                            size_t valuelen, uint8_t flags,
                                            void *user_data) {
  on_invalid_header_callback(session, frame, name, namelen, value, valuelen,
                             flags, user_data);
  return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}