h2_rx_headers(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
{
	struct req *req;
	h2_error h2e;
	const uint8_t *p;
	size_t l;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	ASSERT_RXTHR(h2);

	if (r2 == NULL) {
		if (h2->rxf_stream <= h2->highest_stream)
			return (H2CE_PROTOCOL_ERROR);	// rfc7540,l,1153,1158
		/* NB: we don't need to guard the read of h2->open_streams
		 * because headers are handled sequentially so it cannot
		 * increase under our feet.
		 */
		if (h2->open_streams >=
		    h2->local_settings.max_concurrent_streams) {
			VSLb(h2->vsl, SLT_Debug,
			     "H2: stream %u: Hit maximum number of "
			     "concurrent streams", h2->rxf_stream);
			return (H2SE_REFUSED_STREAM);	// rfc7540,l,1200,1205
		}
		h2->highest_stream = h2->rxf_stream;
		r2 = h2_new_req(wrk, h2, h2->rxf_stream, NULL);
	}
	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);

	if (r2->state != H2_S_IDLE)
		return (H2CE_PROTOCOL_ERROR);	// XXX spec ?
	r2->state = H2_S_OPEN;

	req = r2->req;
	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);

	req->vsl->wid = VXID_Get(wrk, VSL_CLIENTMARKER);
	VSLb(req->vsl, SLT_Begin, "req %u rxreq", VXID(req->sp->vxid));
	VSL(SLT_Link, req->sp->vxid, "req %u rxreq", VXID(req->vsl->wid));

	h2->new_req = req;
	req->sp = h2->sess;
	req->transport = &H2_transport;

	req->t_first = VTIM_real();
	req->t_req = VTIM_real();
	req->t_prev = req->t_first;
	VSLb_ts_req(req, "Start", req->t_first);
	req->acct.req_hdrbytes += h2->rxf_len;

	HTTP_Setup(req->http, req->ws, req->vsl, SLT_ReqMethod);
	http_SetH(req->http, HTTP_HDR_PROTO, "HTTP/2.0");

	h2h_decode_init(h2);

	p = h2->rxf_data;
	l = h2->rxf_len;
	if (h2->rxf_flags & H2FF_HEADERS_PADDED) {
		if (*p + 1 > l)
			return (H2CE_PROTOCOL_ERROR);	// rfc7540,l,1884,1887
		l -= 1 + *p;
		p += 1;
	}
	if (h2->rxf_flags & H2FF_HEADERS_PRIORITY) {
		if (l < 5)
			return (H2CE_PROTOCOL_ERROR);
		l -= 5;
		p += 5;
	}
	h2e = h2h_decode_bytes(h2, p, l);
	if (h2e != NULL) {
		Lck_Lock(&h2->sess->mtx);
		VSLb(h2->vsl, SLT_Debug, "HPACK(hdr) %s", h2e->name);
		Lck_Unlock(&h2->sess->mtx);
		(void)h2h_decode_fini(h2);
		assert(!WS_IsReserved(r2->req->ws));
		h2_del_req(wrk, r2);
		return (h2e);
	}

	if (h2->rxf_flags & H2FF_HEADERS_END_STREAM)
		req->req_body_status = BS_NONE;

	if (h2->rxf_flags & H2FF_HEADERS_END_HEADERS)
		return (h2_end_headers(wrk, h2, req, r2));
	return (0);
}
h2_rx_headers(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
{
	struct req *req;
	h2_error h2e;
	const uint8_t *p;
	size_t l;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	ASSERT_RXTHR(h2);

	if (r2 == NULL) {
		if (h2->rxf_stream <= h2->highest_stream)
			return (H2CE_PROTOCOL_ERROR);	 
		 
		if (h2->open_streams >=
		    h2->local_settings.max_concurrent_streams) {
			VSLb(h2->vsl, SLT_Debug,
			     "H2: stream %u: Hit maximum number of "
			     "concurrent streams", h2->rxf_stream);
			return (H2SE_REFUSED_STREAM);	 
		}
		h2->highest_stream = h2->rxf_stream;
		r2 = h2_new_req(wrk, h2, h2->rxf_stream, NULL);
	}
	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);

	if (r2->state != H2_S_IDLE)
		return (H2CE_PROTOCOL_ERROR);	 
	r2->state = H2_S_OPEN;

	req = r2->req;
	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);

	req->vsl->wid = VXID_Get(wrk, VSL_CLIENTMARKER);
	VSLb(req->vsl, SLT_Begin, "req %u rxreq", VXID(req->sp->vxid));
	VSL(SLT_Link, req->sp->vxid, "req %u rxreq", VXID(req->vsl->wid));

	h2->new_req = req;
	req->sp = h2->sess;
	req->transport = &H2_transport;

	req->t_first = VTIM_real();
	req->t_req = VTIM_real();
	req->t_prev = req->t_first;
	VSLb_ts_req(req, "Start", req->t_first);
	req->acct.req_hdrbytes += h2->rxf_len;

	HTTP_Setup(req->http, req->ws, req->vsl, SLT_ReqMethod);
	http_SetH(req->http, HTTP_HDR_PROTO, "HTTP/2.0");

	h2h_decode_init(h2);

	p = h2->rxf_data;
	l = h2->rxf_len;
	if (h2->rxf_flags & H2FF_HEADERS_PADDED) {
		if (*p + 1 > l)
			return (H2CE_PROTOCOL_ERROR);	 
		l -= 1 + *p;
		p += 1;
	}
	if (h2->rxf_flags & H2FF_HEADERS_PRIORITY) {
		if (l < 5)
			return (H2CE_PROTOCOL_ERROR);
		l -= 5;
		p += 5;
	}
	h2e = h2h_decode_bytes(h2, p, l);
	if (h2e != NULL) {
		Lck_Lock(&h2->sess->mtx);
		VSLb(h2->vsl, SLT_Debug, "HPACK(hdr) %s", h2e->name);
		Lck_Unlock(&h2->sess->mtx);
		(void)h2h_decode_fini(h2);
		assert(!WS_IsReserved(r2->req->ws));
		h2_del_req(wrk, r2);
		return (h2e);
	}

	if (h2->rxf_flags & H2FF_HEADERS_END_STREAM)
		req->req_body_status = BS_NONE;

	if (h2->rxf_flags & H2FF_HEADERS_END_HEADERS)
		return (h2_end_headers(wrk, h2, req, r2));
	return (0);
}