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;
}


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.

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;
}

Infinite loop in DVB-S2-BB dissector in Wireshark 3.4.0 to 3.4.5 allows denial of service via packet injection or crafted capture file


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


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;
}


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);
}


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;
    }
}


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

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;
    }
}

Apache HTTP Server protocol handler for the HTTP/2 protocol checks received request headers against the size limitations as configured for the server and used for the HTTP/1 protocol as well. On violation of these restrictions and HTTP response is sent to the client with a status code indicating why the request was rejected. This rejection response was not fully initialised in the HTTP/2 protocol handler if the offending header was the very first one received or appeared in a a footer. This led to a NULL pointer dereference on initialised memory, crashing reliably the child process. Since such a triggering HTTP/2 request is easy to craft and submit, this can be exploited to DoS the server. This issue affected mod_http2 1.15.17 and Apache HTTP Server version 2.4.47 only. Apache HTTP Server 2.4.47 was never released.


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));
}


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;
}


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;
}


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);
}


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;
}


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;
    }
}


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;
    }
}


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;
}


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;
}


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;
}


apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen,
                                   int *peos, h2_headers **pheaders)
{
    apr_status_t status = APR_SUCCESS;
    apr_off_t requested, missing, max_chunk = H2_DATA_CHUNK_SIZE;
    conn_rec *c;
    int complete, was_closed = 0;

    ap_assert(stream);

    if (stream->rst_error) {
        *plen = 0;
        *peos = 1;
        return APR_ECONNRESET;
    }

    c = stream->session->c;
    prep_output(stream);

    /* determine how much we'd like to send. We cannot send more than
     * is requested. But we can reduce the size in case the master
     * connection operates in smaller chunks. (TSL warmup) */
    if (stream->session->io.write_size > 0) {
        max_chunk = stream->session->io.write_size - H2_FRAME_HDR_LEN;
    }
    requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;

    /* count the buffered data until eos or a headers bucket */
    status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);

    if (status == APR_EAGAIN) {
        /* TODO: ugly, someone needs to retrieve the response first */
        h2_mplx_m_keep_active(stream->session->mplx, stream);
        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                      H2_STRM_MSG(stream, "prep, response eagain"));
        return status;
    }
    else if (status != APR_SUCCESS) {
        return status;
    }

    if (pheaders && *pheaders) {
        return APR_SUCCESS;
    }

    /* If there we do not have enough buffered data to satisfy the requested
     * length *and* we counted the _complete_ buffer (and did not stop in the middle
     * because of meta data there), lets see if we can read more from the
     * output beam */
    missing = H2MIN(requested, stream->max_mem) - *plen;
    if (complete && !*peos && missing > 0) {
        apr_status_t rv = APR_EOF;

        if (stream->output) {
            H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
            h2_beam_log(stream->output, c, APLOG_TRACE2, "pre read output");
            rv = h2_beam_receive(stream->output, stream->out_buffer,
                                 APR_NONBLOCK_READ, stream->max_mem - *plen, &was_closed);
            H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "post");
            h2_beam_log(stream->output, c, APLOG_TRACE2, "post read output");
        }

        if (rv == APR_SUCCESS) {
            /* count the buffer again, now that we have read output */
            status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
        }
        else if (APR_STATUS_IS_EOF(rv)) {
            apr_bucket *eos = apr_bucket_eos_create(c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(stream->out_buffer, eos);
            *peos = 1;
        }
        else if (APR_STATUS_IS_EAGAIN(rv)) {
            /* we set this is the status of this call only if there
             * is no buffered data, see check below */
        }
        else {
            /* real error reading. Give this back directly, even though
             * we may have something buffered. */
            status = rv;
        }
    }

    if (status == APR_SUCCESS) {
        if (*peos || *plen) {
            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                          H2_STRM_MSG(stream, "prepare, len=%ld eos=%d"),
                          (long)*plen, *peos);
        }
        else {
            status = was_closed? APR_EOF : APR_EAGAIN;
            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                          H2_STRM_MSG(stream, "prepare, no data"));
        }
    }
    return status;
}


Diagnose ERE '()|\1'

Problem reported by Hanno Böck in: http://bugs.gnu.org/21513
* lib/regcomp.c (parse_reg_exp): While parsing alternatives, keep
track of the set of previously-completed subexpressions available
before the first alternative, and restore this set just before
parsing each subsequent alternative.  This lets us diagnose the
invalid back-reference in the ERE '()|\1'.

fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax)
{
  Idx num = REG_MISSING;
  unsigned char c;
  while (1)
    {
      fetch_token (token, input, syntax);
      c = token->opr.c;
      if (BE (token->type == END_OF_RE, 0))
	return REG_ERROR;
      if (token->type == OP_CLOSE_DUP_NUM || c == ',')
	break;
      num = ((token->type != CHARACTER || c < '0' || '9' < c
	      || num == REG_ERROR)
	     ? REG_ERROR
	     : num == REG_MISSING
	     ? c - '0'
	     : MIN (RE_DUP_MAX + 1, num * 10 + c - '0'));
    }
  return num;
}

In the GNU C Library (aka glibc or libc6) before 2.28, parse_reg_exp in posix/regcomp.c misparses alternatives, which allows attackers to cause a denial of service (assertion failure and application exit) or trigger an incorrect result by attempting a regular-expression match.


analyze (regex_t *preg)
{
  re_dfa_t *dfa = preg->buffer;
  reg_errcode_t ret;

  /* Allocate arrays.  */
  dfa->nexts = re_malloc (Idx, dfa->nodes_alloc);
  dfa->org_indices = re_malloc (Idx, dfa->nodes_alloc);
  dfa->edests = re_malloc (re_node_set, dfa->nodes_alloc);
  dfa->eclosures = re_malloc (re_node_set, dfa->nodes_alloc);
  if (BE (dfa->nexts == NULL || dfa->org_indices == NULL || dfa->edests == NULL
	  || dfa->eclosures == NULL, 0))
    return REG_ESPACE;

  dfa->subexp_map = re_malloc (Idx, preg->re_nsub);
  if (dfa->subexp_map != NULL)
    {
      Idx i;
      for (i = 0; i < preg->re_nsub; i++)
	dfa->subexp_map[i] = i;
      preorder (dfa->str_tree, optimize_subexps, dfa);
      for (i = 0; i < preg->re_nsub; i++)
	if (dfa->subexp_map[i] != i)
	  break;
      if (i == preg->re_nsub)
	{
	  free (dfa->subexp_map);
	  dfa->subexp_map = NULL;
	}
    }

  ret = postorder (dfa->str_tree, lower_subexps, preg);
  if (BE (ret != REG_NOERROR, 0))
    return ret;
  ret = postorder (dfa->str_tree, calc_first, dfa);
  if (BE (ret != REG_NOERROR, 0))
    return ret;
  preorder (dfa->str_tree, calc_next, dfa);
  ret = preorder (dfa->str_tree, link_nfa_nodes, dfa);
  if (BE (ret != REG_NOERROR, 0))
    return ret;
  ret = calc_eclosure (dfa);
  if (BE (ret != REG_NOERROR, 0))
    return ret;

  /* We only need this during the prune_impossible_nodes pass in regexec.c;
     skip it if p_i_n will not run, as calc_inveclosure can be quadratic.  */
  if ((!preg->no_sub && preg->re_nsub > 0 && dfa->has_plural_match)
      || dfa->nbackref)
    {
      dfa->inveclosures = re_malloc (re_node_set, dfa->nodes_len);
      if (BE (dfa->inveclosures == NULL, 0))
	return REG_ESPACE;
      ret = calc_inveclosure (dfa);
    }

  return ret;
}


parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token,
	       reg_syntax_t syntax, Idx nest, reg_errcode_t *err)
{
  re_dfa_t *dfa = preg->buffer;
  bin_tree_t *tree;
  size_t cur_nsub;
  cur_nsub = preg->re_nsub++;

  fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE);

  /* The subexpression may be a null string.  */
  if (token->type == OP_CLOSE_SUBEXP)
    tree = NULL;
  else
    {
      tree = parse_reg_exp (regexp, preg, token, syntax, nest, err);
      if (BE (*err == REG_NOERROR && token->type != OP_CLOSE_SUBEXP, 0))
	{
	  if (tree != NULL)
	    postorder (tree, free_tree, NULL);
	  *err = REG_EPAREN;
	}
      if (BE (*err != REG_NOERROR, 0))
	return NULL;
    }

  if (cur_nsub <= '9' - '1')
    dfa->completed_bkref_map |= 1 << cur_nsub;

  tree = create_tree (dfa, tree, NULL, SUBEXP);
  if (BE (tree == NULL, 0))
    {
      *err = REG_ESPACE;
      return NULL;
    }
  tree->token.opr.idx = cur_nsub;
  return tree;
}


parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp,
		      re_token_t *token)
{
  unsigned char ch, delim = token->opr.c;
  int i = 0;
  if (re_string_eoi(regexp))
    return REG_EBRACK;
  for (;; ++i)
    {
      if (i >= BRACKET_NAME_BUF_SIZE)
	return REG_EBRACK;
      if (token->type == OP_OPEN_CHAR_CLASS)
	ch = re_string_fetch_byte_case (regexp);
      else
	ch = re_string_fetch_byte (regexp);
      if (re_string_eoi(regexp))
	return REG_EBRACK;
      if (ch == delim && re_string_peek_byte (regexp, 0) == ']')
	break;
      elem->opr.name[i] = ch;
    }
  re_string_skip_bytes (regexp, 1);
  elem->opr.name[i] = '\0';
  switch (token->type)
    {
    case OP_OPEN_COLL_ELEM:
      elem->type = COLL_SYM;
      break;
    case OP_OPEN_EQUIV_CLASS:
      elem->type = EQUIV_CLASS;
      break;
    case OP_OPEN_CHAR_CLASS:
      elem->type = CHAR_CLASS;
      break;
    default:
      break;
    }
  return REG_NOERROR;
}


create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
	     re_token_type_t type)
{
  re_token_t t;
  t.type = type;
  return create_token_tree (dfa, left, right, &t);
}


calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, Idx node, bool root)
{
  reg_errcode_t err;
  Idx i;
  re_node_set eclosure;
  bool ok;
  bool incomplete = false;
  err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1);
  if (BE (err != REG_NOERROR, 0))
    return err;

  /* This indicates that we are calculating this node now.
     We reference this value to avoid infinite loop.  */
  dfa->eclosures[node].nelem = REG_MISSING;

  /* If the current node has constraints, duplicate all nodes
     since they must inherit the constraints.  */
  if (dfa->nodes[node].constraint
      && dfa->edests[node].nelem
      && !dfa->nodes[dfa->edests[node].elems[0]].duplicated)
    {
      err = duplicate_node_closure (dfa, node, node, node,
				    dfa->nodes[node].constraint);
      if (BE (err != REG_NOERROR, 0))
	return err;
    }

  /* Expand each epsilon destination nodes.  */
  if (IS_EPSILON_NODE(dfa->nodes[node].type))
    for (i = 0; i < dfa->edests[node].nelem; ++i)
      {
	re_node_set eclosure_elem;
	Idx edest = dfa->edests[node].elems[i];
	/* If calculating the epsilon closure of 'edest' is in progress,
	   return intermediate result.  */
	if (dfa->eclosures[edest].nelem == REG_MISSING)
	  {
	    incomplete = true;
	    continue;
	  }
	/* If we haven't calculated the epsilon closure of 'edest' yet,
	   calculate now. Otherwise use calculated epsilon closure.  */
	if (dfa->eclosures[edest].nelem == 0)
	  {
	    err = calc_eclosure_iter (&eclosure_elem, dfa, edest, false);
	    if (BE (err != REG_NOERROR, 0))
	      return err;
	  }
	else
	  eclosure_elem = dfa->eclosures[edest];
	/* Merge the epsilon closure of 'edest'.  */
	err = re_node_set_merge (&eclosure, &eclosure_elem);
	if (BE (err != REG_NOERROR, 0))
	  return err;
	/* If the epsilon closure of 'edest' is incomplete,
	   the epsilon closure of this node is also incomplete.  */
	if (dfa->eclosures[edest].nelem == 0)
	  {
	    incomplete = true;
	    re_node_set_free (&eclosure_elem);
	  }
      }

  /* An epsilon closure includes itself.  */
  ok = re_node_set_insert (&eclosure, node);
  if (BE (! ok, 0))
    return REG_ESPACE;
  if (incomplete && !root)
    dfa->eclosures[node].nelem = 0;
  else
    dfa->eclosures[node] = eclosure;
  *new_set = eclosure;
  return REG_NOERROR;
}


free_charset (re_charset_t *cset)
{
  re_free (cset->mbchars);
# ifdef _LIBC
  re_free (cset->coll_syms);
  re_free (cset->equiv_classes);
  re_free (cset->range_starts);
  re_free (cset->range_ends);
# endif
  re_free (cset->char_classes);
  re_free (cset);
}


build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans,
		    const char *class_name,
		    const char *extra, bool non_match,
		    reg_errcode_t *err)
{
  re_bitset_ptr_t sbcset;
#ifdef RE_ENABLE_I18N
  re_charset_t *mbcset;
  Idx alloc = 0;
#endif /* not RE_ENABLE_I18N */
  reg_errcode_t ret;
  re_token_t br_token;
  bin_tree_t *tree;

  sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
#ifdef RE_ENABLE_I18N
  mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
#endif /* RE_ENABLE_I18N */

#ifdef RE_ENABLE_I18N
  if (BE (sbcset == NULL || mbcset == NULL, 0))
#else /* not RE_ENABLE_I18N */
  if (BE (sbcset == NULL, 0))
#endif /* not RE_ENABLE_I18N */
    {
      *err = REG_ESPACE;
      return NULL;
    }

  if (non_match)
    {
#ifdef RE_ENABLE_I18N
      mbcset->non_match = 1;
#endif /* not RE_ENABLE_I18N */
    }

  /* We don't care the syntax in this case.  */
  ret = build_charclass (trans, sbcset,
#ifdef RE_ENABLE_I18N
			 mbcset, &alloc,
#endif /* RE_ENABLE_I18N */
			 class_name, 0);

  if (BE (ret != REG_NOERROR, 0))
    {
      re_free (sbcset);
#ifdef RE_ENABLE_I18N
      free_charset (mbcset);
#endif /* RE_ENABLE_I18N */
      *err = ret;
      return NULL;
    }
  /* \w match '_' also.  */
  for (; *extra; extra++)
    bitset_set (sbcset, *extra);

  /* If it is non-matching list.  */
  if (non_match)
    bitset_not (sbcset);

#ifdef RE_ENABLE_I18N
  /* Ensure only single byte characters are set.  */
  if (dfa->mb_cur_max > 1)
    bitset_mask (sbcset, dfa->sb_char);
#endif

  /* Build a tree for simple bracket.  */
  br_token.type = SIMPLE_BRACKET;
  br_token.opr.sbcset = sbcset;
  tree = create_token_tree (dfa, NULL, NULL, &br_token);
  if (BE (tree == NULL, 0))
    goto build_word_op_espace;

#ifdef RE_ENABLE_I18N
  if (dfa->mb_cur_max > 1)
    {
      bin_tree_t *mbc_tree;
      /* Build a tree for complex bracket.  */
      br_token.type = COMPLEX_BRACKET;
      br_token.opr.mbcset = mbcset;
      dfa->has_mb_node = 1;
      mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token);
      if (BE (mbc_tree == NULL, 0))
	goto build_word_op_espace;
      /* Then join them by ALT node.  */
      tree = create_tree (dfa, tree, mbc_tree, OP_ALT);
      if (BE (mbc_tree != NULL, 1))
	return tree;
    }
  else
    {
      free_charset (mbcset);
      return tree;
    }
#else /* not RE_ENABLE_I18N */
  return tree;
#endif /* not RE_ENABLE_I18N */

 build_word_op_espace:
  re_free (sbcset);
#ifdef RE_ENABLE_I18N
  free_charset (mbcset);
#endif /* RE_ENABLE_I18N */
  *err = REG_ESPACE;
  return NULL;
}


calc_next (void *extra, bin_tree_t *node)
{
  switch (node->token.type)
    {
    case OP_DUP_ASTERISK:
      node->left->next = node;
      break;
    case CONCAT:
      node->left->next = node->right->first;
      node->right->next = node->next;
      break;
    default:
      if (node->left)
	node->left->next = node->next;
      if (node->right)
	node->right->next = node->next;
      break;
    }
  return REG_NOERROR;
}


peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax)
{
  unsigned char c;
  if (re_string_eoi (input))
    {
      token->type = END_OF_RE;
      return 0;
    }
  c = re_string_peek_byte (input, 0);
  token->opr.c = c;

#ifdef RE_ENABLE_I18N
  if (input->mb_cur_max > 1 &&
      !re_string_first_byte (input, re_string_cur_idx (input)))
    {
      token->type = CHARACTER;
      return 1;
    }
#endif /* RE_ENABLE_I18N */

  if (c == '\\' && (syntax & RE_BACKSLASH_ESCAPE_IN_LISTS)
      && re_string_cur_idx (input) + 1 < re_string_length (input))
    {
      /* In this case, '\' escape a character.  */
      unsigned char c2;
      re_string_skip_bytes (input, 1);
      c2 = re_string_peek_byte (input, 0);
      token->opr.c = c2;
      token->type = CHARACTER;
      return 1;
    }
  if (c == '[') /* '[' is a special char in a bracket exps.  */
    {
      unsigned char c2;
      int token_len;
      if (re_string_cur_idx (input) + 1 < re_string_length (input))
	c2 = re_string_peek_byte (input, 1);
      else
	c2 = 0;
      token->opr.c = c2;
      token_len = 2;
      switch (c2)
	{
	case '.':
	  token->type = OP_OPEN_COLL_ELEM;
	  break;
	case '=':
	  token->type = OP_OPEN_EQUIV_CLASS;
	  break;
	case ':':
	  if (syntax & RE_CHAR_CLASSES)
	    {
	      token->type = OP_OPEN_CHAR_CLASS;
	      break;
	    }
	  /* else fall through.  */
	default:
	  token->type = CHARACTER;
	  token->opr.c = c;
	  token_len = 1;
	  break;
	}
      return token_len;
    }
  switch (c)
    {
    case '-':
      token->type = OP_CHARSET_RANGE;
      break;
    case ']':
      token->type = OP_CLOSE_BRACKET;
      break;
    case '^':
      token->type = OP_NON_MATCH_LIST;
      break;
    default:
      token->type = CHARACTER;
    }
  return 1;
}


parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax,
       reg_errcode_t *err)
{
  re_dfa_t *dfa = preg->buffer;
  bin_tree_t *tree, *eor, *root;
  re_token_t current_token;
  dfa->syntax = syntax;
  fetch_token (&current_token, regexp, syntax | RE_CARET_ANCHORS_HERE);
  tree = parse_reg_exp (regexp, preg, &current_token, syntax, 0, err);
  if (BE (*err != REG_NOERROR && tree == NULL, 0))
    return NULL;
  eor = create_tree (dfa, NULL, NULL, END_OF_RE);
  if (tree != NULL)
    root = create_tree (dfa, tree, eor, CONCAT);
  else
    root = eor;
  if (BE (eor == NULL || root == NULL, 0))
    {
      *err = REG_ESPACE;
      return NULL;
    }
  return root;
}


duplicate_node_closure (re_dfa_t *dfa, Idx top_org_node, Idx top_clone_node,
			Idx root_node, unsigned int init_constraint)
{
  Idx org_node, clone_node;
  bool ok;
  unsigned int constraint = init_constraint;
  for (org_node = top_org_node, clone_node = top_clone_node;;)
    {
      Idx org_dest, clone_dest;
      if (dfa->nodes[org_node].type == OP_BACK_REF)
	{
	  /* If the back reference epsilon-transit, its destination must
	     also have the constraint.  Then duplicate the epsilon closure
	     of the destination of the back reference, and store it in
	     edests of the back reference.  */
	  org_dest = dfa->nexts[org_node];
	  re_node_set_empty (dfa->edests + clone_node);
	  clone_dest = duplicate_node (dfa, org_dest, constraint);
	  if (BE (clone_dest == REG_MISSING, 0))
	    return REG_ESPACE;
	  dfa->nexts[clone_node] = dfa->nexts[org_node];
	  ok = re_node_set_insert (dfa->edests + clone_node, clone_dest);
	  if (BE (! ok, 0))
	    return REG_ESPACE;
	}
      else if (dfa->edests[org_node].nelem == 0)
	{
	  /* In case of the node can't epsilon-transit, don't duplicate the
	     destination and store the original destination as the
	     destination of the node.  */
	  dfa->nexts[clone_node] = dfa->nexts[org_node];
	  break;
	}
      else if (dfa->edests[org_node].nelem == 1)
	{
	  /* In case of the node can epsilon-transit, and it has only one
	     destination.  */
	  org_dest = dfa->edests[org_node].elems[0];
	  re_node_set_empty (dfa->edests + clone_node);
	  /* If the node is root_node itself, it means the epsilon closure
	     has a loop.  Then tie it to the destination of the root_node.  */
	  if (org_node == root_node && clone_node != org_node)
	    {
	      ok = re_node_set_insert (dfa->edests + clone_node, org_dest);
	      if (BE (! ok, 0))
	        return REG_ESPACE;
	      break;
	    }
	  /* In case the node has another constraint, append it.  */
	  constraint |= dfa->nodes[org_node].constraint;
	  clone_dest = duplicate_node (dfa, org_dest, constraint);
	  if (BE (clone_dest == REG_MISSING, 0))
	    return REG_ESPACE;
	  ok = re_node_set_insert (dfa->edests + clone_node, clone_dest);
	  if (BE (! ok, 0))
	    return REG_ESPACE;
	}
      else /* dfa->edests[org_node].nelem == 2 */
	{
	  /* In case of the node can epsilon-transit, and it has two
	     destinations. In the bin_tree_t and DFA, that's '|' and '*'.   */
	  org_dest = dfa->edests[org_node].elems[0];
	  re_node_set_empty (dfa->edests + clone_node);
	  /* Search for a duplicated node which satisfies the constraint.  */
	  clone_dest = search_duplicated_node (dfa, org_dest, constraint);
	  if (clone_dest == REG_MISSING)
	    {
	      /* There is no such duplicated node, create a new one.  */
	      reg_errcode_t err;
	      clone_dest = duplicate_node (dfa, org_dest, constraint);
	      if (BE (clone_dest == REG_MISSING, 0))
		return REG_ESPACE;
	      ok = re_node_set_insert (dfa->edests + clone_node, clone_dest);
	      if (BE (! ok, 0))
		return REG_ESPACE;
	      err = duplicate_node_closure (dfa, org_dest, clone_dest,
					    root_node, constraint);
	      if (BE (err != REG_NOERROR, 0))
		return err;
	    }
	  else
	    {
	      /* There is a duplicated node which satisfies the constraint,
		 use it to avoid infinite loop.  */
	      ok = re_node_set_insert (dfa->edests + clone_node, clone_dest);
	      if (BE (! ok, 0))
		return REG_ESPACE;
	    }

	  org_dest = dfa->edests[org_node].elems[1];
	  clone_dest = duplicate_node (dfa, org_dest, constraint);
	  if (BE (clone_dest == REG_MISSING, 0))
	    return REG_ESPACE;
	  ok = re_node_set_insert (dfa->edests + clone_node, clone_dest);
	  if (BE (! ok, 0))
	    return REG_ESPACE;
	}
      org_node = org_dest;
      clone_node = clone_dest;
    }
  return REG_NOERROR;
}


re_set_syntax (syntax)
    reg_syntax_t syntax;
{
  reg_syntax_t ret = re_syntax_options;

  re_syntax_options = syntax;
  return ret;
}


init_word_char (re_dfa_t *dfa)
{
  int i = 0;
  int j;
  int ch = 0;
  dfa->word_ops_used = 1;
  if (BE (dfa->map_notascii == 0, 1))
    {
      bitset_word_t bits0 = 0x00000000;
      bitset_word_t bits1 = 0x03ff0000;
      bitset_word_t bits2 = 0x87fffffe;
      bitset_word_t bits3 = 0x07fffffe;
      if (BITSET_WORD_BITS == 64)
	{
	  dfa->word_char[0] = bits1 << 31 << 1 | bits0;
	  dfa->word_char[1] = bits3 << 31 << 1 | bits2;
	  i = 2;
	}
      else if (BITSET_WORD_BITS == 32)
	{
	  dfa->word_char[0] = bits0;
	  dfa->word_char[1] = bits1;
	  dfa->word_char[2] = bits2;
	  dfa->word_char[3] = bits3;
	  i = 4;
	}
      else
        goto general_case;
      ch = 128;

      if (BE (dfa->is_utf8, 1))
	{
	  memset (&dfa->word_char[i], '\0', (SBC_MAX - ch) / 8);
	  return;
	}
    }

 general_case:
  for (; i < BITSET_WORDS; ++i)
    for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
      if (isalnum (ch) || ch == '_')
	dfa->word_char[i] |= (bitset_word_t) 1 << j;
}


parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa,
	      re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err)
{
  bin_tree_t *tree = NULL, *old_tree = NULL;
  Idx i, start, end, start_idx = re_string_cur_idx (regexp);
  re_token_t start_token = *token;

  if (token->type == OP_OPEN_DUP_NUM)
    {
      end = 0;
      start = fetch_number (regexp, token, syntax);
      if (start == REG_MISSING)
	{
	  if (token->type == CHARACTER && token->opr.c == ',')
	    start = 0; /* We treat "{,m}" as "{0,m}".  */
	  else
	    {
	      *err = REG_BADBR; /* <re>{} is invalid.  */
	      return NULL;
	    }
	}
      if (BE (start != REG_ERROR, 1))
	{
	  /* We treat "{n}" as "{n,n}".  */
	  end = ((token->type == OP_CLOSE_DUP_NUM) ? start
		 : ((token->type == CHARACTER && token->opr.c == ',')
		    ? fetch_number (regexp, token, syntax) : REG_ERROR));
	}
      if (BE (start == REG_ERROR || end == REG_ERROR, 0))
	{
	  /* Invalid sequence.  */
	  if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0))
	    {
	      if (token->type == END_OF_RE)
		*err = REG_EBRACE;
	      else
		*err = REG_BADBR;

	      return NULL;
	    }

	  /* If the syntax bit is set, rollback.  */
	  re_string_set_index (regexp, start_idx);
	  *token = start_token;
	  token->type = CHARACTER;
	  /* mb_partial and word_char bits should be already initialized by
	     peek_token.  */
	  return elem;
	}

      if (BE ((end != REG_MISSING && start > end)
	      || token->type != OP_CLOSE_DUP_NUM, 0))
	{
	  /* First number greater than second.  */
	  *err = REG_BADBR;
	  return NULL;
	}

      if (BE (RE_DUP_MAX < (end == REG_MISSING ? start : end), 0))
	{
	  *err = REG_ESIZE;
	  return NULL;
	}
    }
  else
    {
      start = (token->type == OP_DUP_PLUS) ? 1 : 0;
      end = (token->type == OP_DUP_QUESTION) ? 1 : REG_MISSING;
    }

  fetch_token (token, regexp, syntax);

  if (BE (elem == NULL, 0))
    return NULL;
  if (BE (start == 0 && end == 0, 0))
    {
      postorder (elem, free_tree, NULL);
      return NULL;
    }

  /* Extract "<re>{n,m}" to "<re><re>...<re><re>{0,<m-n>}".  */
  if (BE (start > 0, 0))
    {
      tree = elem;
      for (i = 2; i <= start; ++i)
	{
	  elem = duplicate_tree (elem, dfa);
	  tree = create_tree (dfa, tree, elem, CONCAT);
	  if (BE (elem == NULL || tree == NULL, 0))
	    goto parse_dup_op_espace;
	}

      if (start == end)
	return tree;

      /* Duplicate ELEM before it is marked optional.  */
      elem = duplicate_tree (elem, dfa);
      if (BE (elem == NULL, 0))
        goto parse_dup_op_espace;
      old_tree = tree;
    }
  else
    old_tree = NULL;

  if (elem->token.type == SUBEXP)
    {
      uintptr_t subidx = elem->token.opr.idx;
      postorder (elem, mark_opt_subexp, (void *) subidx);
    }

  tree = create_tree (dfa, elem, NULL,
		      (end == REG_MISSING ? OP_DUP_ASTERISK : OP_ALT));
  if (BE (tree == NULL, 0))
    goto parse_dup_op_espace;

/* From gnulib's "intprops.h":
   True if the arithmetic type T is signed.  */
#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))

  /* This loop is actually executed only when end != REG_MISSING,
     to rewrite <re>{0,n} as (<re>(<re>...<re>?)?)?...  We have
     already created the start+1-th copy.  */
  if (TYPE_SIGNED (Idx) || end != REG_MISSING)
    for (i = start + 2; i <= end; ++i)
      {
	elem = duplicate_tree (elem, dfa);
	tree = create_tree (dfa, tree, elem, CONCAT);
	if (BE (elem == NULL || tree == NULL, 0))
	  goto parse_dup_op_espace;

	tree = create_tree (dfa, tree, NULL, OP_ALT);
	if (BE (tree == NULL, 0))
	  goto parse_dup_op_espace;
      }

  if (old_tree)
    tree = create_tree (dfa, old_tree, tree, CONCAT);

  return tree;

 parse_dup_op_espace:
  *err = REG_ESPACE;
  return NULL;
}


re_comp (s)
     const char *s;
{
  reg_errcode_t ret;
  char *fastmap;

  if (!s)
    {
      if (!re_comp_buf.buffer)
	return gettext ("No previous regular expression");
      return 0;
    }

  if (re_comp_buf.buffer)
    {
      fastmap = re_comp_buf.fastmap;
      re_comp_buf.fastmap = NULL;
      __regfree (&re_comp_buf);
      memset (&re_comp_buf, '\0', sizeof (re_comp_buf));
      re_comp_buf.fastmap = fastmap;
    }

  if (re_comp_buf.fastmap == NULL)
    {
      re_comp_buf.fastmap = (char *) malloc (SBC_MAX);
      if (re_comp_buf.fastmap == NULL)
	return (char *) gettext (__re_error_msgid
				 + __re_error_msgid_idx[(int) REG_ESPACE]);
    }

  /* Since 're_exec' always passes NULL for the 'regs' argument, we
     don't need to initialize the pattern buffer fields which affect it.  */

  /* Match anchors at newlines.  */
  re_comp_buf.newline_anchor = 1;

  ret = re_compile_internal (&re_comp_buf, s, strlen (s), re_syntax_options);

  if (!ret)
    return NULL;

  /* Yes, we're discarding 'const' here if !HAVE_LIBINTL.  */
  return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]);
}


free_workarea_compile (regex_t *preg)
{
  re_dfa_t *dfa = preg->buffer;
  bin_tree_storage_t *storage, *next;
  for (storage = dfa->str_tree_storage; storage; storage = next)
    {
      next = storage->next;
      re_free (storage);
    }
  dfa->str_tree_storage = NULL;
  dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE;
  dfa->str_tree = NULL;
  re_free (dfa->org_indices);
  dfa->org_indices = NULL;
}


optimize_utf8 (re_dfa_t *dfa)
{
  Idx node;
  int i;
  bool mb_chars = false;
  bool has_period = false;

  for (node = 0; node < dfa->nodes_len; ++node)
    switch (dfa->nodes[node].type)
      {
      case CHARACTER:
	if (dfa->nodes[node].opr.c >= ASCII_CHARS)
	  mb_chars = true;
	break;
      case ANCHOR:
	switch (dfa->nodes[node].opr.ctx_type)
	  {
	  case LINE_FIRST:
	  case LINE_LAST:
	  case BUF_FIRST:
	  case BUF_LAST:
	    break;
	  default:
	    /* Word anchors etc. cannot be handled.  It's okay to test
	       opr.ctx_type since constraints (for all DFA nodes) are
	       created by ORing one or more opr.ctx_type values.  */
	    return;
	  }
	break;
      case OP_PERIOD:
	has_period = true;
	break;
      case OP_BACK_REF:
      case OP_ALT:
      case END_OF_RE:
      case OP_DUP_ASTERISK:
      case OP_OPEN_SUBEXP:
      case OP_CLOSE_SUBEXP:
	break;
      case COMPLEX_BRACKET:
	return;
      case SIMPLE_BRACKET:
	/* Just double check.  */
	{
	  int rshift = (ASCII_CHARS % BITSET_WORD_BITS == 0
			? 0
			: BITSET_WORD_BITS - ASCII_CHARS % BITSET_WORD_BITS);
	  for (i = ASCII_CHARS / BITSET_WORD_BITS; i < BITSET_WORDS; ++i)
	    {
	      if (dfa->nodes[node].opr.sbcset[i] >> rshift != 0)
		return;
	      rshift = 0;
	    }
	}
	break;
      default:
	abort ();
      }

  if (mb_chars || has_period)
    for (node = 0; node < dfa->nodes_len; ++node)
      {
	if (dfa->nodes[node].type == CHARACTER
	    && dfa->nodes[node].opr.c >= ASCII_CHARS)
	  dfa->nodes[node].mb_partial = 0;
	else if (dfa->nodes[node].type == OP_PERIOD)
	  dfa->nodes[node].type = OP_UTF8_PERIOD;
      }

  /* The search can be in single byte locale.  */
  dfa->mb_cur_max = 1;
  dfa->is_utf8 = 0;
  dfa->has_mb_node = dfa->nbackref > 0 || has_period;
}


parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token,
	       reg_syntax_t syntax, Idx nest, reg_errcode_t *err)
{
  re_dfa_t *dfa = preg->buffer;
  bin_tree_t *tree, *branch = NULL;
  bitset_word_t initial_bkref_map = dfa->completed_bkref_map;
  tree = parse_branch (regexp, preg, token, syntax, nest, err);
  if (BE (*err != REG_NOERROR && tree == NULL, 0))
    return NULL;

  while (token->type == OP_ALT)
    {
      fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE);
      if (token->type != OP_ALT && token->type != END_OF_RE
	  && (nest == 0 || token->type != OP_CLOSE_SUBEXP))
	{
	  bitset_word_t accumulated_bkref_map = dfa->completed_bkref_map;
	  dfa->completed_bkref_map = initial_bkref_map;
	  branch = parse_branch (regexp, preg, token, syntax, nest, err);
	  if (BE (*err != REG_NOERROR && branch == NULL, 0))
	    {
	      if (tree != NULL)
		postorder (tree, free_tree, NULL);
	      return NULL;
	    }
	  dfa->completed_bkref_map |= accumulated_bkref_map;
	}
      else
	branch = NULL;
      tree = create_tree (dfa, tree, branch, OP_ALT);
      if (BE (tree == NULL, 0))
	{
	  *err = REG_ESPACE;
	  return NULL;
	}
    }
  return tree;
}


init_dfa (re_dfa_t *dfa, size_t pat_len)
{
  __re_size_t table_size;
#ifndef _LIBC
  const char *codeset_name;
#endif
#ifdef RE_ENABLE_I18N
  size_t max_i18n_object_size = MAX (sizeof (wchar_t), sizeof (wctype_t));
#else
  size_t max_i18n_object_size = 0;
#endif
  size_t max_object_size =
    MAX (sizeof (struct re_state_table_entry),
	 MAX (sizeof (re_token_t),
	      MAX (sizeof (re_node_set),
		   MAX (sizeof (regmatch_t),
			max_i18n_object_size))));

  memset (dfa, '\0', sizeof (re_dfa_t));

  /* Force allocation of str_tree_storage the first time.  */
  dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE;

  /* Avoid overflows.  The extra "/ 2" is for the table_size doubling
     calculation below, and for similar doubling calculations
     elsewhere.  And it's <= rather than <, because some of the
     doubling calculations add 1 afterwards.  */
  if (BE (MIN (IDX_MAX, SIZE_MAX / max_object_size) / 2 <= pat_len, 0))
    return REG_ESPACE;

  dfa->nodes_alloc = pat_len + 1;
  dfa->nodes = re_malloc (re_token_t, dfa->nodes_alloc);

  /*  table_size = 2 ^ ceil(log pat_len) */
  for (table_size = 1; ; table_size <<= 1)
    if (table_size > pat_len)
      break;

  dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size);
  dfa->state_hash_mask = table_size - 1;

  dfa->mb_cur_max = MB_CUR_MAX;
#ifdef _LIBC
  if (dfa->mb_cur_max == 6
      && strcmp (_NL_CURRENT (LC_CTYPE, _NL_CTYPE_CODESET_NAME), "UTF-8") == 0)
    dfa->is_utf8 = 1;
  dfa->map_notascii = (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_MAP_TO_NONASCII)
		       != 0);
#else
  codeset_name = nl_langinfo (CODESET);
  if ((codeset_name[0] == 'U' || codeset_name[0] == 'u')
      && (codeset_name[1] == 'T' || codeset_name[1] == 't')
      && (codeset_name[2] == 'F' || codeset_name[2] == 'f')
      && strcmp (codeset_name + 3 + (codeset_name[3] == '-'), "8") == 0)
    dfa->is_utf8 = 1;

  /* We check exhaustively in the loop below if this charset is a
     superset of ASCII.  */
  dfa->map_notascii = 0;
#endif

#ifdef RE_ENABLE_I18N
  if (dfa->mb_cur_max > 1)
    {
      if (dfa->is_utf8)
	dfa->sb_char = (re_bitset_ptr_t) utf8_sb_map;
      else
	{
	  int i, j, ch;

	  dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
	  if (BE (dfa->sb_char == NULL, 0))
	    return REG_ESPACE;

	  /* Set the bits corresponding to single byte chars.  */
	  for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
	    for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
	      {
		wint_t wch = __btowc (ch);
		if (wch != WEOF)
		  dfa->sb_char[i] |= (bitset_word_t) 1 << j;
# ifndef _LIBC
		if (isascii (ch) && wch != ch)
		  dfa->map_notascii = 1;
# endif
	      }
	}
    }
#endif

  if (BE (dfa->nodes == NULL || dfa->state_table == NULL, 0))
    return REG_ESPACE;
  return REG_NOERROR;
}


build_range_exp (const reg_syntax_t syntax,
                 bitset_t sbcset,
                 const bracket_elem_t *start_elem,
                 const bracket_elem_t *end_elem)
# endif /* not RE_ENABLE_I18N */
{
  unsigned int start_ch, end_ch;
  /* Equivalence Classes and Character Classes can't be a range start/end.  */
  if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS
	  || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS,
	  0))
    return REG_ERANGE;

  /* We can handle no multi character collating elements without libc
     support.  */
  if (BE ((start_elem->type == COLL_SYM
	   && strlen ((char *) start_elem->opr.name) > 1)
	  || (end_elem->type == COLL_SYM
	      && strlen ((char *) end_elem->opr.name) > 1), 0))
    return REG_ECOLLATE;

# ifdef RE_ENABLE_I18N
  {
    wchar_t wc;
    wint_t start_wc;
    wint_t end_wc;

    start_ch = ((start_elem->type == SB_CHAR) ? start_elem->opr.ch
		: ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0]
		   : 0));
    end_ch = ((end_elem->type == SB_CHAR) ? end_elem->opr.ch
	      : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0]
		 : 0));
    start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM)
		? __btowc (start_ch) : start_elem->opr.wch);
    end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM)
	      ? __btowc (end_ch) : end_elem->opr.wch);
    if (start_wc == WEOF || end_wc == WEOF)
      return REG_ECOLLATE;
    else if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_wc > end_wc, 0))
      return REG_ERANGE;

    /* Got valid collation sequence values, add them as a new entry.
       However, for !_LIBC we have no collation elements: if the
       character set is single byte, the single byte character set
       that we build below suffices.  parse_bracket_exp passes
       no MBCSET if dfa->mb_cur_max == 1.  */
    if (mbcset)
      {
	/* Check the space of the arrays.  */
	if (BE (*range_alloc == mbcset->nranges, 0))
	  {
	    /* There is not enough space, need realloc.  */
	    wchar_t *new_array_start, *new_array_end;
	    Idx new_nranges;

	    /* +1 in case of mbcset->nranges is 0.  */
	    new_nranges = 2 * mbcset->nranges + 1;
	    /* Use realloc since mbcset->range_starts and mbcset->range_ends
	       are NULL if *range_alloc == 0.  */
	    new_array_start = re_realloc (mbcset->range_starts, wchar_t,
					  new_nranges);
	    new_array_end = re_realloc (mbcset->range_ends, wchar_t,
					new_nranges);

	    if (BE (new_array_start == NULL || new_array_end == NULL, 0))
	      return REG_ESPACE;

	    mbcset->range_starts = new_array_start;
	    mbcset->range_ends = new_array_end;
	    *range_alloc = new_nranges;
	  }

	mbcset->range_starts[mbcset->nranges] = start_wc;
	mbcset->range_ends[mbcset->nranges++] = end_wc;
      }

    /* Build the table for single byte characters.  */
    for (wc = 0; wc < SBC_MAX; ++wc)
      {
	if (start_wc <= wc && wc <= end_wc)
	  bitset_set (sbcset, wc);
      }
  }
# else /* not RE_ENABLE_I18N */
  {
    unsigned int ch;
    start_ch = ((start_elem->type == SB_CHAR ) ? start_elem->opr.ch
		: ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0]
		   : 0));
    end_ch = ((end_elem->type == SB_CHAR ) ? end_elem->opr.ch
	      : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0]
		 : 0));
    if (start_ch > end_ch)
      return REG_ERANGE;
    /* Build the table for single byte characters.  */
    for (ch = 0; ch < SBC_MAX; ++ch)
      if (start_ch <= ch  && ch <= end_ch)
	bitset_set (sbcset, ch);
  }
# endif /* not RE_ENABLE_I18N */
  return REG_NOERROR;
}