static void execlists_cancel_requests(struct intel_engine_cs *engine)
{
	struct intel_engine_execlists * const execlists = &engine->execlists;
	struct i915_request *rq, *rn;
	struct rb_node *rb;
	unsigned long flags;

	GEM_TRACE("%s\n", engine->name);

	/*
	 * Before we call engine->cancel_requests(), we should have exclusive
	 * access to the submission state. This is arranged for us by the
	 * caller disabling the interrupt generation, the tasklet and other
	 * threads that may then access the same state, giving us a free hand
	 * to reset state. However, we still need to let lockdep be aware that
	 * we know this state may be accessed in hardirq context, so we
	 * disable the irq around this manipulation and we want to keep
	 * the spinlock focused on its duties and not accidentally conflate
	 * coverage to the submission's irq state. (Similarly, although we
	 * shouldn't need to disable irq around the manipulation of the
	 * submission's irq state, we also wish to remind ourselves that
	 * it is irq state.)
	 */
	spin_lock_irqsave(&engine->active.lock, flags);

	__execlists_reset(engine, true);

	/* Mark all executing requests as skipped. */
	list_for_each_entry(rq, &engine->active.requests, sched.link)
		mark_eio(rq);

	/* Flush the queued requests to the timeline list (for retiring). */
	while ((rb = rb_first_cached(&execlists->queue))) {
		struct i915_priolist *p = to_priolist(rb);
		int i;

		priolist_for_each_request_consume(rq, rn, p, i) {
			mark_eio(rq);
			__i915_request_submit(rq);
		}

		rb_erase_cached(&p->node, &execlists->queue);
		i915_priolist_free(p);
	}

	/* Cancel all attached virtual engines */
	while ((rb = rb_first_cached(&execlists->virtual))) {
		struct virtual_engine *ve =
			rb_entry(rb, typeof(*ve), nodes[engine->id].rb);

		rb_erase_cached(rb, &execlists->virtual);
		RB_CLEAR_NODE(rb);

		spin_lock(&ve->base.active.lock);
		rq = fetch_and_zero(&ve->request);
		if (rq) {
			mark_eio(rq);

			rq->engine = engine;
			__i915_request_submit(rq);
			i915_request_put(rq);

			ve->base.execlists.queue_priority_hint = INT_MIN;
		}
		spin_unlock(&ve->base.active.lock);
	}

	/* Remaining _unready_ requests will be nop'ed when submitted */

	execlists->queue_priority_hint = INT_MIN;
	execlists->queue = RB_ROOT_CACHED;

	GEM_BUG_ON(__tasklet_is_enabled(&execlists->tasklet));
	execlists->tasklet.func = nop_submission_tasklet;

	spin_unlock_irqrestore(&engine->active.lock, flags);
}


static void __execlists_submission_tasklet(struct intel_engine_cs *const engine)
{
	lockdep_assert_held(&engine->active.lock);
	if (!engine->execlists.pending[0]) {
		rcu_read_lock(); /* protect peeking at execlists->active */
		execlists_dequeue(engine);
		rcu_read_unlock();
	}
}

drm/i915/gen9: Clear residual context state on context switch

Intel ID: PSIRT-TA-201910-001
CVEID: CVE-2019-14615

Intel GPU Hardware prior to Gen11 does not clear EU state
during a context switch. This can result in information
leakage between contexts.

For Gen8 and Gen9, hardware provides a mechanism for
fast cleardown of the EU state, by issuing a PIPE_CONTROL
with bit 27 set. We can use this in a context batch buffer
to explicitly cleardown the state on every context switch.

As this workaround is already in place for gen8, we can borrow
the code verbatim for Gen9.

Signed-off-by: Mika Kuoppala <mika.kuoppala@linux.intel.com>
Signed-off-by: Akeem G Abodunrin <akeem.g.abodunrin@intel.com>
Cc: Kumar Valsan Prathap <prathap.kumar.valsan@intel.com>
Cc: Chris Wilson <chris.p.wilson@intel.com>
Cc: Balestrieri Francesco <francesco.balestrieri@intel.com>
Cc: Bloomfield Jon <jon.bloomfield@intel.com>
Cc: Dutt Sudeep <sudeep.dutt@intel.com>

struct virtual_engine {
	struct intel_engine_cs base;
	struct intel_context context;

	/*
	 * We allow only a single request through the virtual engine at a time
	 * (each request in the timeline waits for the completion fence of
	 * the previous before being submitted). By restricting ourselves to
	 * only submitting a single request, each request is placed on to a
	 * physical to maximise load spreading (by virtue of the late greedy
	 * scheduling -- each real engine takes the next available request
	 * upon idling).
	 */
	struct i915_request *request;

	/*
	 * We keep a rbtree of available virtual engines inside each physical
	 * engine, sorted by priority. Here we preallocate the nodes we need
	 * for the virtual engine, indexed by physical_engine->id.
	 */
	struct ve_node {
		struct rb_node rb;
		int prio;
	} nodes[I915_NUM_ENGINES];

	/*
	 * Keep track of bonded pairs -- restrictions upon on our selection
	 * of physical engines any particular request may be submitted to.
	 * If we receive a submit-fence from a master engine, we will only
	 * use one of sibling_mask physical engines.
	 */
	struct ve_bond {
		const struct intel_engine_cs *master;
		intel_engine_mask_t sibling_mask;
	} *bonds;
	unsigned int num_bonds;

	/* And finally, which physical engines this virtual engine maps onto. */
	unsigned int num_siblings;
	struct intel_engine_cs *siblings[0];
}

static int __execlists_context_alloc(struct intel_context *ce,
				     struct intel_engine_cs *engine);

static void execlists_init_reg_state(u32 *reg_state,
				     const struct intel_context *ce,
				     const struct intel_engine_cs *engine,
				     const struct intel_ring *ring,
				     bool close);

static void
__execlists_update_reg_state(const struct intel_context *ce,
			     const struct intel_engine_cs *engine);

static u32 *set_offsets(u32 *regs,
			const u8 *data,
			const struct intel_engine_cs *engine)

static const u8 gen8_xcs_offsets[] = {
	NOP(1),
	LRI(11, 0),
	REG16(0x244),
	REG(0x034),
	REG(0x030),
	REG(0x038),
	REG(0x03c),
	REG(0x168),
	REG(0x140),
	REG(0x110),
	REG(0x11c),
	REG(0x114),
	REG(0x118),

	NOP(9),
	LRI(9, 0),
	REG16(0x3a8),
	REG16(0x28c),
	REG16(0x288),
	REG16(0x284),
	REG16(0x280),
	REG16(0x27c),
	REG16(0x278),
	REG16(0x274),
	REG16(0x270),

	NOP(13),
	LRI(2, 0),
	REG16(0x200),
	REG(0x028),

	END(),
};


static bool ctx_single_port_submission(const struct intel_context *ce)
{
	return (IS_ENABLED(CONFIG_DRM_I915_GVT) &&
		i915_gem_context_force_single_submission(ce->gem_context));
}


static int intel_init_workaround_bb(struct intel_engine_cs *engine)
{
	struct i915_ctx_workarounds *wa_ctx = &engine->wa_ctx;
	struct i915_wa_ctx_bb *wa_bb[2] = { &wa_ctx->indirect_ctx,
					    &wa_ctx->per_ctx };
	wa_bb_func_t wa_bb_fn[2];
	struct page *page;
	void *batch, *batch_ptr;
	unsigned int i;
	int ret;

	if (engine->class != RENDER_CLASS)
		return 0;

	switch (INTEL_GEN(engine->i915)) {
	case 12:
	case 11:
		return 0;
	case 10:
		wa_bb_fn[0] = gen10_init_indirectctx_bb;
		wa_bb_fn[1] = NULL;
		break;
	case 9:
		wa_bb_fn[0] = gen9_init_indirectctx_bb;
		wa_bb_fn[1] = NULL;
		break;
	case 8:
		wa_bb_fn[0] = gen8_init_indirectctx_bb;
		wa_bb_fn[1] = NULL;
		break;
	default:
		MISSING_CASE(INTEL_GEN(engine->i915));
		return 0;
	}

	ret = lrc_setup_wa_ctx(engine);
	if (ret) {
		DRM_DEBUG_DRIVER("Failed to setup context WA page: %d\n", ret);
		return ret;
	}

	page = i915_gem_object_get_dirty_page(wa_ctx->vma->obj, 0);
	batch = batch_ptr = kmap_atomic(page);

	/*
	 * Emit the two workaround batch buffers, recording the offset from the
	 * start of the workaround batch buffer object for each and their
	 * respective sizes.
	 */
	for (i = 0; i < ARRAY_SIZE(wa_bb_fn); i++) {
		wa_bb[i]->offset = batch_ptr - batch;
		if (GEM_DEBUG_WARN_ON(!IS_ALIGNED(wa_bb[i]->offset,
						  CACHELINE_BYTES))) {
			ret = -EINVAL;
			break;
		}
		if (wa_bb_fn[i])
			batch_ptr = wa_bb_fn[i](engine, batch_ptr);
		wa_bb[i]->size = batch_ptr - (batch + wa_bb[i]->offset);
	}

	BUG_ON(batch_ptr - batch > CTX_WA_BB_OBJ_SIZE);

	kunmap_atomic(batch);
	if (ret)
		lrc_destroy_wa_ctx(engine);

	return ret;
}


static int __execlists_context_alloc(struct intel_context *ce,
				     struct intel_engine_cs *engine)
{
	struct drm_i915_gem_object *ctx_obj;
	struct intel_ring *ring;
	struct i915_vma *vma;
	u32 context_size;
	int ret;

	GEM_BUG_ON(ce->state);
	context_size = round_up(engine->context_size, I915_GTT_PAGE_SIZE);

	if (IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM))
		context_size += I915_GTT_PAGE_SIZE; /* for redzone */

	ctx_obj = i915_gem_object_create_shmem(engine->i915, context_size);
	if (IS_ERR(ctx_obj))
		return PTR_ERR(ctx_obj);

	vma = i915_vma_instance(ctx_obj, &engine->gt->ggtt->vm, NULL);
	if (IS_ERR(vma)) {
		ret = PTR_ERR(vma);
		goto error_deref_obj;
	}

	if (!ce->timeline) {
		struct intel_timeline *tl;

		tl = intel_timeline_create(engine->gt, NULL);
		if (IS_ERR(tl)) {
			ret = PTR_ERR(tl);
			goto error_deref_obj;
		}

		ce->timeline = tl;
	}

	ring = intel_engine_create_ring(engine, (unsigned long)ce->ring);
	if (IS_ERR(ring)) {
		ret = PTR_ERR(ring);
		goto error_deref_obj;
	}

	ret = populate_lr_context(ce, ctx_obj, engine, ring);
	if (ret) {
		DRM_DEBUG_DRIVER("Failed to populate LRC: %d\n", ret);
		goto error_ring_free;
	}

	ce->ring = ring;
	ce->state = vma;

	return 0;

error_ring_free:
	intel_ring_put(ring);
error_deref_obj:
	i915_gem_object_put(ctx_obj);
	return ret;
}

#define WA_TAIL_BYTES (sizeof(u32) * WA_TAIL_DWORDS)

struct virtual_engine {
	struct intel_engine_cs base;
	struct intel_context context;

	/*
	 * We allow only a single request through the virtual engine at a time
	 * (each request in the timeline waits for the completion fence of
	 * the previous before being submitted). By restricting ourselves to
	 * only submitting a single request, each request is placed on to a
	 * physical to maximise load spreading (by virtue of the late greedy
	 * scheduling -- each real engine takes the next available request
	 * upon idling).
	 */
	struct i915_request *request;

	/*
	 * We keep a rbtree of available virtual engines inside each physical
	 * engine, sorted by priority. Here we preallocate the nodes we need
	 * for the virtual engine, indexed by physical_engine->id.
	 */
	struct ve_node {
		struct rb_node rb;
		int prio;
	} nodes[I915_NUM_ENGINES];

	/*
	 * Keep track of bonded pairs -- restrictions upon on our selection
	 * of physical engines any particular request may be submitted to.
	 * If we receive a submit-fence from a master engine, we will only
	 * use one of sibling_mask physical engines.
	 */
	struct ve_bond {
		const struct intel_engine_cs *master;
		intel_engine_mask_t sibling_mask;
	} *bonds;
	unsigned int num_bonds;

	/* And finally, which physical engines this virtual engine maps onto. */
	unsigned int num_siblings;
	struct intel_engine_cs *siblings[0];
}

static int __execlists_context_alloc(struct intel_context *ce,
				     struct intel_engine_cs *engine);

static void execlists_init_reg_state(u32 *reg_state,
				     const struct intel_context *ce,
				     const struct intel_engine_cs *engine,
				     const struct intel_ring *ring,
				     bool close);

static void
__execlists_update_reg_state(const struct intel_context *ce,
			     const struct intel_engine_cs *engine);

static u32 *set_offsets(u32 *regs,
			const u8 *data,
			const struct intel_engine_cs *engine)

static const u8 gen8_xcs_offsets[] = {
	NOP(1),
	LRI(11, 0),
	REG16(0x244),
	REG(0x034),
	REG(0x030),
	REG(0x038),
	REG(0x03c),
	REG(0x168),
	REG(0x140),
	REG(0x110),
	REG(0x11c),
	REG(0x114),
	REG(0x118),

	NOP(9),
	LRI(9, 0),
	REG16(0x3a8),
	REG16(0x28c),
	REG16(0x288),
	REG16(0x284),
	REG16(0x280),
	REG16(0x27c),
	REG16(0x278),
	REG16(0x274),
	REG16(0x270),

	NOP(13),
	LRI(2, 0),
	REG16(0x200),
	REG(0x028),

	END(),
};


static void nop_submission_tasklet(unsigned long data)
{
	/* The driver is wedged; don't process any more events. */
}


static int effective_prio(const struct i915_request *rq)
{
	int prio = rq_prio(rq);

	/*
	 * If this request is special and must not be interrupted at any
	 * cost, so be it. Note we are only checking the most recent request
	 * in the context and so may be masking an earlier vip request. It
	 * is hoped that under the conditions where nopreempt is used, this
	 * will not matter (i.e. all requests to that context will be
	 * nopreempt for as long as desired).
	 */
	if (i915_request_has_nopreempt(rq))
		prio = I915_PRIORITY_UNPREEMPTABLE;

	/*
	 * On unwinding the active request, we give it a priority bump
	 * if it has completed waiting on any semaphore. If we know that
	 * the request has already started, we can prevent an unwanted
	 * preempt-to-idle cycle by taking that into account now.
	 */
	if (__i915_request_has_started(rq))
		prio |= I915_PRIORITY_NOSEMAPHORE;

	/* Restrict mere WAIT boosts from triggering preemption */
	BUILD_BUG_ON(__NO_PREEMPTION & ~I915_PRIORITY_MASK); /* only internal */
	return prio | __NO_PREEMPTION;
}


check_redzone(const void *vaddr, const struct intel_engine_cs *engine)
{
	if (!IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM))
		return;

	vaddr += engine->context_size;

	if (memchr_inv(vaddr, POISON_INUSE, I915_GTT_PAGE_SIZE))
		dev_err_once(engine->i915->drm.dev,
			     "%s context redzone overwritten!\n",
			     engine->name);
}


execlists_schedule_out(struct i915_request *rq)
{
	struct intel_context * const ce = rq->hw_context;
	struct intel_engine_cs *cur, *old;

	trace_i915_request_out(rq);

	old = READ_ONCE(ce->inflight);
	do
		cur = ptr_unmask_bits(old, 2) ? ptr_dec(old) : NULL;
	while (!try_cmpxchg(&ce->inflight, &old, cur));
	if (!cur)
		__execlists_schedule_out(rq, old);

	i915_request_put(rq);
}


int unzzip_print (int argc, char ** argv)
{
    return unzzip_cat(argc, argv, 0);
}

#68 ssize_t return value of zzip_file_read is a signed value being possibly -1

/* No explicit global definitions found. */


static int unzzip_cat (int argc, char ** argv, int extract)
{
    int done = 0;
    int argn;
    ZZIP_DIR* disk;
    zzip_error_t error;

    if (argc == 1)
    {
        printf (__FILE__ " version " ZZIP_PACKAGE_NAME " " ZZIP_PACKAGE_VERSION "\n");
        return EXIT_OK; /* better provide an archive argument */
    }

    disk = zzip_dir_open (argv[1], &error);
    if (! disk) {
	fprintf(stderr, "%s: %s\n", argv[1], zzip_strerror(error));
	return exitcode(error);
    }

    if (argc == 2)
    {  /* list all */
	ZZIP_DIRENT entry;
	while(zzip_dir_read(disk, &entry))
	{
	    char* name = entry.d_name;
	    FILE* out = stdout;
	    if (extract) out = create_fopen(name, "wb", 1);
	    if (! out) {
		DBG3("fopen' %s : %s", name, strerror(errno));
	        if (errno != EISDIR) done = EXIT_ERRORS;
	        continue;
	    }
	    unzzip_cat_file (disk, name, out);
	    if (extract) fclose(out);
	}
    }
    else
    {   /* list only the matching entries - in order of zip directory */
	ZZIP_DIRENT entry;
	while(zzip_dir_read(disk, &entry))
	{
	    char* name = entry.d_name;
	    for (argn=1; argn < argc; argn++)
	    {
		if (! _zzip_fnmatch (argv[argn], name,
		    _zzip_FNM_NOESCAPE|_zzip_FNM_PATHNAME|_zzip_FNM_PERIOD))
	        {
	            FILE* out = stdout;
	            if (extract) out = create_fopen(name, "wb", 1);
		    if (! out) {
			DBG3("fopen. %s : %s", name, strerror(errno));
		        if (errno != EISDIR) done = EXIT_ERRORS;
		        continue;
		    }
	            unzzip_cat_file (disk, name, out);
	            if (extract) fclose(out);
		    break; /* match loop */
	        }
	    }
	}
    }
    zzip_dir_close(disk);
    return done;
}

extern int exitcode(int);

extern FILE* create_fopen(char*, char*, int);


static void unzzip_cat_file(ZZIP_DIR* disk, char* name, FILE* out)
{
    ZZIP_FILE* file = zzip_file_open (disk, name, 0);
    if (file)
    {
	char buffer[1024]; int len;
	while (0 < (len = zzip_file_read (file, buffer, 1024)))
	{
	    fwrite (buffer, 1, len, out);
	}

	zzip_file_close (file);
    }
}


int unzzip_extract (int argc, char ** argv)
{
    return unzzip_cat(argc, argv, 1);
}


static int dissect_dvb_s2_modeadapt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
    int         cur_off = 0, modeadapt_len, modeadapt_type, matched_headers = 0;

    proto_item *ti, *tf;
    proto_tree *dvb_s2_modeadapt_tree;
    proto_tree *dvb_s2_modeadapt_acm_tree;

    unsigned int modcod, mc;
    static int * const modeadapt_acm_bitfields[] = {
        &hf_dvb_s2_modeadapt_acm_fecframe,
        &hf_dvb_s2_modeadapt_acm_pilot,
        &hf_dvb_s2_modeadapt_acm_modcod,
        NULL
    };

    /* Check that there's enough data */
    if (tvb_captured_length(tvb) < DVB_S2_MODEADAPT_MINSIZE)
        return 0;

    /* There are four different mode adaptation formats, with different
       length headers. Two of them have a sync byte at the beginning, but
       the other two do not. In every case, the mode adaptation header is
       followed by the baseband header, which is protected by a CRC-8.
       The CRC-8 is weak protection, so it can match by accident, leading
       to an ambiguity in identifying which format is in use. We will
       check for ambiguity and report it. */
    /* Try L.1 format: no header. */
    if (test_dvb_s2_crc(tvb, DVB_S2_MODEADAPT_L1SIZE)) {
        matched_headers++;
        modeadapt_type = DVB_S2_MODEADAPT_TYPE_L1;
        modeadapt_len = DVB_S2_MODEADAPT_L1SIZE;
    }

    /* Try L.2 format: header includes sync byte */
    if ((tvb_get_guint8(tvb, DVB_S2_MODEADAPT_OFFS_SYNCBYTE) == DVB_S2_MODEADAPT_SYNCBYTE) &&
        test_dvb_s2_crc(tvb, DVB_S2_MODEADAPT_L2SIZE)) {
        matched_headers++;
        modeadapt_type = DVB_S2_MODEADAPT_TYPE_L2;
        modeadapt_len = DVB_S2_MODEADAPT_L2SIZE;
    }

    /* Try L.3 format: header includes sync byte */
    if ((tvb_get_guint8(tvb, DVB_S2_MODEADAPT_OFFS_SYNCBYTE) == DVB_S2_MODEADAPT_SYNCBYTE) &&
        test_dvb_s2_crc(tvb, DVB_S2_MODEADAPT_L3SIZE)) {
        matched_headers++;
        modeadapt_type = DVB_S2_MODEADAPT_TYPE_L3;
        modeadapt_len = DVB_S2_MODEADAPT_L3SIZE;
    }

    /* Try L.4 format: header does not include sync byte */
    if (test_dvb_s2_crc(tvb, DVB_S2_MODEADAPT_L4SIZE)) {
        matched_headers++;
        modeadapt_type = DVB_S2_MODEADAPT_TYPE_L4;
        modeadapt_len = DVB_S2_MODEADAPT_L4SIZE;
    }

    if (matched_headers == 0) {
        /* This does not look like a DVB-S2-BB frame at all. We are a
           heuristic dissector, so we should just punt and let another
           dissector have a try at this one. */
        return 0;
    }

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "DVB-S2 ");
    col_set_str(pinfo->cinfo, COL_INFO,     "DVB-S2 ");

    /* If there's a mode adaptation header, create display subtree for it */
    if (modeadapt_len > 0) {
        /* ti = proto_tree_add_item(tree, proto_dvb_s2_modeadapt, tvb, 0, modeadapt_len, ENC_NA); */
        ti = proto_tree_add_protocol_format(tree, proto_dvb_s2_modeadapt, tvb, 0, modeadapt_len,
            "DVB-S2 Mode Adaptation Header L.%d", modeadapt_type);
        dvb_s2_modeadapt_tree = proto_item_add_subtree(ti, ett_dvb_s2_modeadapt);

        if (matched_headers > 1) {
            expert_add_info_format(pinfo, ti, &ei_dvb_s2_bb_header_ambiguous,
                "Mode adaptation header format is ambiguous. Assuming L.%d", modeadapt_type);
        }

        /* SYNC byte if used in this header format; value has already been checked */
        if (modeadapt_type == DVB_S2_MODEADAPT_TYPE_L2 ||
            modeadapt_type == DVB_S2_MODEADAPT_TYPE_L3) {
            proto_tree_add_item(dvb_s2_modeadapt_tree, hf_dvb_s2_modeadapt_sync, tvb, cur_off, 1, ENC_BIG_ENDIAN);
            cur_off++;
        }

        /* ACM byte and subfields if used in this header format */
        if (modeadapt_type == DVB_S2_MODEADAPT_TYPE_L2 ||
            modeadapt_type == DVB_S2_MODEADAPT_TYPE_L3 ||
            modeadapt_type == DVB_S2_MODEADAPT_TYPE_L4) {
            mc = tvb_get_guint8(tvb, 1);
            //mc = tvb_get_letohs(tvb, 0);
            if (mc & 0x80) {
                modcod = 0x80;
                modcod |= ((mc & 0x1F) << 2);
                modcod |= ((mc & 0x40) >> 5);
                tf = proto_tree_add_item(dvb_s2_modeadapt_tree, hf_dvb_s2_modeadapt_acm, tvb,
                        DVB_S2_MODEADAPT_OFFS_ACMBYTE, 1, ENC_BIG_ENDIAN);

                dvb_s2_modeadapt_acm_tree = proto_item_add_subtree(tf, ett_dvb_s2_modeadapt_acm);

                proto_tree_add_item(dvb_s2_modeadapt_acm_tree, hf_dvb_s2_modeadapt_acm_pilot, tvb,
                        DVB_S2_MODEADAPT_OFFS_ACMBYTE, 1, ENC_BIG_ENDIAN);
                proto_tree_add_uint_format_value(dvb_s2_modeadapt_acm_tree, hf_dvb_s2_modeadapt_acm_modcod_s2x, tvb,
                        DVB_S2_MODEADAPT_OFFS_ACMBYTE, 1, mc, "DVBS2X %s(%d)", modeadapt_modcods[modcod].strptr, modcod);
            } else {
                proto_tree_add_bitmask_with_flags(dvb_s2_modeadapt_tree, tvb, DVB_S2_MODEADAPT_OFFS_ACMBYTE, hf_dvb_s2_modeadapt_acm,
                        ett_dvb_s2_modeadapt_acm, modeadapt_acm_bitfields, ENC_BIG_ENDIAN, BMT_NO_FLAGS);
            }
            cur_off++;
        }

        /* CNI and Frame No if used in this header format */
        if (modeadapt_type == DVB_S2_MODEADAPT_TYPE_L3 ||
            modeadapt_type == DVB_S2_MODEADAPT_TYPE_L4) {
            proto_tree_add_item(dvb_s2_modeadapt_tree, hf_dvb_s2_modeadapt_cni, tvb, cur_off, 1, ENC_BIG_ENDIAN);
            cur_off++;

            proto_tree_add_item(dvb_s2_modeadapt_tree, hf_dvb_s2_modeadapt_frameno, tvb, cur_off, 1, ENC_BIG_ENDIAN);
            cur_off++;
        }
    }

    /* start DVB-BB dissector */
    cur_off += dissect_dvb_s2_bb(tvb_new_subset_remaining(tvb, cur_off), pinfo, tree, NULL);

    return cur_off;
}

DVB-S2-BB: Prevent infinite loop

Commit 4bf4ee88f0544727e7f89f3f288c6afd2f650a4c removed an else
statement that broke out of the BBFrame processing loop. Without
it, infinite loops might be possible if the GSE frames have bit errors
in the length field.

#define DVB_S2_MODEADAPT_MINSIZE        (DVB_S2_BB_OFFS_CRC + 1)

#define DVB_S2_MODEADAPT_TYPE_L1        1

#define DVB_S2_MODEADAPT_TYPE_L2        2

#define DVB_S2_MODEADAPT_TYPE_L3        3

#define DVB_S2_MODEADAPT_TYPE_L4        4

#define DVB_S2_MODEADAPT_L1SIZE         0

#define DVB_S2_MODEADAPT_L2SIZE         2

#define DVB_S2_MODEADAPT_L3SIZE         4

#define DVB_S2_MODEADAPT_L4SIZE         3


static gboolean test_dvb_s2_crc(tvbuff_t *tvb, guint offset) {

    guint8 input8;

    /* only check BB Header and return */
    if (tvb_captured_length(tvb) < (offset + DVB_S2_BB_HEADER_LEN))
        return FALSE;

    input8 = tvb_get_guint8(tvb, offset + DVB_S2_BB_OFFS_CRC);

    if (compute_crc8(tvb, DVB_S2_BB_HEADER_LEN - 1, offset) != input8)
        return FALSE;
    else
        return TRUE;
}

#define DVB_S2_MODEADAPT_MINSIZE        (DVB_S2_BB_OFFS_CRC + 1)


static int dissect_dvb_s2_bb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
    proto_item *ti;
    proto_tree *dvb_s2_bb_tree;

    guint8      input8, matype1;
    guint8      sync_flag = 0;
    guint16     input16, bb_data_len = 0, user_packet_length;

    int         sub_dissected        = 0, flag_is_ms = 0, new_off = 0;

    static int * const bb_header_bitfields[] = {
        &hf_dvb_s2_bb_matype1_gs,
        &hf_dvb_s2_bb_matype1_mis,
        &hf_dvb_s2_bb_matype1_acm,
        &hf_dvb_s2_bb_matype1_issyi,
        &hf_dvb_s2_bb_matype1_npd,
        &hf_dvb_s2_bb_matype1_low_ro,
        NULL
    };

    col_append_str(pinfo->cinfo, COL_PROTOCOL, "BB ");
    col_append_str(pinfo->cinfo, COL_INFO, "Baseband ");

    /* create display subtree for the protocol */
    ti = proto_tree_add_item(tree, proto_dvb_s2_bb, tvb, 0, DVB_S2_BB_HEADER_LEN, ENC_NA);
    dvb_s2_bb_tree = proto_item_add_subtree(ti, ett_dvb_s2_bb);

    matype1 = tvb_get_guint8(tvb, DVB_S2_BB_OFFS_MATYPE1);
    new_off += 1;

    if (BIT_IS_CLEAR(matype1, DVB_S2_BB_MIS_POS))
        flag_is_ms = 1;

    proto_tree_add_bitmask_with_flags(dvb_s2_bb_tree, tvb, DVB_S2_BB_OFFS_MATYPE1, hf_dvb_s2_bb_matype1,
        ett_dvb_s2_bb_matype1, bb_header_bitfields, ENC_BIG_ENDIAN, BMT_NO_FLAGS);

    input8 = tvb_get_guint8(tvb, DVB_S2_BB_OFFS_MATYPE1);

    if ((pinfo->fd->num == 1) && (_use_low_rolloff_value != 0)) {
        _use_low_rolloff_value = 0;
    }
    if (((input8 & 0x03) == 3) && !_use_low_rolloff_value) {
      _use_low_rolloff_value = 1;
    }
    if (_use_low_rolloff_value) {
       proto_tree_add_item(dvb_s2_bb_tree, hf_dvb_s2_bb_matype1_low_ro, tvb,
                           DVB_S2_BB_OFFS_MATYPE1, 1, ENC_BIG_ENDIAN);
    } else {
       proto_tree_add_item(dvb_s2_bb_tree, hf_dvb_s2_bb_matype1_high_ro, tvb,
                           DVB_S2_BB_OFFS_MATYPE1, 1, ENC_BIG_ENDIAN);
    }

    input8 = tvb_get_guint8(tvb, DVB_S2_BB_OFFS_MATYPE2);
    new_off += 1;
    if (flag_is_ms) {
        proto_tree_add_uint_format_value(dvb_s2_bb_tree, hf_dvb_s2_bb_matype2, tvb,
                                   DVB_S2_BB_OFFS_MATYPE2, 1, input8, "Input Stream Identifier (ISI): %d",
                                   input8);
    } else {
        proto_tree_add_uint_format_value(dvb_s2_bb_tree, hf_dvb_s2_bb_matype2, tvb,
                                   DVB_S2_BB_OFFS_MATYPE2, 1, input8, "reserved");
    }

    user_packet_length = input16 = tvb_get_ntohs(tvb, DVB_S2_BB_OFFS_UPL);
    new_off += 2;

    proto_tree_add_uint_format(dvb_s2_bb_tree, hf_dvb_s2_bb_upl, tvb,
                               DVB_S2_BB_OFFS_UPL, 2, input16, "User Packet Length: %d bits (%d bytes)",
                               (guint16) input16, (guint16) input16 / 8);

    bb_data_len = input16 = tvb_get_ntohs(tvb, DVB_S2_BB_OFFS_DFL);
    bb_data_len /= 8;
    new_off += 2;

    proto_tree_add_uint_format_value(dvb_s2_bb_tree, hf_dvb_s2_bb_dfl, tvb,
                               DVB_S2_BB_OFFS_DFL, 2, input16, "%d bits (%d bytes)", input16, input16 / 8);

    new_off += 1;
    sync_flag = tvb_get_guint8(tvb, DVB_S2_BB_OFFS_SYNC);
    proto_tree_add_item(dvb_s2_bb_tree, hf_dvb_s2_bb_sync, tvb, DVB_S2_BB_OFFS_SYNC, 1, ENC_BIG_ENDIAN);

    new_off += 2;
    proto_tree_add_item(dvb_s2_bb_tree, hf_dvb_s2_bb_syncd, tvb, DVB_S2_BB_OFFS_SYNCD, 2, ENC_BIG_ENDIAN);

    new_off += 1;
    proto_tree_add_checksum(dvb_s2_bb_tree, tvb, DVB_S2_BB_OFFS_CRC, hf_dvb_s2_bb_crc, hf_dvb_s2_bb_crc_status, &ei_dvb_s2_bb_crc, pinfo,
        compute_crc8(tvb, DVB_S2_BB_HEADER_LEN - 1, 0), ENC_NA, PROTO_CHECKSUM_VERIFY);

    switch (matype1 & DVB_S2_BB_TSGS_MASK) {
    case DVB_S2_BB_TSGS_GENERIC_CONTINUOUS:
        /* Check GSE constraints on the BB header per 9.2.1 of ETSI TS 102 771 */
        if (BIT_IS_SET(matype1, DVB_S2_BB_ISSYI_POS)) {
            expert_add_info(pinfo, ti, &ei_dvb_s2_bb_issy_invalid);
        }
        if (BIT_IS_SET(matype1, DVB_S2_BB_NPD_POS)) {
            expert_add_info(pinfo, ti, &ei_dvb_s2_bb_npd_invalid);
        }
        if (user_packet_length != 0x0000) {
            expert_add_info_format(pinfo, ti, &ei_dvb_s2_bb_upl_invalid,
                "UPL is 0x%04x. It must be 0x0000 for GSE packets.", user_packet_length);
        }

        if (dvb_s2_df_dissection) {
            while (bb_data_len) {
                if (sync_flag == DVB_S2_BB_SYNC_EIP_CRC32 && bb_data_len == DVB_S2_BB_EIP_CRC32_LEN) {
                    proto_tree_add_item(dvb_s2_bb_tree, hf_dvb_s2_bb_eip_crc32, tvb, new_off, bb_data_len, ENC_NA);
                    bb_data_len = 0;
                    new_off += DVB_S2_BB_EIP_CRC32_LEN;
                } else {
                    /* start DVB-GSE dissector */
                    sub_dissected = dissect_dvb_s2_gse(tvb_new_subset_length(tvb, new_off, bb_data_len), pinfo, tree, NULL);
                    new_off += sub_dissected;

                    if ((sub_dissected <= bb_data_len) && (sub_dissected >= DVB_S2_GSE_MINSIZE)) {
                        bb_data_len -= sub_dissected;
                        if (bb_data_len < DVB_S2_GSE_MINSIZE)
                            bb_data_len = 0;
                    } else {
                        bb_data_len = 0;
                    }
                }
            }
        } else {
            proto_tree_add_item(dvb_s2_bb_tree, hf_dvb_s2_bb_df, tvb, new_off, bb_data_len, ENC_NA);
            new_off += bb_data_len;
        }
        break;

    case DVB_S2_BB_TSGS_GENERIC_PACKETIZED:
        proto_tree_add_item(tree, hf_dvb_s2_bb_packetized, tvb, new_off, bb_data_len, ENC_NA);
        new_off += bb_data_len;
        break;

    case DVB_S2_BB_TSGS_TRANSPORT_STREAM:
        proto_tree_add_item(tree, hf_dvb_s2_bb_transport, tvb, new_off, bb_data_len, ENC_NA);
        new_off += bb_data_len;
        break;

    default:
        proto_tree_add_item(tree, hf_dvb_s2_bb_reserved, tvb, new_off, bb_data_len, ENC_NA);
        new_off += bb_data_len;
        expert_add_info(pinfo, ti, &ei_dvb_s2_bb_reserved);
        break;
    }

    return new_off;
}

#define BIT_IS_SET(var, bit) ((var) & (1 << (bit)))

#define BIT_IS_CLEAR(var, bit) !BIT_IS_SET(var, bit)

#define DVB_S2_MODEADAPT_MINSIZE        (DVB_S2_BB_OFFS_CRC + 1)


static guint8 compute_crc8(tvbuff_t *p, guint8 len, guint8 offset)
{
    int    i;
    guint8 crc = 0, tmp;

    for (i = 0; i < len; i++) {
        tmp = tvb_get_guint8(p, offset++);
        crc = crc8_table[crc ^ tmp];
    }
    return crc;
}

static dissector_handle_t ip_handle;

static dissector_handle_t ipv6_handle;

static dissector_handle_t eth_withoutfcs_handle;

static dissector_handle_t dvb_s2_table_handle;

static dissector_handle_t data_handle;

static gboolean dvb_s2_full_dissection = FALSE;

static gboolean dvb_s2_df_dissection = FALSE;

static int proto_dvb_s2_modeadapt = -1;

static int hf_dvb_s2_modeadapt_sync = -1;

static int hf_dvb_s2_modeadapt_acm = -1;

static int hf_dvb_s2_modeadapt_acm_fecframe = -1;

static int hf_dvb_s2_modeadapt_acm_pilot = -1;

static int hf_dvb_s2_modeadapt_acm_modcod = -1;

static int hf_dvb_s2_modeadapt_acm_modcod_s2x = -1;

static int hf_dvb_s2_modeadapt_cni = -1;

static int hf_dvb_s2_modeadapt_frameno = -1;

static int proto_dvb_s2_bb = -1;

static int hf_dvb_s2_bb_matype1 = -1;

static int hf_dvb_s2_bb_matype1_gs = -1;

static int hf_dvb_s2_bb_matype1_mis = -1;

static int hf_dvb_s2_bb_matype1_acm = -1;

static int hf_dvb_s2_bb_matype1_issyi = -1;

static int hf_dvb_s2_bb_matype1_npd = -1;

static int hf_dvb_s2_bb_matype1_high_ro = -1;

static int hf_dvb_s2_bb_matype1_low_ro = -1;

static int hf_dvb_s2_bb_matype2 = -1;

static int hf_dvb_s2_bb_upl = -1;

static int hf_dvb_s2_bb_dfl = -1;

static int hf_dvb_s2_bb_sync = -1;

static int hf_dvb_s2_bb_syncd = -1;

static int hf_dvb_s2_bb_crc = -1;

static int hf_dvb_s2_bb_crc_status = -1;

static int hf_dvb_s2_bb_df = -1;

static int hf_dvb_s2_bb_eip_crc32 = -1;

static int hf_dvb_s2_bb_packetized = -1;

static int hf_dvb_s2_bb_transport = -1;

static int hf_dvb_s2_bb_reserved = -1;

static int proto_dvb_s2_gse = -1;

static int hf_dvb_s2_gse_hdr = -1;

static int hf_dvb_s2_gse_hdr_start = -1;

static int hf_dvb_s2_gse_hdr_stop = -1;

static int hf_dvb_s2_gse_hdr_labeltype = -1;

static int hf_dvb_s2_gse_hdr_length = -1;

static int hf_dvb_s2_gse_padding = -1;

static int hf_dvb_s2_gse_proto_next_header = -1;

static int hf_dvb_s2_gse_proto_ethertype = -1;

static int hf_dvb_s2_gse_label6 = -1;

static int hf_dvb_s2_gse_label3 = -1;

static int hf_dvb_s2_gse_fragid = -1;

static int hf_dvb_s2_gse_totlength = -1;

static int hf_dvb_s2_gse_exthdr = -1;

static int hf_dvb_s2_gse_ncr = -1;

static int hf_dvb_s2_gse_data = -1;

static int hf_dvb_s2_gse_crc32 = -1;

static gint ett_dvb_s2_modeadapt = -1;


dvbs2_defragment_init(void)
{
  reassembly_table_init(&dvbs2_reassembly_table,
                        &addresses_reassembly_table_functions);
}

void proto_register_dvb_s2_modeadapt(void);

void proto_reg_handoff_dvb_s2_modeadapt(void);

static reassembly_table dvbs2_reassembly_table;


static int dissect_dvb_s2_gse(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
    int         new_off                      = 0;
    int         frag_len;
    guint16     gse_hdr, data_len, padding_len, gse_proto = 0;

    proto_item *ti;
    proto_item *ttf;
    proto_tree *dvb_s2_gse_tree, *dvb_s2_gse_ncr_tree;

    tvbuff_t   *next_tvb, *data_tvb;
    gboolean   dissected = FALSE;
    gboolean   update_col_info = TRUE;

    static int * const gse_header_bitfields[] = {
        &hf_dvb_s2_gse_hdr_start,
        &hf_dvb_s2_gse_hdr_stop,
        &hf_dvb_s2_gse_hdr_labeltype,
        &hf_dvb_s2_gse_hdr_length,
        NULL
    };

    col_append_str(pinfo->cinfo, COL_INFO, " GSE");

    /* get the GSE header */
    gse_hdr = tvb_get_ntohs(tvb, DVB_S2_GSE_OFFS_HDR);

    /* check if this is just padding, which takes up the rest of the frame */
    if (BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_START_POS) &&
        BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_STOP_POS) &&
        BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_LABELTYPE_POS1) && BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_LABELTYPE_POS2)) {

        padding_len = tvb_reported_length(tvb);
        proto_tree_add_uint_format(tree, hf_dvb_s2_gse_padding, tvb, new_off, padding_len, padding_len,
                                   "DVB-S2 GSE Padding, Length: %d", padding_len);
        col_append_str(pinfo->cinfo, COL_INFO, " pad");
        new_off += padding_len;

        return new_off;
    } else {
        /* Not padding, parse as a GSE Header */
        new_off += 2;
        frag_len = (gse_hdr & DVB_S2_GSE_HDR_LENGTH_MASK)+2;
        ti = proto_tree_add_item(tree, proto_dvb_s2_gse, tvb, 0, frag_len, ENC_NA);
        dvb_s2_gse_tree = proto_item_add_subtree(ti, ett_dvb_s2_gse);

        proto_tree_add_bitmask_with_flags(dvb_s2_gse_tree, tvb, DVB_S2_GSE_OFFS_HDR, hf_dvb_s2_gse_hdr,
            ett_dvb_s2_gse_hdr, gse_header_bitfields, ENC_BIG_ENDIAN, BMT_NO_TFS);

        /* Get the fragment ID for reassembly */
        guint8 fragid = tvb_get_guint8(tvb, new_off);
        if (BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_START_POS) || BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_STOP_POS)) {
            /* Not a start or end packet, add only the fragid */
            proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_fragid, tvb, new_off, 1, ENC_BIG_ENDIAN);

            new_off += 1;
        }
        if (BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_START_POS) && BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_STOP_POS)) {
            /* Start packet, add the fragment size */
            proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_totlength, tvb, new_off, 2, ENC_BIG_ENDIAN);
            col_append_str(pinfo->cinfo, COL_INFO, "(frag) ");

            new_off += 2;
        }
        if (BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_START_POS)) {
            /* Start packet, decode the header */
            gse_proto = tvb_get_ntohs(tvb, new_off);

            /* Protocol Type */
            if (gse_proto <= 1535) {
                /* Type 1 (Next-Header Type field) */
                proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_proto_next_header, tvb, new_off, 2, ENC_BIG_ENDIAN);
            }
            else {
                /* Type 2 (EtherType compatible Type Fields) */
                proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_proto_ethertype, tvb, new_off, 2, ENC_BIG_ENDIAN);
            }
            new_off += 2;

            if (BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_LABELTYPE_POS1) && BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_LABELTYPE_POS2)) {
                /* 6 byte label */
                if (BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_STOP_POS))
                    col_append_str(pinfo->cinfo, COL_INFO, "6 ");

                proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_label6, tvb, new_off, 6, ENC_NA);

                new_off += 6;
            } else if (BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_LABELTYPE_POS1) &&
                       BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_LABELTYPE_POS2)) {
                /* 3 byte label */
                if (BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_STOP_POS))
                    col_append_str(pinfo->cinfo, COL_INFO, "3 ");

                proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_label3, tvb, new_off, 3, ENC_BIG_ENDIAN);

                new_off += 3;
            } else {
                /* 0 byte label */
                if (BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_STOP_POS))
                    col_append_str(pinfo->cinfo, COL_INFO, "0 ");
            }
            if (gse_proto < 0x0600 && gse_proto >= 0x100) {
                /* Only display optional extension headers */
                /* TODO: needs to be tested */

                /* TODO: implementation needs to be checked (len of ext-header??) */
                proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_exthdr, tvb, new_off, 1, ENC_BIG_ENDIAN);

                new_off += 1;
            }
        }
        else
        {
            /* correct cinfo */
            col_append_str(pinfo->cinfo, COL_INFO, "(frag) ");
        }

        next_tvb = tvb_new_subset_remaining(tvb, new_off);

        if (BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_START_POS) && BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_STOP_POS)) {
            data_len = (gse_hdr & DVB_S2_GSE_HDR_LENGTH_MASK) - (new_off - DVB_S2_GSE_MINSIZE) - DVB_S2_GSE_CRC32_LEN;
        } else {
            data_len = (gse_hdr & DVB_S2_GSE_HDR_LENGTH_MASK) - (new_off - DVB_S2_GSE_MINSIZE);
        }

        data_tvb = NULL;
        if (BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_START_POS) || BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_STOP_POS)) {
            fragment_head *dvbs2_frag_head = NULL;
            int offset = new_off;
            if (BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_START_POS)) {
                offset -= 2; /* re-include GSE type in reassembled data */
                data_len += 2;
            }
            dvbs2_frag_head = fragment_add_seq_next(&dvbs2_reassembly_table, tvb, offset,
                pinfo, fragid, NULL, data_len, BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_STOP_POS));

            if (BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_STOP_POS))
                dvbs2_frag_head = fragment_end_seq_next(&dvbs2_reassembly_table, pinfo, fragid, NULL);

            data_tvb = process_reassembled_data(tvb, new_off, pinfo, "Reassembled DVB-S2",
                dvbs2_frag_head, &dvbs2_frag_items, &update_col_info, tree);
        }

        if (data_tvb != NULL) {
            /* We have a reassembled packet. Extract the gse_proto from it. */
            gse_proto = tvb_get_ntohs(data_tvb, 0);
            /* And then remove it from the reassembled data */
            data_tvb = tvb_new_subset_remaining(data_tvb, 2);
        } else {
            data_tvb = tvb_new_subset_length(tvb, new_off, data_len);
        }

        switch (gse_proto) {
            case ETHERTYPE_IP:
                if (dvb_s2_full_dissection)
                {
                    new_off += call_dissector(ip_handle, next_tvb, pinfo, tree);
                    dissected = TRUE;
                }
                break;

            case ETHERTYPE_IPv6:
                if (dvb_s2_full_dissection)
                {
                    new_off += call_dissector(ipv6_handle, next_tvb, pinfo, tree);
                    dissected = TRUE;
                }
                break;

            case ETHERTYPE_VLAN:
                if (dvb_s2_full_dissection)
                {
                    new_off += call_dissector(eth_withoutfcs_handle, next_tvb, pinfo, tree);
                    dissected = TRUE;
                }
                break;

            case DVB_RCS2_SIGNAL_TABLE:
                call_dissector(dvb_s2_table_handle, data_tvb, pinfo, tree);
                new_off += data_len;
                dissected = TRUE;
                break;

            case DVB_RCS2_NCR:
                ttf = proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_ncr, tvb, new_off, data_len, ENC_NA);
                dvb_s2_gse_ncr_tree = proto_item_add_subtree(ttf, ett_dvb_s2_gse_ncr);
                proto_tree_add_item(dvb_s2_gse_ncr_tree, hf_dvb_s2_gse_data, tvb, new_off, data_len, ENC_NA);
                new_off += data_len;
                dissected = TRUE;
                break;

            default:
                /* Not handled! TODO: expert info? */
                break;
        }

        if (!dissected) {
            proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_data, tvb, new_off, data_len, ENC_NA);
            new_off += data_len;
        }

        /* add crc32 if last fragment */
        if (BIT_IS_CLEAR(gse_hdr, DVB_S2_GSE_HDR_START_POS) && BIT_IS_SET(gse_hdr, DVB_S2_GSE_HDR_STOP_POS)) {
            proto_tree_add_item(dvb_s2_gse_tree, hf_dvb_s2_gse_crc32, tvb, new_off, DVB_S2_GSE_CRC32_LEN, ENC_BIG_ENDIAN);
            new_off += DVB_S2_GSE_CRC32_LEN;
        }
    }

    return new_off;
}

#define BIT_IS_SET(var, bit) ((var) & (1 << (bit)))

#define BIT_IS_CLEAR(var, bit) !BIT_IS_SET(var, bit)


void proto_register_dvb_s2_modeadapt(void)
{
    module_t *dvb_s2_modeadapt_module;

    static hf_register_info hf_modeadapt[] = {
        {&hf_dvb_s2_modeadapt_sync, {
                "Sync Byte", "dvb-s2_modeadapt.sync",
                FT_UINT8, BASE_HEX, NULL, 0x0,
                "Das Sync Byte", HFILL}
        },
        {&hf_dvb_s2_modeadapt_acm, {
                "ACM command", "dvb-s2_modeadapt.acmcmd",
                FT_UINT8, BASE_HEX, NULL, 0x0,
                NULL, HFILL}
        },
        {&hf_dvb_s2_modeadapt_acm_fecframe, {
                "FEC frame size", "dvb-s2_modeadapt.acmcmd.fecframe",
                FT_BOOLEAN, 8, TFS(&tfs_modeadapt_fecframe), DVB_S2_MODEADAPT_FECFRAME_MASK,
                "FEC", HFILL}
        },
        {&hf_dvb_s2_modeadapt_acm_pilot, {
                "Pilots configuration", "dvb-s2_modeadapt.acmcmd.pilots",
                FT_BOOLEAN, 8, TFS(&tfs_modeadapt_pilots), DVB_S2_MODEADAPT_PILOTS_MASK,
                "Pilots", HFILL}
        },
        {&hf_dvb_s2_modeadapt_acm_modcod, {
                "Modcod indicator", "dvb-s2_modeadapt.acmcmd.modcod",
                FT_UINT8, BASE_DEC|BASE_EXT_STRING, &modeadapt_modcods_ext, DVB_S2_MODEADAPT_MODCODS_MASK,
                "Modcod", HFILL}
        },
        {&hf_dvb_s2_modeadapt_acm_modcod_s2x, {
                "Modcod indicator", "dvb-s2_modeadapt.acmcmd.modcod",
                FT_UINT8, BASE_DEC|BASE_EXT_STRING, &modeadapt_modcods_ext, DVB_S2_MODEADAPT_MODCODS_S2X_MASK,
                "Modcod S2X", HFILL}
        },
        {&hf_dvb_s2_modeadapt_cni, {
                "Carrier to Noise [dB]", "dvb-s2_modeadapt.cni",
                FT_UINT8, BASE_DEC|BASE_EXT_STRING, &modeadapt_esno_ext, 0x0,
                "CNI", HFILL}
        },
        {&hf_dvb_s2_modeadapt_frameno, {
                "Frame number", "dvb-s2_modeadapt.frameno",
                FT_UINT8, BASE_DEC, NULL, 0x0,
                "fno", HFILL}
        }
    };

/* Setup protocol subtree array */
    static gint *ett_modeadapt[] = {
        &ett_dvb_s2_modeadapt,
        &ett_dvb_s2_modeadapt_acm
    };

    static hf_register_info hf_bb[] = {
        {&hf_dvb_s2_bb_matype1, {
                "MATYPE1", "dvb-s2_bb.matype1",
                FT_UINT8, BASE_HEX, NULL, 0x0,
                "MATYPE1 Header Field", HFILL}
        },
        {&hf_dvb_s2_bb_matype1_gs, {
                "TS/GS Stream Input", "dvb-s2_bb.matype1.tsgs",
                FT_UINT8, BASE_DEC, VALS(bb_tsgs), DVB_S2_BB_TSGS_MASK,
                "Transport Stream Input or Generic Stream Input", HFILL}
        },
        {&hf_dvb_s2_bb_matype1_mis, {
                "Input Stream", "dvb-s2_bb.matype1.mis",
                FT_BOOLEAN, 8, TFS(&tfs_bb_mis), DVB_S2_BB_MIS_MASK,
                "Single Input Stream or Multiple Input Stream", HFILL}
        },
        {&hf_dvb_s2_bb_matype1_acm, {
                "Coding and Modulation", "dvb-s2_bb.matype1.acm",
                FT_BOOLEAN, 8, TFS(&tfs_bb_acm), DVB_S2_BB_ACM_MASK,
                "Constant Coding and Modulation or Adaptive Coding and Modulation", HFILL}
        },
        {&hf_dvb_s2_bb_matype1_issyi, {
                "ISSYI", "dvb-s2_bb.matype1.issyi",
                FT_BOOLEAN, 8, TFS(&tfs_bb_issyi), DVB_S2_BB_ISSYI_MASK,
                "Input Stream Synchronization Indicator", HFILL}
        },
        {&hf_dvb_s2_bb_matype1_npd, {
                "NPD", "dvb-s2_bb.matype1.npd",
                FT_BOOLEAN, 8, TFS(&tfs_bb_npd), DVB_S2_BB_NPD_MASK,
                "Null-packet deletion enabled", HFILL}
        },
        {&hf_dvb_s2_bb_matype1_high_ro, {
                "RO", "dvb-s2_bb.matype1.ro",
                FT_UINT8, BASE_DEC, VALS(bb_high_ro), DVB_S2_BB_RO_MASK,
                "Transmission Roll-off factor", HFILL}
        },
        {&hf_dvb_s2_bb_matype1_low_ro, {
                "RO", "dvb-s2_bb.matype1.ro",
                FT_UINT8, BASE_DEC, VALS(bb_low_ro), DVB_S2_BB_RO_MASK,
                "Transmission Roll-off factor", HFILL}
        },
        {&hf_dvb_s2_bb_matype2, {
                "MATYPE2", "dvb-s2_bb.matype2",
                FT_UINT8, BASE_HEX, NULL, 0x0,
                "MATYPE2 Header Field", HFILL}
        },
        {&hf_dvb_s2_bb_upl, {
                "UPL", "dvb-s2_bb.upl",
                FT_UINT16, BASE_HEX, NULL, 0x0,
                "User Packet Length", HFILL}
        },
        {&hf_dvb_s2_bb_dfl, {
                "DFL", "dvb-s2_bb.dfl",
                FT_UINT16, BASE_HEX, NULL, 0x0,
                "Data Field Length", HFILL}
        },
        {&hf_dvb_s2_bb_sync, {
                "SYNC", "dvb-s2_bb.sync",
                FT_UINT8, BASE_HEX, NULL, 0x0,
                "Copy of the User Packet Sync-byte", HFILL}
        },
        {&hf_dvb_s2_bb_syncd, {
                "SYNCD", "dvb-s2_bb.syncd",
                FT_UINT16, BASE_HEX, NULL, 0x0,
                "Distance to first user packet", HFILL}
        },
        {&hf_dvb_s2_bb_crc, {
                "Checksum", "dvb-s2_bb.crc",
                FT_UINT8, BASE_HEX, NULL, 0x0,
                "BB Header CRC-8", HFILL}
        },
        {&hf_dvb_s2_bb_crc_status, {
                "Checksum Status", "dvb-s2_bb.crc.status",
                FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0x0,
                NULL, HFILL}
        },
        {&hf_dvb_s2_bb_packetized, {
                "Packetized Generic Stream Data", "dvb-s2_bb.packetized",
                FT_BYTES, BASE_NONE, NULL, 0x0,
                "Packetized Generic Stream (non-TS) Data", HFILL}
        },
        {&hf_dvb_s2_bb_transport, {
                "Transport Stream Data", "dvb-s2_bb.transport",
                FT_BYTES, BASE_NONE, NULL, 0x0,
                "Transport Stream (TS) Data", HFILL}
        },
        {&hf_dvb_s2_bb_reserved, {
                "Reserved Stream Type Data", "dvb-s2_bb.reserved",
                FT_BYTES, BASE_NONE, NULL, 0x0,
                "Stream of an unknown reserved type", HFILL}
        },
        {&hf_dvb_s2_bb_df, {
                "BBFrame user data", "dvb-s2_bb.df",
                FT_BYTES, BASE_NONE, NULL, 0x0,
                NULL, HFILL}
        },
        {&hf_dvb_s2_bb_eip_crc32, {
                "EIP CRC32", "dvb-s2_bb.eip_crc32",
                FT_UINT32, BASE_HEX, NULL, 0x0,
                "Explicit Integrity Protection CRC32", HFILL}
        }
    };

    static gint *ett_bb[] = {
        &ett_dvb_s2_bb,
        &ett_dvb_s2_bb_matype1
    };

    /* DVB-S2 GSE Frame */
    static hf_register_info hf_gse[] = {
        {&hf_dvb_s2_gse_hdr, {
                "GSE header", "dvb-s2_gse.hdr",
                FT_UINT16, BASE_HEX, NULL, 0x0,
                "GSE Header (start/stop/length)", HFILL}
        },
        {&hf_dvb_s2_gse_hdr_start, {
                "Start", "dvb-s2_gse.hdr.start",
                FT_BOOLEAN, 16, TFS(&tfs_enabled_disabled), DVB_S2_GSE_HDR_START_MASK,
                "Start Indicator", HFILL}
        },
        {&hf_dvb_s2_gse_hdr_stop, {
                "Stop", "dvb-s2_gse.hdr.stop",
                FT_BOOLEAN, 16, TFS(&tfs_enabled_disabled), DVB_S2_GSE_HDR_STOP_MASK,
                "Stop Indicator", HFILL}
        },
        {&hf_dvb_s2_gse_hdr_labeltype, {
                "Label Type", "dvb-s2_gse.hdr.labeltype",
                FT_UINT16, BASE_HEX, VALS(gse_labeltype), DVB_S2_GSE_HDR_LABELTYPE_MASK,
                "Label Type Indicator", HFILL}
        },
        {&hf_dvb_s2_gse_hdr_length, {
                "Length", "dvb-s2_gse.hdr.length",
                FT_UINT16, BASE_DEC, NULL, DVB_S2_GSE_HDR_LENGTH_MASK,
                "GSE Length", HFILL}
        },
        {&hf_dvb_s2_gse_padding, {
                "GSE Padding", "dvb-s2_gse.padding",
                FT_UINT16, BASE_DEC, NULL, 0x0,
                "GSE Padding Bytes", HFILL}
        },
        {&hf_dvb_s2_gse_proto_next_header, {
                "Protocol", "dvb-s2_gse.proto",
                FT_UINT16, BASE_HEX, VALS(gse_proto_next_header_str), 0x0,
                "Protocol Type", HFILL}
        },
        {&hf_dvb_s2_gse_proto_ethertype, {
                "Protocol", "dvb-s2_gse.proto",
                FT_UINT16, BASE_HEX, VALS(etype_vals), 0x0,
                "Protocol Type", HFILL}
        },
        {&hf_dvb_s2_gse_label6, {
                "Label", "dvb-s2_gse.label_ether",
                FT_ETHER, BASE_NONE, NULL, 0x0,
                "Label Field", HFILL}
        },
        {&hf_dvb_s2_gse_label3, {
                "Label", "dvb-s2_gse.label",
                FT_UINT24, BASE_HEX, NULL, 0x0,
                "Label Field", HFILL}
        },
        {&hf_dvb_s2_gse_fragid, {
                "Frag ID", "dvb-s2_gse.fragid",
                FT_UINT8, BASE_HEX, NULL, 0x0,
                "Fragment ID", HFILL}
        },
        {&hf_dvb_s2_gse_totlength, {
                "Total Length", "dvb-s2_gse.totlength",
                FT_UINT16, BASE_DEC, NULL, 0x0,
                "GSE Total Frame Length", HFILL}
        },
        {&hf_dvb_s2_gse_exthdr, {
                "Extension Header", "dvb-s2_gse.exthdr",
                FT_UINT8, BASE_HEX, NULL, 0x0,
                "optional Extension Header", HFILL}
        },
        {&hf_dvb_s2_gse_ncr, {
                "NCR Packet", "dvb-s2_gse.ncr",
                FT_BYTES, BASE_NONE, NULL, 0x0,
                "GSE NCR PAcket", HFILL}
        },
        {&hf_dvb_s2_gse_data, {
                "PDU Data", "dvb-s2_gse.data",
                FT_BYTES, BASE_NONE, NULL, 0x0,
                "GSE Frame User Data", HFILL}
        },
        {&hf_dvb_s2_gse_crc32, {
                "CRC", "dvb-s2_gse.crc",
                FT_UINT32, BASE_HEX, NULL, 0x0,
                "CRC-32", HFILL}
        },
        { &hf_dvbs2_fragment_overlap,
            { "Fragment overlap", "dvb-s2_gse.fragment.overlap", FT_BOOLEAN, BASE_NONE,
                NULL, 0x0, "Fragment overlaps with other fragments", HFILL }},

        { &hf_dvbs2_fragment_overlap_conflict,
            { "Conflicting data in fragment overlap", "dvb-s2_gse.fragment.overlap.conflict",
                FT_BOOLEAN, BASE_NONE, NULL, 0x0,
                "Overlapping fragments contained conflicting data", HFILL }},

        { &hf_dvbs2_fragment_multiple_tails,
            { "Multiple tail fragments found", "dvb-s2_gse.fragment.multipletails",
                FT_BOOLEAN, BASE_NONE, NULL, 0x0,
                "Several tails were found when defragmenting the packet", HFILL }},

        { &hf_dvbs2_fragment_too_long_fragment,
            { "Fragment too long", "dvb-s2_gse.fragment.toolongfragment",
                FT_BOOLEAN, BASE_NONE, NULL, 0x0,
                "Fragment contained data past end of packet", HFILL }},

        { &hf_dvbs2_fragment_error,
            { "Defragmentation error", "dvb-s2_gse.fragment.error", FT_FRAMENUM, BASE_NONE,
                NULL, 0x0, "Defragmentation error due to illegal fragments", HFILL }},

        { &hf_dvbs2_fragment_count,
            { "Fragment count", "dvb-s2_gse.fragment.count", FT_UINT32, BASE_DEC,
                NULL, 0x0, NULL, HFILL }},

        { &hf_dvbs2_fragment,
            { "DVB-S2 GSE Fragment", "dvb-s2_gse.fragment", FT_FRAMENUM, BASE_NONE,
                NULL, 0x0, NULL, HFILL }},

        { &hf_dvbs2_fragments,
            { "DVB-S2 GSE Fragments", "dvb-s2_gse.fragments", FT_BYTES, BASE_NONE,
                NULL, 0x0, NULL, HFILL }},

        { &hf_dvbs2_reassembled_in,
            { "Reassembled DVB-S2 GSE in frame", "dvb-s2_gse.reassembled_in", FT_FRAMENUM, BASE_NONE,
                NULL, 0x0, "This GSE packet is reassembled in this frame", HFILL }},

        { &hf_dvbs2_reassembled_length,
            { "Reassembled DVB-S2 GSE length", "dvb-s2_gse.reassembled.length", FT_UINT32, BASE_DEC,
                NULL, 0x0, "The total length of the reassembled payload", HFILL }},

        { &hf_dvbs2_reassembled_data,
            { "Reassembled DVB-S2 GSE data", "dvb-s2_gse.reassembled.data", FT_BYTES, BASE_NONE,
                NULL, 0x0, "The reassembled payload", HFILL }}
    };

    static gint *ett_gse[] = {
        &ett_dvb_s2_gse,
        &ett_dvb_s2_gse_hdr,
        &ett_dvb_s2_gse_ncr,
        &ett_dvbs2_fragments,
        &ett_dvbs2_fragment,
    };

    static ei_register_info ei[] = {
        { &ei_dvb_s2_bb_crc, { "dvb-s2_bb.bad_checksum", PI_CHECKSUM, PI_ERROR, "Bad checksum", EXPFILL }},
        { &ei_dvb_s2_bb_issy_invalid, {"dvb-s2_bb.issy_invalid", PI_PROTOCOL, PI_WARN, "ISSY is active, which is not allowed for GSE packets", EXPFILL }},
        { &ei_dvb_s2_bb_npd_invalid, {"dvb-s2_bb.npd_invalid", PI_PROTOCOL, PI_WARN, "NPD is active, which is not allowed for GSE packets", EXPFILL }},
        { &ei_dvb_s2_bb_upl_invalid, {"dvb-s2_bb.upl_invalid", PI_PROTOCOL, PI_WARN, "User Packet Length non-zero, which is not allowed for GSE packets", EXPFILL }},
        { &ei_dvb_s2_bb_reserved, {"dvb-s2_bb.reserved_frame_format", PI_PROTOCOL, PI_WARN, "Reserved frame format in TS/GS is not defined", EXPFILL }},
        { &ei_dvb_s2_bb_header_ambiguous, { "dvb-s2_bb.header_ambiguous", PI_ASSUMPTION, PI_WARN, "Mode Adaptation header ambiguous", EXPFILL }},
    };

    expert_module_t* expert_dvb_s2_bb;

    proto_dvb_s2_modeadapt = proto_register_protocol("DVB-S2 Mode Adaptation Header", "DVB-S2", "dvb-s2_modeadapt");

    proto_dvb_s2_bb = proto_register_protocol("DVB-S2 Baseband Frame", "DVB-S2-BB", "dvb-s2_bb");

    proto_dvb_s2_gse = proto_register_protocol("DVB-S2 GSE Packet", "DVB-S2-GSE", "dvb-s2_gse");

    proto_register_field_array(proto_dvb_s2_modeadapt, hf_modeadapt, array_length(hf_modeadapt));
    proto_register_subtree_array(ett_modeadapt, array_length(ett_modeadapt));

    proto_register_field_array(proto_dvb_s2_bb, hf_bb, array_length(hf_bb));
    proto_register_subtree_array(ett_bb, array_length(ett_bb));
    expert_dvb_s2_bb = expert_register_protocol(proto_dvb_s2_bb);
    expert_register_field_array(expert_dvb_s2_bb, ei, array_length(ei));

    proto_register_field_array(proto_dvb_s2_gse, hf_gse, array_length(hf_gse));
    proto_register_subtree_array(ett_gse, array_length(ett_gse));

    dvb_s2_modeadapt_module = prefs_register_protocol(proto_dvb_s2_modeadapt, proto_reg_handoff_dvb_s2_modeadapt);

    prefs_register_obsolete_preference(dvb_s2_modeadapt_module, "enable");

    prefs_register_bool_preference(dvb_s2_modeadapt_module, "decode_df",
        "Enable dissection of DATA FIELD",
        "Check this to enable full protocol dissection of data above BBHeader",
        &dvb_s2_df_dissection);

    prefs_register_bool_preference(dvb_s2_modeadapt_module, "full_decode",
        "Enable dissection of GSE data",
        "Check this to enable full protocol dissection of data above GSE Layer",
        &dvb_s2_full_dissection);

    register_init_routine(dvbs2_defragment_init);
}

static dissector_handle_t ip_handle;

static dissector_handle_t ipv6_handle;

static dissector_handle_t eth_withoutfcs_handle;

static dissector_handle_t dvb_s2_table_handle;

static dissector_handle_t data_handle;

void proto_register_dvb_s2_modeadapt(void);

void proto_reg_handoff_dvb_s2_modeadapt(void);

static gboolean dvb_s2_full_dissection = FALSE;

static gboolean dvb_s2_df_dissection = FALSE;

static int proto_dvb_s2_modeadapt = -1;

static int hf_dvb_s2_modeadapt_sync = -1;

static int hf_dvb_s2_modeadapt_acm = -1;

static int hf_dvb_s2_modeadapt_acm_fecframe = -1;

static int hf_dvb_s2_modeadapt_acm_pilot = -1;

static int hf_dvb_s2_modeadapt_acm_modcod = -1;

static int hf_dvb_s2_modeadapt_acm_modcod_s2x = -1;

static int hf_dvb_s2_modeadapt_cni = -1;

static int hf_dvb_s2_modeadapt_frameno = -1;

static int proto_dvb_s2_bb = -1;

static int hf_dvb_s2_bb_matype1 = -1;

static int hf_dvb_s2_bb_matype1_gs = -1;

static int hf_dvb_s2_bb_matype1_mis = -1;

static int hf_dvb_s2_bb_matype1_acm = -1;

static int hf_dvb_s2_bb_matype1_issyi = -1;

static int hf_dvb_s2_bb_matype1_npd = -1;

static int hf_dvb_s2_bb_matype1_high_ro = -1;

static int hf_dvb_s2_bb_matype1_low_ro = -1;

static int hf_dvb_s2_bb_matype2 = -1;

static int hf_dvb_s2_bb_upl = -1;

static int hf_dvb_s2_bb_dfl = -1;

static int hf_dvb_s2_bb_sync = -1;

static int hf_dvb_s2_bb_syncd = -1;

static int hf_dvb_s2_bb_crc = -1;

static int hf_dvb_s2_bb_crc_status = -1;

static int hf_dvb_s2_bb_df = -1;

static int hf_dvb_s2_bb_eip_crc32 = -1;

static int hf_dvb_s2_bb_packetized = -1;

static int hf_dvb_s2_bb_transport = -1;

static int hf_dvb_s2_bb_reserved = -1;

static int proto_dvb_s2_gse = -1;

static int hf_dvb_s2_gse_hdr = -1;

static int hf_dvb_s2_gse_hdr_start = -1;

static int hf_dvb_s2_gse_hdr_stop = -1;

static int hf_dvb_s2_gse_hdr_labeltype = -1;

static int hf_dvb_s2_gse_hdr_length = -1;

static int hf_dvb_s2_gse_padding = -1;

static int hf_dvb_s2_gse_proto_next_header = -1;

static int hf_dvb_s2_gse_proto_ethertype = -1;

static int hf_dvb_s2_gse_label6 = -1;

static int hf_dvb_s2_gse_label3 = -1;

static int hf_dvb_s2_gse_fragid = -1;

static int hf_dvb_s2_gse_totlength = -1;

static int hf_dvb_s2_gse_exthdr = -1;

static int hf_dvb_s2_gse_ncr = -1;

static int hf_dvb_s2_gse_data = -1;


void proto_reg_handoff_dvb_s2_modeadapt(void)
{
    static gboolean prefs_initialized = FALSE;

    if (!prefs_initialized) {
        heur_dissector_add("udp", dissect_dvb_s2_modeadapt, "DVB-S2 over UDP", "dvb_s2_udp", proto_dvb_s2_modeadapt, HEURISTIC_DISABLE);
        ip_handle   = find_dissector_add_dependency("ip", proto_dvb_s2_bb);
        ipv6_handle = find_dissector_add_dependency("ipv6", proto_dvb_s2_bb);
        dvb_s2_table_handle = find_dissector("dvb-s2_table");
        eth_withoutfcs_handle = find_dissector("eth_withoutfcs");
        data_handle = find_dissector("data");
        prefs_initialized = TRUE;
    }
}


int h2_stream_was_closed(const h2_stream *stream)
{
    switch (stream->state) {
        case H2_SS_CLOSED:
        case H2_SS_CLEANUP:
            return 1;
        default:
            return 0;
    }
}

mod_htt2: fix incomplete sync with latest changes in github, adjust version number.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1889119 13f79535-47bb-0310-9956-ffa450edef68

#define S_CLS     (H2_SS_CLOSED + 1)

#define S_CLN     (H2_SS_CLEANUP + 1)

static int trans_on_send[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */
{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */
{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */
};


static int on_frame_recv(h2_stream_state_t state, int frame_type)
{
    return on_frame(state, frame_type, trans_on_recv, H2_ALEN(trans_on_recv));
}

static int trans_on_send[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */
{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */
{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */
};


apr_status_t h2_stream_add_header(h2_stream *stream,
                                  const char *name, size_t nlen,
                                  const char *value, size_t vlen)
{
    h2_session *session = stream->session;
    int error = 0, was_added = 0;
    apr_status_t status = APR_SUCCESS;

    if (stream->has_response) {
        return APR_EINVAL;
    }

    if (name[0] == ':') {
        if ((vlen) > session->s->limit_req_line) {
            /* pseudo header: approximation of request line size check */
            if (!h2_stream_is_ready(stream)) {
                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
                              H2_STRM_LOG(APLOGNO(10178), stream,
                                          "Request pseudo header exceeds "
                                          "LimitRequestFieldSize: %s"), name);
            }
            error = HTTP_REQUEST_URI_TOO_LARGE;
            goto cleanup;
        }
    }

    if (session->s->limit_req_fields > 0
        && stream->request_headers_added > session->s->limit_req_fields) {
        /* already over limit, count this attempt, but do not take it in */
        ++stream->request_headers_added;
    }
    else if (H2_SS_IDLE == stream->state) {
        if (!stream->rtmp) {
            stream->rtmp = h2_req_create(stream->id, stream->pool,
                                         NULL, NULL, NULL, NULL, NULL, 0);
        }
        status = h2_request_add_header(stream->rtmp, stream->pool,
                                       name, nlen, value, vlen,
                                       session->s->limit_req_fieldsize, &was_added);
        if (was_added) ++stream->request_headers_added;
    }
    else if (H2_SS_OPEN == stream->state) {
        status = add_trailer(stream, name, nlen, value, vlen,
                             session->s->limit_req_fieldsize, &was_added);
        if (was_added) ++stream->request_headers_added;
    }
    else {
        status = APR_EINVAL;
        goto cleanup;
    }

    if (APR_EINVAL == status) {
        /* header too long */
        if (!h2_stream_is_ready(stream)) {
            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
                          H2_STRM_LOG(APLOGNO(10180), stream,"Request header exceeds "
                                      "LimitRequestFieldSize: %.*s"),
                          (int)H2MIN(nlen, 80), name);
        }
        error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
        goto cleanup;
    }

    if (session->s->limit_req_fields > 0
        && stream->request_headers_added > session->s->limit_req_fields) {
        /* too many header lines */
        if (stream->request_headers_added > session->s->limit_req_fields + 100) {
            /* yeah, right, this request is way over the limit, say goodbye */
            h2_stream_rst(stream, H2_ERR_ENHANCE_YOUR_CALM);
            return APR_ECONNRESET;
        }
        if (!h2_stream_is_ready(stream)) {
            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
                          H2_STRM_LOG(APLOGNO(10181), stream, "Number of request headers "
                                      "exceeds LimitRequestFields"));
        }
        error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
        goto cleanup;
    }

cleanup:
    if (error) {
        set_error_response(stream, error);
        return APR_EINVAL;
    }
    else if (status != APR_SUCCESS) {
        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
                      H2_STRM_MSG(stream, "header %s not accepted"), name);
        h2_stream_dispatch(stream, H2_SEV_CANCELLED);
    }
    return status;
}

#define S_OPEN    (H2_SS_OPEN + 1)

static int trans_on_send[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */
{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */
{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */
};


apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_t frame_len)
{
    apr_status_t status = APR_SUCCESS;
    int new_state, eos = 0;

    new_state = on_frame_recv(stream->state, ftype);
    if (new_state < 0) {
        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                      H2_STRM_MSG(stream, "invalid frame %d recv"), ftype);
        AP_DEBUG_ASSERT(new_state > S_XXX);
        return transit(stream, new_state);
    }

    switch (ftype) {
        case NGHTTP2_DATA:
            eos = (flags & NGHTTP2_FLAG_END_STREAM);
            break;

        case NGHTTP2_HEADERS:
            eos = (flags & NGHTTP2_FLAG_END_STREAM);
            if (stream->state == H2_SS_OPEN) {
                /* trailer HEADER */
                if (!eos) {
                    h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
                }
                stream->in_trailer_octets += frame_len;
            }
            else {
                /* request HEADER */
                ap_assert(stream->request == NULL);
                if (stream->rtmp == NULL) {
                    /* This can only happen, if the stream has received no header
                     * name/value pairs at all. The latest nghttp2 version have become
                     * pretty good at detecting this early. In any case, we have
                     * to abort the connection here, since this is clearly a protocol error */
                    return APR_EINVAL;
                }
                status = h2_stream_end_headers(stream, eos, frame_len);
                if (status != APR_SUCCESS) goto leave;
            }
            break;

        default:
            break;
    }
    status = transit(stream, new_state);
    if (status == APR_SUCCESS && eos) {
        status = transit(stream, on_event(stream, H2_SEV_CLOSED_R));
    }
leave:
    return status;
}

#define S_XXX     (-2)                      /* Programming Error */

#define S_OPEN    (H2_SS_OPEN + 1)

static int trans_on_send[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */
{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */
{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */
};


apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount)
{
    h2_session *session = stream->session;

    if (amount > 0) {
        apr_off_t consumed = amount;

        while (consumed > 0) {
            int len = (consumed > INT_MAX)? INT_MAX : (int)consumed;
            nghttp2_session_consume(session->ngh2, stream->id, len);
            consumed -= len;
        }

#ifdef H2_NG2_LOCAL_WIN_SIZE
        if (1) {
            int cur_size = nghttp2_session_get_stream_local_window_size(
                session->ngh2, stream->id);
            int win = stream->in_window_size;
            int thigh = win * 8/10;
            int tlow = win * 2/10;
            const int win_max = 2*1024*1024;
            const int win_min = 32*1024;

            /* Work in progress, probably should add directives for these
             * values once this stabilizes somewhat. The general idea is
             * to adapt stream window sizes if the input window changes
             * a) very quickly (< good RTT) from full to empty
             * b) only a little bit (> bad RTT)
             * where in a) it grows and in b) it shrinks again.
             */
            if (cur_size > thigh && amount > thigh && win < win_max) {
                /* almost empty again with one reported consumption, how
                 * long did this take? */
                long ms = apr_time_msec(apr_time_now() - stream->in_last_write);
                if (ms < 40) {
                    win = H2MIN(win_max, win + (64*1024));
                }
            }
            else if (cur_size < tlow && amount < tlow && win > win_min) {
                /* staying full, for how long already? */
                long ms = apr_time_msec(apr_time_now() - stream->in_last_write);
                if (ms > 700) {
                    win = H2MAX(win_min, win - (32*1024));
                }
            }

            if (win != stream->in_window_size) {
                stream->in_window_size = win;
                nghttp2_session_set_local_window_size(session->ngh2,
                        NGHTTP2_FLAG_NONE, stream->id, win);
            }
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
                          "h2_stream(%ld-%d): consumed %ld bytes, window now %d/%d",
                          session->id, stream->id, (long)amount,
                          cur_size, stream->in_window_size);
        }
#endif
    }
    return APR_SUCCESS;
}


apr_status_t h2_stream_set_request_rec(h2_stream *stream,
                                       request_rec *r, int eos)
{
    h2_request *req;
    apr_status_t status;

    ap_assert(stream->request == NULL);
    ap_assert(stream->rtmp == NULL);
    if (stream->rst_error) {
        return APR_ECONNRESET;
    }
    status = h2_request_rcreate(&req, stream->pool, r);
    if (status == APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
                      H2_STRM_LOG(APLOGNO(03058), stream,
                      "set_request_rec %s host=%s://%s%s"),
                      req->method, req->scheme, req->authority, req->path);
        stream->rtmp = req;
        /* simulate the frames that led to this */
        return h2_stream_recv_frame(stream, NGHTTP2_HEADERS,
                                    NGHTTP2_FLAG_END_STREAM, 0);
    }
    return status;
}


const char *h2_stream_state_str(h2_stream *stream)
{
    return h2_ss_str(stream->state);
}

typedef struct {
    apr_size_t maxlen;
    const char *failed_key;
} val_len_check_ctx;


static void on_state_enter(h2_stream *stream)
{
    if (stream->monitor && stream->monitor->on_state_enter) {
        stream->monitor->on_state_enter(stream->monitor->ctx, stream);
    }
}


static void set_policy_for(h2_stream *stream, h2_request *r)
{
    int enabled = h2_session_push_enabled(stream->session);
    stream->push_policy = h2_push_policy_determine(r->headers, stream->pool, enabled);
    r->serialize = h2_config_sgeti(stream->session->s, H2_CONF_SER_HEADERS);
}


static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, const char *tag)
{
    if (APLOG_C_IS_LEVEL(s->session->c, lvl)) {
        conn_rec *c = s->session->c;
        char buffer[4 * 1024];
        apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);

        len = h2_util_bb_print(buffer, bmax, tag, "", s->out_buffer);
        ap_log_cerror(APLOG_MARK, lvl, 0, c,
                      H2_STRM_MSG(s, "out-buffer(%s)"), len? buffer : "empty");
    }
}


static apr_status_t transit(h2_stream *stream, int new_state)
{
    if (new_state == stream->state) {
        return APR_SUCCESS;
    }
    else if (new_state < 0) {
        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c,
                      H2_STRM_LOG(APLOGNO(03081), stream, "invalid transition"));
        on_state_invalid(stream);
        return APR_EINVAL;
    }

    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                  H2_STRM_MSG(stream, "transit to [%s]"), h2_ss_str(new_state));
    stream->state = new_state;
    switch (new_state) {
        case H2_SS_IDLE:
            break;
        case H2_SS_RSVD_L:
            close_input(stream);
            break;
        case H2_SS_RSVD_R:
            break;
        case H2_SS_OPEN:
            break;
        case H2_SS_CLOSED_L:
            close_output(stream);
            break;
        case H2_SS_CLOSED_R:
            close_input(stream);
            break;
        case H2_SS_CLOSED:
            close_input(stream);
            close_output(stream);
            if (stream->out_buffer) {
                apr_brigade_cleanup(stream->out_buffer);
            }
            break;
        case H2_SS_CLEANUP:
            break;
    }
    on_state_enter(stream);
    return APR_SUCCESS;
}

#define S_RS_L    (H2_SS_RSVD_L + 1)

#define S_RS_R    (H2_SS_RSVD_R + 1)

#define S_OPEN    (H2_SS_OPEN + 1)

#define S_CL_L    (H2_SS_CLOSED_L + 1)

#define S_CL_R    (H2_SS_CLOSED_R + 1)

#define S_CLS     (H2_SS_CLOSED + 1)

#define S_CLN     (H2_SS_CLEANUP + 1)

static int trans_on_send[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */
{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */
{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */
};


void h2_stream_set_request(h2_stream *stream, const h2_request *r)
{
    ap_assert(stream->request == NULL);
    ap_assert(stream->rtmp == NULL);
    stream->rtmp = h2_request_clone(stream->pool, r);
}


static int on_map(h2_stream_state_t state, int map[H2_SS_MAX])
{
    int op = map[state];
    switch (op) {
        case S_XXX:
        case S_ERR:
            return op;
        case S_NOP:
            return state;
        default:
            return op-1;
    }
}

#define S_XXX     (-2)                      /* Programming Error */

#define S_ERR     (-1)                      /* Protocol Error */

#define S_NOP     (0)                       /* No Change */

static int trans_on_send[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */
{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */
{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */
};


h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
                            h2_stream_monitor *monitor, int initiated_on)
{
    h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));

    stream->id           = id;
    stream->initiated_on = initiated_on;
    stream->created      = apr_time_now();
    stream->state        = H2_SS_IDLE;
    stream->pool         = pool;
    stream->session      = session;
    stream->monitor      = monitor;
    stream->max_mem      = session->max_stream_mem;

#ifdef H2_NG2_LOCAL_WIN_SIZE
    stream->in_window_size =
        nghttp2_session_get_stream_local_window_size(
            stream->session->ngh2, stream->id);
#endif

    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
                  H2_STRM_LOG(APLOGNO(03082), stream, "created"));
    on_state_enter(stream);
    return stream;
}


static int on_event(h2_stream* stream, h2_stream_event_t ev)
{
    if (stream->monitor && stream->monitor->on_event) {
        stream->monitor->on_event(stream->monitor->ctx, stream, ev);
    }
    if (ev < H2_ALEN(trans_on_event)) {
        return on_map(stream->state, trans_on_event[ev]);
    }
    return stream->state;
}


static apr_status_t close_output(h2_stream *stream)
{
    if (!stream->output || h2_beam_is_closed(stream->output)) {
        return APR_SUCCESS;
    }
    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                  H2_STRM_MSG(stream, "closing output"));
    return h2_beam_leave(stream->output);
}


static apr_status_t setup_input(h2_stream *stream) {
    if (stream->input == NULL) {
        int empty = (stream->input_eof
                     && (!stream->in_buffer
                         || APR_BRIGADE_EMPTY(stream->in_buffer)));
        if (!empty) {
            h2_beam_create(&stream->input, stream->pool, stream->id,
                           "input", H2_BEAM_OWNER_SEND, 0,
                           stream->session->s->timeout);
            h2_beam_send_from(stream->input, stream->pool);
        }
    }
    return APR_SUCCESS;
}


static void on_state_invalid(h2_stream *stream)
{
    if (stream->monitor && stream->monitor->on_state_invalid) {
        stream->monitor->on_state_invalid(stream->monitor->ctx, stream);
    }
    /* stream got an event/frame invalid in its state */
    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                  H2_STRM_MSG(stream, "invalid state event"));
    switch (stream->state) {
        case H2_SS_OPEN:
        case H2_SS_RSVD_L:
        case H2_SS_RSVD_R:
        case H2_SS_CLOSED_L:
        case H2_SS_CLOSED_R:
            h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
            break;
        default:
            break;
    }
}

#define S_RS_L    (H2_SS_RSVD_L + 1)

#define S_RS_R    (H2_SS_RSVD_R + 1)

#define S_OPEN    (H2_SS_OPEN + 1)

#define S_CL_L    (H2_SS_CLOSED_L + 1)

#define S_CL_R    (H2_SS_CLOSED_R + 1)

static int trans_on_send[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */
{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */
{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */
};


apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes)
{
    apr_status_t status;
    val_len_check_ctx ctx;

    status = h2_request_end_headers(stream->rtmp, stream->pool, eos, raw_bytes);
    if (APR_SUCCESS == status) {
        set_policy_for(stream, stream->rtmp);
        stream->request = stream->rtmp;
        stream->rtmp = NULL;

        ctx.maxlen = stream->session->s->limit_req_fieldsize;
        ctx.failed_key = NULL;
        apr_table_do(table_check_val_len, &ctx, stream->request->headers, NULL);
        if (ctx.failed_key) {
            if (!h2_stream_is_ready(stream)) {
                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c,
                              H2_STRM_LOG(APLOGNO(10230), stream,"Request header exceeds "
                                          "LimitRequestFieldSize: %.*s"),
                              (int)H2MIN(strlen(ctx.failed_key), 80), ctx.failed_key);
            }
            set_error_response(stream, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE);
            /* keep on returning APR_SUCCESS, so that we send a HTTP response and
             * do not RST the stream. */
        }
    }
    return status;
}


void h2_stream_rst(h2_stream *stream, int error_code)
{
    stream->rst_error = error_code;
    if (stream->input) {
        h2_beam_abort(stream->input);
    }
    if (stream->output) {
        h2_beam_leave(stream->output);
    }
    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                  H2_STRM_MSG(stream, "reset, error=%d"), error_code);
    h2_stream_dispatch(stream, H2_SEV_CANCELLED);
}


apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
                                    const uint8_t *data, size_t len)
{
    h2_session *session = stream->session;
    apr_status_t status = APR_SUCCESS;

    stream->in_data_frames++;
    if (len > 0) {
        if (APLOGctrace3(session->c)) {
            const char *load = apr_pstrndup(stream->pool, (const char *)data, len);
            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, session->c,
                          H2_STRM_MSG(stream, "recv DATA, len=%d: -->%s<--"),
                          (int)len, load);
        }
        else {
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c,
                          H2_STRM_MSG(stream, "recv DATA, len=%d"), (int)len);
        }
        stream->in_data_octets += len;
        if (!stream->in_buffer) {
            stream->in_buffer = apr_brigade_create(stream->pool,
                                                   session->c->bucket_alloc);
        }
        apr_brigade_write(stream->in_buffer, NULL, NULL, (const char *)data, len);
        h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
    }
    return status;
}


apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
                               apr_off_t *plen, int *peos)
{
    conn_rec *c = stream->session->c;
    apr_status_t status = APR_SUCCESS;

    if (stream->rst_error) {
        return APR_ECONNRESET;
    }
    status = h2_append_brigade(bb, stream->out_buffer, plen, peos, is_not_headers);
    if (status == APR_SUCCESS && !*peos && !*plen) {
        status = APR_EAGAIN;
    }
    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
                  H2_STRM_MSG(stream, "read_to, len=%ld eos=%d"),
                  (long)*plen, *peos);
    return status;
}


apr_status_t h2_stream_flush_input(h2_stream *stream)
{
    apr_status_t status = APR_SUCCESS;

    if (stream->in_buffer && !APR_BRIGADE_EMPTY(stream->in_buffer)) {
        setup_input(stream);
        status = h2_beam_send(stream->input, stream->in_buffer, APR_BLOCK_READ);
        stream->in_last_write = apr_time_now();
    }
    if (stream->input_eof
        && stream->input && !h2_beam_is_closed(stream->input)) {
        status = h2_beam_close(stream->input);
    }
    return status;
}

#define S_ERR     (-1)                      /* Protocol Error */

#define S_NOP     (0)                       /* No Change */

#define S_XXX     (-2)                      /* Programming Error */

#define S_OPEN    (H2_SS_OPEN + 1)

#define S_CLS     (H2_SS_CLOSED + 1)

#define S_IDL     (H2_SS_IDL + 1)

#define S_CL_L    (H2_SS_CLOSED_L + 1)

#define S_CL_R    (H2_SS_CLOSED_R + 1)

#define S_CLN     (H2_SS_CLEANUP + 1)

#define S_RS_L    (H2_SS_RSVD_L + 1)

#define S_RS_R    (H2_SS_RSVD_R + 1)

typedef struct {
    apr_size_t maxlen;
    const char *failed_key;
} val_len_check_ctx;

static int trans_on_event[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_XXX, S_ERR,  S_ERR,  S_CL_L, S_CLS,  S_XXX,  S_XXX,  S_XXX, },/* EV_CLOSED_L*/
{ S_ERR, S_ERR,  S_ERR,  S_CL_R, S_ERR,  S_CLS,  S_NOP,  S_NOP, },/* EV_CLOSED_R*/
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* EV_CANCELLED*/
{ S_NOP, S_XXX,  S_XXX,  S_XXX,  S_XXX,  S_CLS,  S_CLN,  S_XXX, },/* EV_EOS_SENT*/
};


static apr_status_t add_buffered_data(h2_stream *stream, apr_off_t requested,
                                      apr_off_t *plen, int *peos, int *is_all,
                                      h2_headers **pheaders)
{
    apr_bucket *b, *e;

    *peos = 0;
    *plen = 0;
    *is_all = 0;
    if (pheaders) {
        *pheaders = NULL;
    }

    H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "add_buffered_data");
    b = APR_BRIGADE_FIRST(stream->out_buffer);
    while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
        e = APR_BUCKET_NEXT(b);
        if (APR_BUCKET_IS_METADATA(b)) {
            if (APR_BUCKET_IS_FLUSH(b)) {
                APR_BUCKET_REMOVE(b);
                apr_bucket_destroy(b);
            }
            else if (APR_BUCKET_IS_EOS(b)) {
                *peos = 1;
                return APR_SUCCESS;
            }
            else if (H2_BUCKET_IS_HEADERS(b)) {
                if (*plen > 0) {
                    /* data before the response, can only return up to here */
                    return APR_SUCCESS;
                }
                else if (pheaders) {
                    *pheaders = h2_bucket_headers_get(b);
                    APR_BUCKET_REMOVE(b);
                    apr_bucket_destroy(b);
                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                                  H2_STRM_MSG(stream, "prep, -> response %d"),
                                  (*pheaders)->status);
                    return APR_SUCCESS;
                }
                else {
                    return APR_EAGAIN;
                }
            }
        }
        else if (b->length == 0) {
            APR_BUCKET_REMOVE(b);
            apr_bucket_destroy(b);
        }
        else {
            ap_assert(b->length != (apr_size_t)-1);
            *plen += b->length;
            if (*plen >= requested) {
                *plen = requested;
                return APR_SUCCESS;
            }
        }
        b = e;
    }
    *is_all = 1;
    return APR_SUCCESS;
}


const h2_priority *h2_stream_get_priority(h2_stream *stream,
                                          h2_headers *response)
{
    if (response && stream->initiated_on) {
        const char *ctype = apr_table_get(response->headers, "content-type");
        if (ctype) {
            /* FIXME: Not good enough, config needs to come from request->server */
            return h2_cconfig_get_priority(stream->session->c, ctype);
        }
    }
    return NULL;
}


static int is_not_headers(apr_bucket *b)
{
    return !H2_BUCKET_IS_HEADERS(b);
}


static apr_bucket *get_first_headers_bucket(apr_bucket_brigade *bb)
{
    if (bb) {
        apr_bucket *b = APR_BRIGADE_FIRST(bb);
        while (b != APR_BRIGADE_SENTINEL(bb)) {
            if (H2_BUCKET_IS_HEADERS(b)) {
                return b;
            }
            b = APR_BUCKET_NEXT(b);
        }
    }
    return NULL;
}


static const char *h2_ss_str(h2_stream_state_t state)
{
    switch (state) {
        case H2_SS_IDLE:
            return "IDLE";
        case H2_SS_RSVD_L:
            return "RESERVED_LOCAL";
        case H2_SS_RSVD_R:
            return "RESERVED_REMOTE";
        case H2_SS_OPEN:
            return "OPEN";
        case H2_SS_CLOSED_L:
            return "HALF_CLOSED_LOCAL";
        case H2_SS_CLOSED_R:
            return "HALF_CLOSED_REMOTE";
        case H2_SS_CLOSED:
            return "CLOSED";
        case H2_SS_CLEANUP:
            return "CLEANUP";
        default:
            return "UNKNOWN";
    }
}


apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_t frame_len)
{
    apr_status_t status = APR_SUCCESS;
    int new_state, eos = 0;

    new_state = on_frame_send(stream->state, ftype);
    if (new_state < 0) {
        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                      H2_STRM_MSG(stream, "invalid frame %d send"), ftype);
        AP_DEBUG_ASSERT(new_state > S_XXX);
        return transit(stream, new_state);
    }

    ++stream->out_frames;
    stream->out_frame_octets += frame_len;
    switch (ftype) {
        case NGHTTP2_DATA:
            eos = (flags & NGHTTP2_FLAG_END_STREAM);
            break;

        case NGHTTP2_HEADERS:
            eos = (flags & NGHTTP2_FLAG_END_STREAM);
            break;

        case NGHTTP2_PUSH_PROMISE:
                /* start pushed stream */
                ap_assert(stream->request == NULL);
                ap_assert(stream->rtmp != NULL);
                status = h2_stream_end_headers(stream, 1, 0);
                if (status != APR_SUCCESS) goto leave;
            break;

        default:
            break;
    }
    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                  H2_STRM_MSG(stream, "send frame %d, eos=%d"), ftype, eos);
    status = transit(stream, new_state);
    if (status == APR_SUCCESS && eos) {
        status = transit(stream, on_event(stream, H2_SEV_CLOSED_L));
    }
leave:
    return status;
}

#define S_XXX     (-2)                      /* Programming Error */

static int trans_on_send[][H2_SS_MAX] = {
/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */
{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */
{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */
{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */
{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */
{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */
{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */
};


static void prep_output(h2_stream *stream) {
    conn_rec *c = stream->session->c;
    if (!stream->out_buffer) {
        stream->out_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
    }
}


static apr_status_t add_trailer(h2_stream *stream,
                                const char *name, size_t nlen,
                                const char *value, size_t vlen,
                                size_t max_field_len, int *pwas_added)
{
    conn_rec *c = stream->session->c;
    char *hname, *hvalue;
    const char *existing;

    *pwas_added = 0;
    if (nlen == 0 || name[0] == ':') {
        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c,
                      H2_STRM_LOG(APLOGNO(03060), stream,
                      "pseudo header in trailer"));
        return APR_EINVAL;
    }
    if (h2_req_ignore_trailer(name, nlen)) {
        return APR_SUCCESS;
    }
    if (!stream->trailers) {
        stream->trailers = apr_table_make(stream->pool, 5);
    }
    hname = apr_pstrndup(stream->pool, name, nlen);
    h2_util_camel_case_header(hname, nlen);
    existing = apr_table_get(stream->trailers, hname);
    if (max_field_len
        && ((existing? strlen(existing)+2 : 0) + vlen + nlen + 2 > max_field_len)) {
        /* "key: (oldval, )?nval" is too long */
        return APR_EINVAL;
    }
    if (!existing) *pwas_added = 1;
    hvalue = apr_pstrndup(stream->pool, value, vlen);
    apr_table_mergen(stream->trailers, hname, hvalue);
    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
                  H2_STRM_MSG(stream, "added trailer '%s: %s'"), hname, hvalue);

    return APR_SUCCESS;
}


apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
{
    apr_status_t status = APR_SUCCESS;
    apr_array_header_t *pushes;
    int i;

    pushes = h2_push_collect_update(stream, stream->request, response);
    if (pushes && !apr_is_empty_array(pushes)) {
        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                      H2_STRM_MSG(stream, "found %d push candidates"),
                      pushes->nelts);
        for (i = 0; i < pushes->nelts; ++i) {
            h2_push *push = APR_ARRAY_IDX(pushes, i, h2_push*);
            h2_stream *s = h2_session_push(stream->session, stream, push);
            if (!s) {
                status = APR_ECONNRESET;
                break;
            }
        }
    }
    return status;
}


static int on_frame_send(h2_stream_state_t state, int frame_type)
{
    return on_frame(state, frame_type, trans_on_send, H2_ALEN(trans_on_send));
}


apr_table_t *h2_stream_get_trailers(h2_stream *stream)
{
    return NULL;
}


static void on_state_event(h2_stream *stream, h2_stream_event_t ev)
{
    if (stream->monitor && stream->monitor->on_state_event) {
        stream->monitor->on_state_event(stream->monitor->ctx, stream, ev);
    }
}


static int on_frame(h2_stream_state_t state, int frame_type,
                    int frame_map[][H2_SS_MAX], apr_size_t maxlen)
{
    ap_assert(frame_type >= 0);
    ap_assert(state >= 0);
    if (frame_type >= maxlen) {
        return state; /* NOP, ignore unknown frame types */
    }
    return on_map(state, frame_map[frame_type]);
}


void h2_stream_cleanup(h2_stream *stream)
{
    apr_status_t status;

    ap_assert(stream);
    if (stream->out_buffer) {
        /* remove any left over output buckets that may still have
         * references into request pools */
        apr_brigade_cleanup(stream->out_buffer);
    }
    if (stream->input) {
        h2_beam_abort(stream->input);
        status = h2_beam_wait_empty(stream->input, APR_NONBLOCK_READ);
        if (status == APR_EAGAIN) {
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
                          H2_STRM_MSG(stream, "wait on input drain"));
            status = h2_beam_wait_empty(stream->input, APR_BLOCK_READ);
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c,
                          H2_STRM_MSG(stream, "input drain returned"));
        }
    }
}


void h2_stream_set_monitor(h2_stream *stream, h2_stream_monitor *monitor)
{
    stream->monitor = monitor;
}


apr_status_t h2_stream_prep_processing(h2_stream *stream)
{
    if (stream->request) {
        const h2_request *r = stream->request;
        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                      H2_STRM_MSG(stream, "schedule %s %s://%s%s chunked=%d"),
                      r->method, r->scheme, r->authority, r->path, r->chunked);
        setup_input(stream);
        stream->scheduled = 1;
        return APR_SUCCESS;
    }
    return APR_EINVAL;
}


static void set_error_response(h2_stream *stream, int http_status)
{
    if (!h2_stream_is_ready(stream) && stream->rtmp) {
        stream->rtmp->http_status = http_status;
    }
}


int h2_stream_is_ready(h2_stream *stream)
{
    if (stream->has_response) {
        return 1;
    }
    else if (stream->out_buffer && get_first_headers_bucket(stream->out_buffer)) {
        return 1;
    }
    return 0;
}


void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev)
{
    int new_state;

    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
                  H2_STRM_MSG(stream, "dispatch event %d"), ev);
    new_state = on_event(stream, ev);
    if (new_state < 0) {
        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c,
                      H2_STRM_LOG(APLOGNO(10002), stream, "invalid event %d"), ev);
        on_state_invalid(stream);
        AP_DEBUG_ASSERT(new_state > S_XXX);
        return;
    }
    else if (new_state == stream->state) {
        /* nop */
        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
                      H2_STRM_MSG(stream, "non-state event %d"), ev);
        return;
    }
    else {
        on_state_event(stream, ev);
        transit(stream, new_state);
    }
}


void h2_stream_destroy(h2_stream *stream)
{
    ap_assert(stream);
    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c,
                  H2_STRM_MSG(stream, "destroy"));
    apr_pool_destroy(stream->pool);
}


static int table_check_val_len(void *baton, const char *key, const char *value)
{
    val_len_check_ctx *ctx = baton;

    if (strlen(value) <= ctx->maxlen) return 1;
    ctx->failed_key = key;
    return 0;
}


static apr_status_t close_input(h2_stream *stream)
{
    conn_rec *c = stream->session->c;
    apr_status_t status = APR_SUCCESS;

    stream->input_eof = 1;
    if (stream->input && h2_beam_is_closed(stream->input)) {
        return APR_SUCCESS;
    }

    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
                  H2_STRM_MSG(stream, "closing input"));
    if (stream->rst_error) {
        return APR_ECONNRESET;
    }

    if (stream->trailers && !apr_is_empty_table(stream->trailers)) {
        apr_bucket *b;
        h2_headers *r;

        if (!stream->in_buffer) {
            stream->in_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
        }

        r = h2_headers_create(HTTP_OK, stream->trailers, NULL,
            stream->in_trailer_octets, stream->pool);
        stream->trailers = NULL;
        b = h2_bucket_headers_create(c->bucket_alloc, r);
        APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);

        b = apr_bucket_eos_create(c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);

        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
                      H2_STRM_MSG(stream, "added trailers"));
        h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
    }
    if (stream->input) {
        h2_stream_flush_input(stream);
        return h2_beam_close(stream->input);
    }
    return status;
}