}

void dump_hevc_track_info(GF_ISOFile *file, u32 trackNum, GF_HEVCConfig *hevccfg
#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC)
	, HEVCState *hevc_state
#endif /*GPAC_DISABLE_AV_PARSERS  && defined(GPAC_DISABLE_HEVC)*/
	)
{
#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC)
	u32 idx;
#endif
	u32 k;
	Bool non_hevc_base_layer=GF_FALSE;
	fprintf(stderr, "\t%s Info:", hevccfg->is_lhvc ? "LHVC" : "HEVC");
	if (!hevccfg->is_lhvc)
		fprintf(stderr, " Profile %s @ Level %g - Chroma Format %s\n", gf_hevc_get_profile_name(hevccfg->profile_idc), ((Double)hevccfg->level_idc) / 30.0, gf_avc_hevc_get_chroma_format_name(hevccfg->chromaFormat));
	fprintf(stderr, "\n");
	fprintf(stderr, "\tNAL Unit length bits: %d", 8*hevccfg->nal_unit_size);
	if (!hevccfg->is_lhvc)
		fprintf(stderr, " - general profile compatibility 0x%08X\n", hevccfg->general_profile_compatibility_flags);
	fprintf(stderr, "\n");
	fprintf(stderr, "\tParameter Sets: ");
	for (k=0; k<gf_list_count(hevccfg->param_array); k++) {
		GF_NALUFFParamArray *ar=gf_list_get(hevccfg->param_array, k);
		if (ar->type==GF_HEVC_NALU_SEQ_PARAM) {
			fprintf(stderr, "%d SPS ", gf_list_count(ar->nalus));
		}
		if (ar->type==GF_HEVC_NALU_PIC_PARAM) {
			fprintf(stderr, "%d PPS ", gf_list_count(ar->nalus));
		}
		if (ar->type==GF_HEVC_NALU_VID_PARAM) {
			fprintf(stderr, "%d VPS ", gf_list_count(ar->nalus));

#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC)
			for (idx=0; idx<gf_list_count(ar->nalus); idx++) {
				GF_NALUFFParam *vps = gf_list_get(ar->nalus, idx);
				s32 ps_idx=gf_hevc_read_vps(vps->data, vps->size, hevc_state);
				if (hevccfg->is_lhvc && (ps_idx>=0)) {
					non_hevc_base_layer = ! hevc_state->vps[ps_idx].base_layer_internal_flag;
				}
			}
#endif

		}
	}

	fprintf(stderr, "\n");
#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC)
	for (k=0; k<gf_list_count(hevccfg->param_array); k++) {
		GF_NALUFFParamArray *ar=gf_list_get(hevccfg->param_array, k);
		u32 width, height;
		s32 par_n, par_d;

		if (ar->type !=GF_HEVC_NALU_SEQ_PARAM) continue;
		for (idx=0; idx<gf_list_count(ar->nalus); idx++) {
			GF_Err e;
			GF_NALUFFParam *sps = gf_list_get(ar->nalus, idx);
			par_n = par_d = -1;
			e = gf_hevc_get_sps_info_with_state(hevc_state, sps->data, sps->size, NULL, &width, &height, &par_n, &par_d);
			if (e==GF_OK) {
				fprintf(stderr, "\tSPS resolution %dx%d", width, height);
				if ((par_n>0) && (par_d>0)) {
					u32 tw, th;
					gf_isom_get_track_layout_info(file, trackNum, &tw, &th, NULL, NULL, NULL);
					fprintf(stderr, " - Pixel Aspect Ratio %d:%d - Indicated track size %d x %d", par_n, par_d, tw, th);
				}
				fprintf(stderr, "\n");
			} else {
				M4_LOG(GF_LOG_ERROR, ("Failed to read SPS: %s\n\n", gf_error_to_string(e) ));
			}
		}
	}
#endif
	if (!hevccfg->is_lhvc)
		fprintf(stderr, "\tBit Depth luma %d - Chroma %d - %d temporal layers\n", hevccfg->luma_bit_depth, hevccfg->chroma_bit_depth, hevccfg->numTemporalLayers);
	else
		fprintf(stderr, "\t%d temporal layers\n", hevccfg->numTemporalLayers);
	if (hevccfg->is_lhvc) {
		fprintf(stderr, "\t%sHEVC base layer - Complete representation %d\n", non_hevc_base_layer ? "Non-" : "", hevccfg->complete_representation);
	}

	for (k=0; k<gf_list_count(hevccfg->param_array); k++) {
		GF_NALUFFParamArray *ar=gf_list_get(hevccfg->param_array, k);
		if (ar->type==GF_HEVC_NALU_SEQ_PARAM) print_config_hash(ar->nalus, "SPS");
		else if (ar->type==GF_HEVC_NALU_PIC_PARAM) print_config_hash(ar->nalus, "PPS");
		else if (ar->type==GF_HEVC_NALU_VID_PARAM) print_config_hash(ar->nalus, "VPS");


static void dump_stats(FILE *dump, const GF_SceneStatistics *stats)
{
	u32 i;
	s32 created, count, draw_created, draw_count, deleted, draw_deleted;
	created = count = draw_created = draw_count = deleted = draw_deleted = 0;

	fprintf(dump, "<NodeStatistics>\n");
	fprintf(dump, "<General NumberOfNodeTypes=\"%d\"/>\n", gf_list_count(stats->node_stats));
	for (i=0; i<gf_list_count(stats->node_stats); i++) {
		GF_NodeStats *ptr = gf_list_get(stats->node_stats, i);
		fprintf(dump, "<NodeStat NodeName=\"%s\">\n", ptr->name);

		switch (ptr->tag) {
#ifndef GPAC_DISABLE_VRML
		case TAG_MPEG4_Bitmap:
		case TAG_MPEG4_Background2D:
		case TAG_MPEG4_Background:
		case TAG_MPEG4_Box:
		case TAG_MPEG4_Circle:
		case TAG_MPEG4_CompositeTexture2D:
		case TAG_MPEG4_CompositeTexture3D:
		case TAG_MPEG4_Cylinder:
		case TAG_MPEG4_Cone:
		case TAG_MPEG4_Curve2D:
		case TAG_MPEG4_Extrusion:
		case TAG_MPEG4_ElevationGrid:
		case TAG_MPEG4_IndexedFaceSet2D:
		case TAG_MPEG4_IndexedFaceSet:
		case TAG_MPEG4_IndexedLineSet2D:
		case TAG_MPEG4_IndexedLineSet:
		case TAG_MPEG4_PointSet2D:
		case TAG_MPEG4_PointSet:
		case TAG_MPEG4_Rectangle:
		case TAG_MPEG4_Sphere:
		case TAG_MPEG4_Text:
		case TAG_MPEG4_Ellipse:
		case TAG_MPEG4_XCurve2D:
			draw_count += ptr->nb_created + ptr->nb_used - ptr->nb_del;
			draw_deleted += ptr->nb_del;
			draw_created += ptr->nb_created;
			break;
#endif /*GPAC_DISABLE_VRML*/
		}
		fprintf(dump, "<Instanciation NbObjects=\"%d\" NbUse=\"%d\" NbDestroy=\"%d\"/>\n", ptr->nb_created, ptr->nb_used, ptr->nb_del);
		count += ptr->nb_created + ptr->nb_used;
		deleted += ptr->nb_del;
		created += ptr->nb_created;
		fprintf(dump, "</NodeStat>\n");
	}
	if (i) {
		fprintf(dump, "<CumulatedStat TotalNumberOfNodes=\"%d\" ReallyAllocatedNodes=\"%d\" DeletedNodes=\"%d\" NumberOfAttributes=\"%d\"/>\n", count, created, deleted, stats->nb_svg_attributes);
		fprintf(dump, "<DrawableNodesCumulatedStat TotalNumberOfNodes=\"%d\" ReallyAllocatedNodes=\"%d\" DeletedNodes=\"%d\"/>\n", draw_count, draw_created, draw_deleted);
	}
	fprintf(dump, "</NodeStatistics>\n");

	created = count = deleted = 0;
	if (gf_list_count(stats->proto_stats)) {
		fprintf(dump, "<ProtoStatistics NumberOfProtoUsed=\"%d\">\n", gf_list_count(stats->proto_stats));
		for (i=0; i<gf_list_count(stats->proto_stats); i++) {
			GF_NodeStats *ptr = gf_list_get(stats->proto_stats, i);
			fprintf(dump, "<ProtoStat ProtoName=\"%s\">\n", ptr->name);
			fprintf(dump, "<Instanciation NbObjects=\"%d\" NbUse=\"%d\" NbDestroy=\"%d\"/>\n", ptr->nb_created, ptr->nb_used, ptr->nb_del);
			count += ptr->nb_created + ptr->nb_used;
			deleted += ptr->nb_del;
			created += ptr->nb_created;
			fprintf(dump, "</ProtoStat>\n");
		}
		if (i) fprintf(dump, "<CumulatedStat TotalNumberOfProtos=\"%d\" ReallyAllocatedProtos=\"%d\" DeletedProtos=\"%d\"/>\n", count, created, deleted);
		fprintf(dump, "</ProtoStatistics>\n");
	}
	fprintf(dump, "<FixedValues min=\"%f\" max=\"%f\">\n", FIX2FLT( stats->min_fixed) , FIX2FLT( stats->max_fixed ));
	fprintf(dump, "<Resolutions scaleIntegerPart=\"%d\" scaleFracPart=\"%d\" coordIntegerPart=\"%d\" coordFracPart=\"%d\"/>\n", stats->scale_int_res_2d, stats->scale_frac_res_2d, stats->int_res_2d, stats->frac_res_2d);
	fprintf(dump, "</FixedValues>\n");
	fprintf(dump, "<FieldStatistic FieldType=\"MFVec2f\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\" NumRemoved=\"%d\"/>\n", stats->count_2d, stats->rem_2d);
	if (stats->count_2d) {
		fprintf(dump, "<ExtendInfo MinVec2f=\"%f %f\" MaxVec2f=\"%f %f\"/>\n", FIX2FLT( stats->min_2d.x) , FIX2FLT( stats->min_2d.y ), FIX2FLT( stats->max_2d.x ), FIX2FLT( stats->max_2d.y ) );
	}
	fprintf(dump, "</FieldStatistic>\n");

	fprintf(dump, "<FieldStatistic FieldType=\"MFVec3f\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\" NumRemoved=\"%d\"/>", stats->count_3d, stats->rem_3d);
	if (stats->count_3d) {
		fprintf(dump, "<ExtendInfo MinVec3f=\"%f %f %f\" MaxVec3f=\"%f %f %f\"/>\n", FIX2FLT( stats->min_3d.x ), FIX2FLT( stats->min_3d.y ), FIX2FLT( stats->min_3d.z ), FIX2FLT( stats->max_3d.x ), FIX2FLT( stats->max_3d.y ), FIX2FLT( stats->max_3d.z ) );
	}
	fprintf(dump, "</FieldStatistic>\n");

	fprintf(dump, "<FieldStatistic FieldType=\"MF/SFColor\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\" NumRemoved=\"%d\"/>", stats->count_color, stats->rem_color);
	fprintf(dump, "</FieldStatistic>\n");

	fprintf(dump, "<FieldStatistic FieldType=\"MF/SFFloat\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\" NumRemoved=\"%d\"/>", stats->count_float, stats->rem_float);
	fprintf(dump, "</FieldStatistic>\n");

	fprintf(dump, "<FieldStatistic FieldType=\"SFVec2f\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\"/>", stats->count_2f);
	fprintf(dump, "</FieldStatistic>\n");
	fprintf(dump, "<FieldStatistic FieldType=\"SFVec3f\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\"/>", stats->count_3f);
	fprintf(dump, "</FieldStatistic>\n");
}


u32 PrintNode(const char *name, u32 graph_type)
{
#ifdef GPAC_DISABLE_VRML
	M4_LOG(GF_LOG_ERROR, ("VRML/MPEG-4/X3D scene graph is disabled in this build of GPAC\n"));
	return 2;
#else
	const char *std_name;
	GF_Node *node;
	GF_SceneGraph *sg;
	u32 tag;
#ifndef GPAC_DISABLE_BIFS
#endif /*GPAC_DISABLE_BIFS*/
	Bool is_nodefield = 0;

	char *sep = strchr(name, '.');
	if (sep) {
		sep[0] = 0;
		is_nodefield = 1;
	}

	if (graph_type==1) {
#ifndef GPAC_DISABLE_X3D
		tag = gf_node_x3d_type_by_class_name(name);
		std_name = "X3D";
#else
		M4_LOG(GF_LOG_ERROR, ("X3D node printing is not supported (X3D support disabled)\n"));
		return 2;
#endif
	} else {
		tag = gf_node_mpeg4_type_by_class_name(name);
		std_name = "MPEG4";
	}
	if (!tag) {
		M4_LOG(GF_LOG_ERROR, ("Unknown %s node %s\n", std_name, name));
		return 2;
	}

	sg = gf_sg_new();
	node = gf_node_new(sg, tag);
	gf_node_register(node, NULL);
	name = gf_node_get_class_name(node);
	if (!node) {
		M4_LOG(GF_LOG_ERROR, ("Node %s not supported in current built\n", name));
		return 2;
	}
	do_print_node(node, sg, name, graph_type, is_nodefield, GF_FALSE);

	gf_node_unregister(node, NULL);
	gf_sg_del(sg);
#endif /*GPAC_DISABLE_VRML*/
	return 1;
}


u32 PrintBuiltInBoxes(char *argval, u32 do_cov)
{
	u32 i, count=gf_isom_get_num_supported_boxes();

	fprintf(stdout, "<Boxes>\n");
	//index 0 is our internal unknown box handler
	for (i=1; i<count; i++) {
		gf_isom_dump_supported_box(i, stdout);
        if (do_cov) {
			u32 btype = gf_isom_get_supported_box_type(i);
            GF_Box *b=gf_isom_box_new(btype);
            if (b) {
                GF_Box *c=NULL;
                gf_isom_clone_box(b, &c);
                if (c) gf_isom_box_del(c);
                gf_isom_box_del(b);
            }
        }
	}
	fprintf(stdout, "</Boxes>\n");
	return 1;
}


#endif

static void dump_isom_obu(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, Bool dump_crc)
{
#ifndef GPAC_DISABLE_AV_PARSERS
	u32 i, count, track, timescale;
	AV1State av1;
	ObuType obu_type;
	u64 obu_size;
	u32 hdr_size;
	GF_BitStream *bs;
	u32 idx;

	track = gf_isom_get_track_by_id(file, trackID);

	gf_av1_init_state(&av1);
	av1.config = gf_isom_av1_config_get(file, track, 1);
	if (!av1.config) {
		M4_LOG(GF_LOG_ERROR, ("Error: Track #%d is not AV1!\n", trackID));
		return;
	}

	count = gf_isom_get_sample_count(file, track);
	timescale = gf_isom_get_media_timescale(file, track);

	fprintf(dump, "<OBUTrack trackID=\"%d\" SampleCount=\"%d\" TimeScale=\"%d\">\n", trackID, count, timescale);

	fprintf(dump, " <OBUConfig>\n");

	for (i=0; i<gf_list_count(av1.config->obu_array); i++) {
		GF_AV1_OBUArrayEntry *obu = gf_list_get(av1.config->obu_array, i);
		bs = gf_bs_new(obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ);
		gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1);
		gf_inspect_dump_obu(dump, &av1, obu->obu, obu->obu_length, obu_type, obu_size, hdr_size, dump_crc);
		gf_bs_del(bs);
	}
	fprintf(dump, " </OBUConfig>\n");

	fprintf(dump, " <OBUSamples>\n");

	for (i=0; i<count; i++) {
		u64 dts, cts;
		u32 size;
		u8 *ptr;
		GF_ISOSample *samp = gf_isom_get_sample(file, track, i+1, NULL);
		if (!samp) {
			fprintf(dump, "<!-- Unable to fetch sample %d -->\n", i+1);
			continue;
		}
		dts = samp->DTS;
		cts = dts + (s32) samp->CTS_Offset;

		fprintf(dump, "  <Sample number=\"%d\" DTS=\""LLD"\" CTS=\""LLD"\" size=\"%d\" RAP=\"%d\" >\n", i+1, dts, cts, samp->dataLength, samp->IsRAP);
		if (cts<dts) fprintf(dump, "<!-- NEGATIVE CTS OFFSET! -->\n");

		idx = 1;
		ptr = samp->data;
		size = samp->dataLength;

		bs = gf_bs_new(ptr, size, GF_BITSTREAM_READ);
		while (size) {
			gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1);
			if (obu_size > size) {
				fprintf(dump, "   <!-- OBU number %d is corrupted: size is %d but only %d remains -->\n", idx, (u32) obu_size, size);
				break;
			}
			gf_inspect_dump_obu(dump, &av1, ptr, obu_size, obu_type, obu_size, hdr_size, dump_crc);
			ptr += obu_size;
			size -= (u32)obu_size;
			idx++;
		}
		gf_bs_del(bs);
		fprintf(dump, "  </Sample>\n");
		gf_isom_sample_del(&samp);

		fprintf(dump, "\n");
		gf_set_progress("Analysing Track OBUs", i+1, count);
	}
	fprintf(dump, " </OBUSamples>\n");
	fprintf(dump, "</OBUTrack>\n");

	if (av1.config) gf_odf_av1_cfg_del(av1.config);
	gf_av1_reset_state(&av1, GF_TRUE);


static void dump_isom_nal_ex(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, u32 dump_flags)
{
	u32 i, j, count, nb_descs, track, nalh_size, timescale, cur_extract_mode;
	s32 countRef;
	Bool is_adobe_protected = GF_FALSE;
	Bool is_cenc_protected = GF_FALSE;
	Bool is_hevc = GF_FALSE;
	Bool is_vvc = GF_FALSE;
#ifndef GPAC_DISABLE_AV_PARSERS
	AVCState *avc_state = NULL;
	HEVCState *hevc_state = NULL;
	VVCState *vvc_state = NULL;
#endif
	GF_AVCConfig *avccfg, *svccfg;
	GF_HEVCConfig *hevccfg, *lhvccfg;
	GF_VVCConfig *vvccfg;
	GF_NALUFFParam *slc;
	Bool has_svcc = GF_FALSE;

	track = gf_isom_get_track_by_id(file, trackID);

	count = gf_isom_get_sample_count(file, track);

	timescale = gf_isom_get_media_timescale(file, track);

	cur_extract_mode = gf_isom_get_nalu_extract_mode(file, track);

	nb_descs = gf_isom_get_sample_description_count(file, track);
	if (!nb_descs) {
		M4_LOG(GF_LOG_ERROR, ("Error: Track #%d has no sample description so is likely not NALU-based!\n", trackID));
		return;
	}

	fprintf(dump, "<NALUTrack trackID=\"%d\" SampleCount=\"%d\" TimeScale=\"%d\">\n", trackID, count, timescale);

#ifndef GPAC_DISABLE_AV_PARSERS

#define DUMP_ARRAY(arr, name, loc, _is_svc)\
	if (arr) {\
		fprintf(dump, "  <%sArray location=\"%s\">\n", name, loc);\
		for (i=0; i<gf_list_count(arr); i++) {\
			slc = gf_list_get(arr, i);\
			fprintf(dump, "   <NALU size=\"%d\" ", slc->size);\
			gf_inspect_dump_nalu(dump, (u8 *) slc->data, slc->size, _is_svc, is_hevc ? hevc_state : NULL, avc_state, is_vvc ? vvc_state : NULL, nalh_size, (dump_flags&1) ? GF_TRUE : GF_FALSE, GF_FALSE);\
		}\
		fprintf(dump, "  </%sArray>\n", name);\
	}\

#else

#define DUMP_ARRAY(arr, name, loc, _is_svc)\
	if (arr) {\
		fprintf(dump, "  <%sArray location=\"%s\">\n", name, loc);\
		for (i=0; i<gf_list_count(arr); i++) {\
			slc = gf_list_get(arr, i);\
			fprintf(dump, "   <NALU size=\"%d\" ", slc->size);\
			fprintf(dump, "/>\n");\
		}\
		fprintf(dump, "  </%sArray>\n", name);\
	}\

#endif

	nalh_size = 0;

	for (j=0; j<nb_descs; j++) {
		GF_AVCConfig *mvccfg;
		Bool is_svc;

		avccfg = gf_isom_avc_config_get(file, track, j+1);
		svccfg = gf_isom_svc_config_get(file, track, j+1);
		mvccfg = gf_isom_mvc_config_get(file, track, j+1);
		hevccfg = gf_isom_hevc_config_get(file, track, j+1);
		lhvccfg = gf_isom_lhvc_config_get(file, track, j+1);
		vvccfg = gf_isom_vvc_config_get(file, track, j+1);

		is_svc = (svccfg!=NULL) ? 1:0;

		if (hevccfg || lhvccfg) {
			is_hevc = 1;
#ifndef GPAC_DISABLE_AV_PARSERS
			GF_SAFEALLOC(hevc_state, HEVCState)
#endif
		} else if (vvccfg) {
			is_vvc = 1;
#ifndef GPAC_DISABLE_AV_PARSERS
			GF_SAFEALLOC(vvc_state, VVCState)
#endif
		} else if (avccfg || svccfg || mvccfg) {
#ifndef GPAC_DISABLE_AV_PARSERS
			GF_SAFEALLOC(avc_state, AVCState)
#endif
		}

		//for tile tracks the hvcC is stored in the 'tbas' track
		if (!hevccfg && gf_isom_get_reference_count(file, track, GF_ISOM_REF_TBAS)) {
			u32 tk = 0;
			gf_isom_get_reference(file, track, GF_ISOM_REF_TBAS, 1, &tk);
			hevccfg = gf_isom_hevc_config_get(file, tk, 1);
		}

		fprintf(dump, " <NALUConfig>\n");

		if (!avccfg && !svccfg && !hevccfg && !lhvccfg && !vvccfg) {
			M4_LOG(GF_LOG_ERROR, ("Error: Track #%d is not NALU or OBU based!\n", trackID));
			return;
		}

		if (avccfg) {
			nalh_size = avccfg->nal_unit_size;

			DUMP_ARRAY(avccfg->sequenceParameterSets, "AVCSPS", "avcC", is_svc);
			DUMP_ARRAY(avccfg->pictureParameterSets, "AVCPPS", "avcC", is_svc)
			DUMP_ARRAY(avccfg->sequenceParameterSetExtensions, "AVCSPSEx", "avcC", is_svc)
		}
		if (is_svc) {
			if (!nalh_size) nalh_size = svccfg->nal_unit_size;
			DUMP_ARRAY(svccfg->sequenceParameterSets, "SVCSPS", "svcC", is_svc)
			DUMP_ARRAY(svccfg->pictureParameterSets, "SVCPPS", "svcC", is_svc)
		}
		if (mvccfg) {
			if (!nalh_size) nalh_size = mvccfg->nal_unit_size;
			DUMP_ARRAY(mvccfg->sequenceParameterSets, "SVCSPS", "mvcC", is_svc)
			DUMP_ARRAY(mvccfg->pictureParameterSets, "SVCPPS", "mvcC", is_svc)
		}
		if (hevccfg) {
			u32 idx;
			nalh_size = hevccfg->nal_unit_size;
			for (idx=0; idx<gf_list_count(hevccfg->param_array); idx++) {
				GF_NALUFFParamArray *ar = gf_list_get(hevccfg->param_array, idx);
				if (ar->type==GF_HEVC_NALU_SEQ_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCSPS", "hvcC", 0)
				} else if (ar->type==GF_HEVC_NALU_PIC_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCPPS", "hvcC", 0)
				} else if (ar->type==GF_HEVC_NALU_VID_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCVPS", "hvcC", 0)
				} else {
					DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "hvcC", 0)
				}
			}
		}
		if (vvccfg) {
			u32 idx;
			nalh_size = vvccfg->nal_unit_size;
			for (idx=0; idx<gf_list_count(vvccfg->param_array); idx++) {
				GF_NALUFFParamArray *ar = gf_list_get(vvccfg->param_array, idx);
				if (ar->type==GF_VVC_NALU_SEQ_PARAM) {
					DUMP_ARRAY(ar->nalus, "VVCSPS", "vvcC", 0)
				} else if (ar->type==GF_VVC_NALU_PIC_PARAM) {
					DUMP_ARRAY(ar->nalus, "VVCPPS", "vvcC", 0)
				} else if (ar->type==GF_VVC_NALU_VID_PARAM) {
					DUMP_ARRAY(ar->nalus, "VVCVPS", "vvcC", 0)
				} else {
					DUMP_ARRAY(ar->nalus, "VVCUnknownPS", "vvcC", 0)
				}
			}
		}
		if (lhvccfg) {
			u32 idx;
			nalh_size = lhvccfg->nal_unit_size;
			for (idx=0; idx<gf_list_count(lhvccfg->param_array); idx++) {
				GF_NALUFFParamArray *ar = gf_list_get(lhvccfg->param_array, idx);
				if (ar->type==GF_HEVC_NALU_SEQ_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCSPS", "lhcC", 0)
				} else if (ar->type==GF_HEVC_NALU_PIC_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCPPS", "lhcC", 0)
				} else if (ar->type==GF_HEVC_NALU_VID_PARAM) {
					DUMP_ARRAY(ar->nalus, "HEVCVPS", "lhcC", 0)
				} else {
					DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "lhcC", 0)
				}
			}
		}
		fprintf(dump, " </NALUConfig>\n");

		if (avccfg) gf_odf_avc_cfg_del(avccfg);
		if (svccfg) {
			gf_odf_avc_cfg_del(svccfg);
			has_svcc = GF_TRUE;
		}
		if (hevccfg) gf_odf_hevc_cfg_del(hevccfg);
		if (vvccfg) gf_odf_vvc_cfg_del(vvccfg);
		if (lhvccfg) gf_odf_hevc_cfg_del(lhvccfg);
	}

	/*fixme: for dumping encrypted track: we don't have neither avccfg nor svccfg*/
	if (!nalh_size) nalh_size = 4;

	/*for testing dependency*/
	countRef = gf_isom_get_reference_count(file, track, GF_ISOM_REF_SCAL);
	if (countRef > 0)
	{
		GF_ISOTrackID refTrackID;
		fprintf(dump, " <SCALReferences>\n");
		for (i = 1; i <= (u32) countRef; i++)
		{
			gf_isom_get_reference_ID(file, track, GF_ISOM_REF_SCAL, i, &refTrackID);
			fprintf(dump, "  <SCALReference number=\"%d\" refTrackID=\"%d\"/>\n", i, refTrackID);
		}

		fprintf(dump, " </SCALReferences>\n");
	}

	fprintf(dump, " <NALUSamples>\n");
	gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT);
	is_adobe_protected = gf_isom_is_adobe_protection_media(file, track, 1);
	is_cenc_protected = gf_isom_is_cenc_media(file, track, 1);
	for (i=0; i<count; i++) {
		u64 dts, cts;
		Bool is_rap;
		u32 size, nal_size, idx, di;
		u8 *ptr;
		GF_ISOSample *samp = gf_isom_get_sample(file, track, i+1, &di);
		if (!samp) {
			fprintf(dump, "<!-- Unable to fetch sample %d -->\n", i+1);
			continue;
		}
		dts = samp->DTS;
		cts = dts + (s32) samp->CTS_Offset;
		is_rap = samp->IsRAP;
		if (!is_rap) gf_isom_get_sample_rap_roll_info(file, track, i+1, &is_rap, NULL, NULL);

		if (dump_flags&2) {
			fprintf(dump, "  <Sample size=\"%d\" RAP=\"%d\"", samp->dataLength, is_rap);
		} else {
			fprintf(dump, "  <Sample DTS=\""LLD"\" CTS=\""LLD"\" size=\"%d\" RAP=\"%d\"", dts, cts, samp->dataLength, is_rap);
		}
		if (nb_descs>1)
			fprintf(dump, " sample_description=\"%d\"", di);
		fprintf(dump, " >\n");

		if (cts<dts) fprintf(dump, "<!-- NEGATIVE CTS OFFSET! -->\n");

		idx = 1;
		ptr = samp->data;
		size = samp->dataLength;
		if (is_adobe_protected) {
			u8 encrypted_au = ptr[0];
			if (encrypted_au) {
				fprintf(dump, "   <!-- Sample number %d is an Adobe's protected sample: can not be dumped -->\n", i+1);
				fprintf(dump, "  </Sample>\n\n");
				continue;
			}
			else {
				ptr++;
				size--;
			}
		}
		while (size) {
			nal_size = read_nal_size_hdr(ptr, nalh_size);
			ptr += nalh_size;

			if (nal_size >= UINT_MAX-nalh_size || nalh_size + nal_size > size) {
				fprintf(dump, "   <!-- NALU number %d is corrupted: size is %d but only %d remains -->\n", idx, nal_size, size);
				break;
			} else {
				fprintf(dump, "   <NALU size=\"%d\" ", nal_size);
#ifndef GPAC_DISABLE_AV_PARSERS
				Bool is_encrypted = 0;
				if (is_cenc_protected) {
					GF_Err e = gf_isom_get_sample_cenc_info(file, track, i + 1, &is_encrypted, NULL, NULL, NULL, NULL);
					if (e != GF_OK) {
						fprintf(dump, "dump_msg=\"Error %s while fetching encryption info for sample, assuming sample is encrypted\" ", gf_error_to_string(e) );
						is_encrypted = GF_TRUE;
					}
				}
				gf_inspect_dump_nalu(dump, ptr, nal_size, has_svcc ? 1 : 0, hevc_state, avc_state, vvc_state, nalh_size, dump_flags, is_encrypted);
#else
				fprintf(dump, "/>\n");
#endif
			}
			idx++;
			ptr+=nal_size;
			size -= nal_size + nalh_size;
		}
		fprintf(dump, "  </Sample>\n");
		gf_isom_sample_del(&samp);

		fprintf(dump, "\n");
		gf_set_progress("Analysing Track NALUs", i+1, count);
	}
	fprintf(dump, " </NALUSamples>\n");
	fprintf(dump, "</NALUTrack>\n");

	gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode);

#ifndef GPAC_DISABLE_AV_PARSERS
	if (hevc_state) gf_free(hevc_state);
	if (vvc_state) gf_free(vvc_state);
	if (avc_state) gf_free(avc_state);


}

static void dump_qt_prores(GF_ISOFile *file, u32 trackID, FILE *dump, Bool dump_crc)
{
#ifndef GPAC_DISABLE_AV_PARSERS
	u32 i, count, track, timescale;

	track = gf_isom_get_track_by_id(file, trackID);

	count = gf_isom_get_sample_count(file, track);
	timescale = gf_isom_get_media_timescale(file, track);

	fprintf(dump, "<ProResTrack trackID=\"%d\" SampleCount=\"%d\" TimeScale=\"%d\">\n", trackID, count, timescale);

	for (i=0; i<count; i++) {
		void gf_inspect_dump_prores(FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc);
		u64 dts, cts;
		GF_ISOSample *samp = gf_isom_get_sample(file, track, i+1, NULL);
		if (!samp) {
			fprintf(dump, "<!-- Unable to fetch sample %d -->\n", i+1);
			continue;
		}
		dts = samp->DTS;
		cts = dts + (s32) samp->CTS_Offset;

		if (cts!=dts) fprintf(dump, "<!-- Wrong timing info (CTS "LLD" vs DTS "LLD") ! -->\n", cts, dts);
		if (!samp->IsRAP) fprintf(dump, "<!-- Wrong sync sample info, sample is not SAP1 ! -->\n");

		fprintf(dump, "  <Sample number=\"%d\" CTS=\""LLD"\" size=\"%d\">\n", i+1, cts, samp->dataLength);

		gf_inspect_dump_prores(dump, samp->data, samp->dataLength, dump_crc);
		fprintf(dump, "  </Sample>\n");

		gf_isom_sample_del(&samp);

		fprintf(dump, "\n");
		gf_set_progress("Analysing ProRes Track", i+1, count);
	}
	fprintf(dump, "</ProResTrack>\n");


l2tp_bearer_cap_print(netdissect_options *ndo, const u_char *dat, u_int length)
{
	const uint32_t *ptr = (const uint32_t *)dat;

	if (length < 4) {
		ND_PRINT((ndo, "AVP too short"));
		return;
	}
	if (EXTRACT_32BITS(ptr) &  L2TP_BEARER_CAP_ANALOG_MASK) {
		ND_PRINT((ndo, "A"));
	}
	if (EXTRACT_32BITS(ptr) &  L2TP_BEARER_CAP_DIGITAL_MASK) {
		ND_PRINT((ndo, "D"));
	}
}


l2tp_avp_print(netdissect_options *ndo, const u_char *dat, int length)
{
	u_int len;
	const uint16_t *ptr = (const uint16_t *)dat;
	uint16_t attr_type;
	int hidden = FALSE;

	if (length <= 0) {
		return;
	}

	ND_PRINT((ndo, " "));

	ND_TCHECK(*ptr);	/* Flags & Length */
	len = EXTRACT_16BITS(ptr) & L2TP_AVP_HDR_LEN_MASK;

	/* If it is not long enough to contain the header, we'll give up. */
	if (len < 6)
		goto trunc;

	/* If it goes past the end of the remaining length of the packet,
	   we'll give up. */
	if (len > (u_int)length)
		goto trunc;

	/* If it goes past the end of the remaining length of the captured
	   data, we'll give up. */
	ND_TCHECK2(*ptr, len);

	/*
	 * After this point, we don't need to check whether we go past
	 * the length of the captured data; however, we *do* need to
	 * check whether we go past the end of the AVP.
	 */

	if (EXTRACT_16BITS(ptr) & L2TP_AVP_HDR_FLAG_MANDATORY) {
		ND_PRINT((ndo, "*"));
	}
	if (EXTRACT_16BITS(ptr) & L2TP_AVP_HDR_FLAG_HIDDEN) {
		hidden = TRUE;
		ND_PRINT((ndo, "?"));
	}
	ptr++;

	if (EXTRACT_16BITS(ptr)) {
		/* Vendor Specific Attribute */
	        ND_PRINT((ndo, "VENDOR%04x:", EXTRACT_16BITS(ptr))); ptr++;
		ND_PRINT((ndo, "ATTR%04x", EXTRACT_16BITS(ptr))); ptr++;
		ND_PRINT((ndo, "("));
		print_octets(ndo, (const u_char *)ptr, len-6);
		ND_PRINT((ndo, ")"));
	} else {
		/* IETF-defined Attributes */
		ptr++;
		attr_type = EXTRACT_16BITS(ptr); ptr++;
		ND_PRINT((ndo, "%s", tok2str(l2tp_avp2str, "AVP-#%u", attr_type)));
		ND_PRINT((ndo, "("));
		if (hidden) {
			ND_PRINT((ndo, "???"));
		} else {
			switch (attr_type) {
			case L2TP_AVP_MSGTYPE:
				l2tp_msgtype_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_RESULT_CODE:
				l2tp_result_code_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_PROTO_VER:
				l2tp_proto_ver_print(ndo, ptr, len-6);
				break;
			case L2TP_AVP_FRAMING_CAP:
				l2tp_framing_cap_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_BEARER_CAP:
				l2tp_bearer_cap_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_TIE_BREAKER:
				if (len-6 < 8) {
					ND_PRINT((ndo, "AVP too short"));
					break;
				}
				print_octets(ndo, (const u_char *)ptr, 8);
				break;
			case L2TP_AVP_FIRM_VER:
			case L2TP_AVP_ASSND_TUN_ID:
			case L2TP_AVP_RECV_WIN_SIZE:
			case L2TP_AVP_ASSND_SESS_ID:
				if (len-6 < 2) {
					ND_PRINT((ndo, "AVP too short"));
					break;
				}
				print_16bits_val(ndo, ptr);
				break;
			case L2TP_AVP_HOST_NAME:
			case L2TP_AVP_VENDOR_NAME:
			case L2TP_AVP_CALLING_NUMBER:
			case L2TP_AVP_CALLED_NUMBER:
			case L2TP_AVP_SUB_ADDRESS:
			case L2TP_AVP_PROXY_AUTH_NAME:
			case L2TP_AVP_PRIVATE_GRP_ID:
				print_string(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_CHALLENGE:
			case L2TP_AVP_INI_RECV_LCP:
			case L2TP_AVP_LAST_SENT_LCP:
			case L2TP_AVP_LAST_RECV_LCP:
			case L2TP_AVP_PROXY_AUTH_CHAL:
			case L2TP_AVP_PROXY_AUTH_RESP:
			case L2TP_AVP_RANDOM_VECTOR:
				print_octets(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_Q931_CC:
				l2tp_q931_cc_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_CHALLENGE_RESP:
				if (len-6 < 16) {
					ND_PRINT((ndo, "AVP too short"));
					break;
				}
				print_octets(ndo, (const u_char *)ptr, 16);
				break;
			case L2TP_AVP_CALL_SER_NUM:
			case L2TP_AVP_MINIMUM_BPS:
			case L2TP_AVP_MAXIMUM_BPS:
			case L2TP_AVP_TX_CONN_SPEED:
			case L2TP_AVP_PHY_CHANNEL_ID:
			case L2TP_AVP_RX_CONN_SPEED:
				if (len-6 < 4) {
					ND_PRINT((ndo, "AVP too short"));
					break;
				}
				print_32bits_val(ndo, (const uint32_t *)ptr);
				break;
			case L2TP_AVP_BEARER_TYPE:
				l2tp_bearer_type_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_FRAMING_TYPE:
				l2tp_framing_type_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_PACKET_PROC_DELAY:
				l2tp_packet_proc_delay_print(ndo);
				break;
			case L2TP_AVP_PROXY_AUTH_TYPE:
				l2tp_proxy_auth_type_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_PROXY_AUTH_ID:
				l2tp_proxy_auth_id_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_CALL_ERRORS:
				l2tp_call_errors_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_ACCM:
				l2tp_accm_print(ndo, (const u_char *)ptr, len-6);
				break;
			case L2TP_AVP_SEQ_REQUIRED:
				break;	/* No Attribute Value */
			case L2TP_AVP_PPP_DISCON_CC:
				l2tp_ppp_discon_cc_print(ndo, (const u_char *)ptr, len-6);
				break;
			default:
				break;
			}
		}
		ND_PRINT((ndo, ")"));
	}

	l2tp_avp_print(ndo, dat+len, length-len);
	return;

 trunc:
	ND_PRINT((ndo, "|..."));
}


static struct net_bridge_mdb_entry *br_mdb_ip_get(
	struct net_bridge_mdb_htable *mdb, struct br_ip *dst)
{
	if (!mdb)
		return NULL;

	return __br_mdb_ip_get(mdb, dst, br_ip_hash(mdb, dst));
}


static int br_mdb_copy(struct net_bridge_mdb_htable *new,
		       struct net_bridge_mdb_htable *old,
		       int elasticity)
{
	struct net_bridge_mdb_entry *mp;
	struct hlist_node *p;
	int maxlen;
	int len;
	int i;

	for (i = 0; i < old->max; i++)
		hlist_for_each_entry(mp, p, &old->mhash[i], hlist[old->ver])
			hlist_add_head(&mp->hlist[new->ver],
				       &new->mhash[br_ip_hash(new, &mp->addr)]);

	if (!elasticity)
		return 0;

	maxlen = 0;
	for (i = 0; i < new->max; i++) {
		len = 0;
		hlist_for_each_entry(mp, p, &new->mhash[i], hlist[new->ver])
			len++;
		if (len > maxlen)
			maxlen = len;
	}

	return maxlen > elasticity ? -EINVAL : 0;
}


void br_multicast_disable_port(struct net_bridge_port *port)
{
	struct net_bridge *br = port->br;
	struct net_bridge_port_group *pg;
	struct hlist_node *p, *n;

	spin_lock(&br->multicast_lock);
	hlist_for_each_entry_safe(pg, p, n, &port->mglist, mglist)
		br_multicast_del_pg(br, pg);

	if (!hlist_unhashed(&port->rlist))
		hlist_del_init_rcu(&port->rlist);
	del_timer(&port->multicast_router_timer);
	del_timer(&port->multicast_query_timer);
	spin_unlock(&br->multicast_lock);
}


static void br_multicast_leave_group(struct net_bridge *br,
				     struct net_bridge_port *port,
				     struct br_ip *group)
{
	struct net_bridge_mdb_htable *mdb;
	struct net_bridge_mdb_entry *mp;
	struct net_bridge_port_group *p;
	unsigned long now;
	unsigned long time;

	spin_lock(&br->multicast_lock);
	if (!netif_running(br->dev) ||
	    (port && port->state == BR_STATE_DISABLED) ||
	    timer_pending(&br->multicast_querier_timer))
		goto out;

	mdb = mlock_dereference(br->mdb, br);
	mp = br_mdb_ip_get(mdb, group);
	if (!mp)
		goto out;

	now = jiffies;
	time = now + br->multicast_last_member_count *
		     br->multicast_last_member_interval;

	if (!port) {
		if (!hlist_unhashed(&mp->mglist) &&
		    (timer_pending(&mp->timer) ?
		     time_after(mp->timer.expires, time) :
		     try_to_del_timer_sync(&mp->timer) >= 0)) {
			mod_timer(&mp->timer, time);

			mp->queries_sent = 0;
			mod_timer(&mp->query_timer, now);
		}

		goto out;
	}

	for (p = mlock_dereference(mp->ports, br);
	     p != NULL;
	     p = mlock_dereference(p->next, br)) {
		if (p->port != port)
			continue;

		if (!hlist_unhashed(&p->mglist) &&
		    (timer_pending(&p->timer) ?
		     time_after(p->timer.expires, time) :
		     try_to_del_timer_sync(&p->timer) >= 0)) {
			mod_timer(&p->timer, time);

			p->queries_sent = 0;
			mod_timer(&p->query_timer, now);
		}

		break;
	}

out:
	spin_unlock(&br->multicast_lock);
}


int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
		     struct sk_buff *skb)
{
	BR_INPUT_SKB_CB(skb)->igmp = 0;
	BR_INPUT_SKB_CB(skb)->mrouters_only = 0;

	if (br->multicast_disabled)
		return 0;

	switch (skb->protocol) {
	case htons(ETH_P_IP):
		return br_multicast_ipv4_rcv(br, port, skb);
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	case htons(ETH_P_IPV6):
		return br_multicast_ipv6_rcv(br, port, skb);
#endif
	}

	return 0;
}


int br_multicast_toggle(struct net_bridge *br, unsigned long val)
{
	struct net_bridge_port *port;
	int err = 0;
	struct net_bridge_mdb_htable *mdb;

	spin_lock(&br->multicast_lock);
	if (br->multicast_disabled == !val)
		goto unlock;

	br->multicast_disabled = !val;
	if (br->multicast_disabled)
		goto unlock;

	if (!netif_running(br->dev))
		goto unlock;

	mdb = mlock_dereference(br->mdb, br);
	if (mdb) {
		if (mdb->old) {
			err = -EEXIST;
rollback:
			br->multicast_disabled = !!val;
			goto unlock;
		}

		err = br_mdb_rehash(&br->mdb, mdb->max,
				    br->hash_elasticity);
		if (err)
			goto rollback;
	}

	br_multicast_open(br);
	list_for_each_entry(port, &br->port_list, list) {
		if (port->state == BR_STATE_DISABLED ||
		    port->state == BR_STATE_BLOCKING)
			continue;

		__br_multicast_enable_port(port);
	}

unlock:
	spin_unlock(&br->multicast_lock);

	return err;
}


static int br_multicast_ipv4_rcv(struct net_bridge *br,
				 struct net_bridge_port *port,
				 struct sk_buff *skb)
{
	struct sk_buff *skb2 = skb;
	struct iphdr *iph;
	struct igmphdr *ih;
	unsigned len;
	unsigned offset;
	int err;

	/* We treat OOM as packet loss for now. */
	if (!pskb_may_pull(skb, sizeof(*iph)))
		return -EINVAL;

	iph = ip_hdr(skb);

	if (iph->ihl < 5 || iph->version != 4)
		return -EINVAL;

	if (!pskb_may_pull(skb, ip_hdrlen(skb)))
		return -EINVAL;

	iph = ip_hdr(skb);

	if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
		return -EINVAL;

	if (iph->protocol != IPPROTO_IGMP)
		return 0;

	len = ntohs(iph->tot_len);
	if (skb->len < len || len < ip_hdrlen(skb))
		return -EINVAL;

	if (skb->len > len) {
		skb2 = skb_clone(skb, GFP_ATOMIC);
		if (!skb2)
			return -ENOMEM;

		err = pskb_trim_rcsum(skb2, len);
		if (err)
			goto err_out;
	}

	len -= ip_hdrlen(skb2);
	offset = skb_network_offset(skb2) + ip_hdrlen(skb2);
	__skb_pull(skb2, offset);
	skb_reset_transport_header(skb2);

	err = -EINVAL;
	if (!pskb_may_pull(skb2, sizeof(*ih)))
		goto out;

	switch (skb2->ip_summed) {
	case CHECKSUM_COMPLETE:
		if (!csum_fold(skb2->csum))
			break;
		/* fall through */
	case CHECKSUM_NONE:
		skb2->csum = 0;
		if (skb_checksum_complete(skb2))
			goto out;
	}

	err = 0;

	BR_INPUT_SKB_CB(skb)->igmp = 1;
	ih = igmp_hdr(skb2);

	switch (ih->type) {
	case IGMP_HOST_MEMBERSHIP_REPORT:
	case IGMPV2_HOST_MEMBERSHIP_REPORT:
		BR_INPUT_SKB_CB(skb2)->mrouters_only = 1;
		err = br_ip4_multicast_add_group(br, port, ih->group);
		break;
	case IGMPV3_HOST_MEMBERSHIP_REPORT:
		err = br_ip4_multicast_igmp3_report(br, port, skb2);
		break;
	case IGMP_HOST_MEMBERSHIP_QUERY:
		err = br_ip4_multicast_query(br, port, skb2);
		break;
	case IGMP_HOST_LEAVE_MESSAGE:
		br_ip4_multicast_leave_group(br, port, ih->group);
		break;
	}

out:
	__skb_push(skb2, offset);
err_out:
	if (skb2 != skb)
		kfree_skb(skb2);
	return err;
}


void br_multicast_stop(struct net_bridge *br)
{
	struct net_bridge_mdb_htable *mdb;
	struct net_bridge_mdb_entry *mp;
	struct hlist_node *p, *n;
	u32 ver;
	int i;

	del_timer_sync(&br->multicast_router_timer);
	del_timer_sync(&br->multicast_querier_timer);
	del_timer_sync(&br->multicast_query_timer);

	spin_lock_bh(&br->multicast_lock);
	mdb = mlock_dereference(br->mdb, br);
	if (!mdb)
		goto out;

	br->mdb = NULL;

	ver = mdb->ver;
	for (i = 0; i < mdb->max; i++) {
		hlist_for_each_entry_safe(mp, p, n, &mdb->mhash[i],
					  hlist[ver]) {
			del_timer(&mp->timer);
			del_timer(&mp->query_timer);
			call_rcu_bh(&mp->rcu, br_multicast_free_group);
		}
	}

	if (mdb->old) {
		spin_unlock_bh(&br->multicast_lock);
		rcu_barrier_bh();
		spin_lock_bh(&br->multicast_lock);
		WARN_ON(mdb->old);
	}

	mdb->old = mdb;
	call_rcu_bh(&mdb->rcu, br_mdb_free);

out:
	spin_unlock_bh(&br->multicast_lock);
}


static int br_ip6_multicast_query(struct net_bridge *br,
				  struct net_bridge_port *port,
				  struct sk_buff *skb)
{
	struct ipv6hdr *ip6h = ipv6_hdr(skb);
	struct mld_msg *mld = (struct mld_msg *) icmp6_hdr(skb);
	struct net_bridge_mdb_entry *mp;
	struct mld2_query *mld2q;
	struct net_bridge_port_group *p;
	struct net_bridge_port_group __rcu **pp;
	unsigned long max_delay;
	unsigned long now = jiffies;
	struct in6_addr *group = NULL;
	int err = 0;

	spin_lock(&br->multicast_lock);
	if (!netif_running(br->dev) ||
	    (port && port->state == BR_STATE_DISABLED))
		goto out;

	br_multicast_query_received(br, port, !ipv6_addr_any(&ip6h->saddr));

	if (skb->len == sizeof(*mld)) {
		if (!pskb_may_pull(skb, sizeof(*mld))) {
			err = -EINVAL;
			goto out;
		}
		mld = (struct mld_msg *) icmp6_hdr(skb);
		max_delay = msecs_to_jiffies(htons(mld->mld_maxdelay));
		if (max_delay)
			group = &mld->mld_mca;
	} else if (skb->len >= sizeof(*mld2q)) {
		if (!pskb_may_pull(skb, sizeof(*mld2q))) {
			err = -EINVAL;
			goto out;
		}
		mld2q = (struct mld2_query *)icmp6_hdr(skb);
		if (!mld2q->mld2q_nsrcs)
			group = &mld2q->mld2q_mca;
		max_delay = mld2q->mld2q_mrc ? MLDV2_MRC(mld2q->mld2q_mrc) : 1;
	}

	if (!group)
		goto out;

	mp = br_mdb_ip6_get(mlock_dereference(br->mdb, br), group);
	if (!mp)
		goto out;

	max_delay *= br->multicast_last_member_count;
	if (!hlist_unhashed(&mp->mglist) &&
	    (timer_pending(&mp->timer) ?
	     time_after(mp->timer.expires, now + max_delay) :
	     try_to_del_timer_sync(&mp->timer) >= 0))
		mod_timer(&mp->timer, now + max_delay);

	for (pp = &mp->ports;
	     (p = mlock_dereference(*pp, br)) != NULL;
	     pp = &p->next) {
		if (timer_pending(&p->timer) ?
		    time_after(p->timer.expires, now + max_delay) :
		    try_to_del_timer_sync(&p->timer) >= 0)
			mod_timer(&mp->timer, now + max_delay);
	}

out:
	spin_unlock(&br->multicast_lock);
	return err;
}


void TfLiteTensorFree(TfLiteTensor* t) {
  TfLiteTensorDataFree(t);
  if (t->dims) TfLiteIntArrayFree(t->dims);
  t->dims = NULL;

  if (t->dims_signature) {
    TfLiteIntArrayFree((TfLiteIntArray *) t->dims_signature);
  }
  t->dims_signature = NULL;

  TfLiteQuantizationFree(&t->quantization);
  TfLiteSparsityFree(t->sparsity);
  t->sparsity = NULL;
}


static void ReorderAUContext(GF_List *sample_list, GF_AUContext *au, Bool lsr_dump)
{
	u64 autime, time;
	u32 i;
	Bool has_base;
	GF_AUContext *ptr;

	/*
		this happens when converting from bt to xmt
		NOTE: Comment is wrong? this happens when just loading BT
	*/
	if (!au->timing_sec) {
		au->timing_sec = (Double) (s64) au->timing;
		/* Hack to avoid timescale=0 which happens when loading a BT with no SLConfig*/
		if (!au->owner->timeScale) au->owner->timeScale = 1000;
		au->timing_sec /= au->owner->timeScale;
	}
	/*this happens when converting from xmt to bt*/
	if (!au->timing) {
		assert(au->owner->timeScale);
		au->timing = (u64) (au->timing_sec * au->owner->timeScale);
	}

	autime = au->timing + au->owner->imp_exp_time;
	has_base = 0;
	i=0;
	while ((ptr = (GF_AUContext*)gf_list_enum(sample_list, &i))) {
		time = ptr->timing + ptr->owner->imp_exp_time;
		if (
		    /*time ordered*/
		    (time > autime)
		    /*set bifs first for first AU*/
		    || (!has_base && (time == autime) && (ptr->owner->streamType < au->owner->streamType) )
		    /*set OD first for laser*/
		    || (lsr_dump && (au->owner->streamType==GF_STREAM_OD))
		) {
			gf_list_insert(sample_list, au, i-1);
			return;
		}

		has_base = 0;
		if ( (ptr->owner->streamType == au->owner->streamType) && (time == autime) ) has_base = 1;
	}
	gf_list_add(sample_list, au);
}


static GF_Err DumpLSRAddReplaceInsert(GF_SceneDumper *sdump, GF_Command *com)
{
	char szID[100];
	Bool is_text = 0;
	GF_CommandField *f;
	char *lsrns = sd_get_lsr_namespace(com->in_scene);

	const char *com_name = (com->tag==GF_SG_LSR_REPLACE) ? "Replace" : ( (com->tag==GF_SG_LSR_ADD) ? "Add" : "Insert" );

	DUMP_IND(sdump);

	gf_fprintf(sdump->trace, "<%s%s ref=\"%s\" ", lsrns, com_name, lsr_format_node_id(com->node, com->RouteID, szID));
	f = (GF_CommandField *) gf_list_get(com->command_fields, 0);
	if (f && (f->pos>=0) ) gf_fprintf(sdump->trace, "index=\"%d\" ", f->pos);
	if (f) {
		GF_FieldInfo info;
		if (!f->new_node && !f->node_list) {
			char *att_name = NULL;
			if (f->fieldType==SVG_Transform_Scale_datatype) att_name = "scale";
			else if (f->fieldType==SVG_Transform_Rotate_datatype) att_name = "rotation";
			else if (f->fieldType==SVG_Transform_Translate_datatype) att_name = "translation";
			else if (f->fieldIndex==(u32) -1) att_name = "textContent";
			else {
				if (!com->node) return GF_NON_COMPLIANT_BITSTREAM;
				att_name = (char*) gf_svg_get_attribute_name(com->node, f->fieldIndex);
			}

			gf_fprintf(sdump->trace, "attributeName=\"%s\" ", att_name);
			if (f->field_ptr) {
				char *att;
				info.far_ptr = f->field_ptr;
				info.fieldIndex = f->fieldIndex;
				info.fieldType = f->fieldType;
				info.name = att_name;

				if ((s32) f->pos >= 0) {
					att = gf_svg_dump_attribute_indexed(com->node, &info);
				} else {
					att = gf_svg_dump_attribute(com->node, &info);
				}
				gf_fprintf(sdump->trace, "value=\"%s\" ", att ? att : "");
				if (att) gf_free(att);
			}

			if (com->fromNodeID) {
				GF_FieldInfo op_info;
				GF_Node *op = gf_sg_find_node(sdump->sg, com->fromNodeID);
				gf_fprintf(sdump->trace, "operandElementId=\"%s\" ", lsr_format_node_id(op, com->RouteID, szID));
				gf_node_get_field(op, com->fromFieldIndex, &op_info);
				gf_fprintf(sdump->trace, "operandAttributeName=\"%s\" ", op_info.name);
			}

			gf_fprintf(sdump->trace, "/>\n");
			return GF_OK;
		}
		if (f->new_node && f->new_node->sgprivate->tag==TAG_DOMText) is_text = 1;
		/*if fieldIndex (eg attributeName) is set, this is children replacement*/
		if (f->fieldIndex>0)
			gf_fprintf(sdump->trace, "attributeName=\"children\" ");
	}

	gf_fprintf(sdump->trace, ">");
	if (!is_text) {
		gf_fprintf(sdump->trace, "\n");
		sdump->indent++;
	}
	if (f) {
		if (f->new_node) {
			gf_dump_svg_element(sdump, f->new_node, com->node, 0);
		} else if (f->node_list) {
			GF_ChildNodeItem *list = f->node_list;
			while (list) {
				gf_dump_svg_element(sdump, list->node, com->node, 0);
				list = list->next;
			}
		}
	}
	if (!is_text) {
		sdump->indent--;
		DUMP_IND(sdump);
	}
	gf_fprintf(sdump->trace, "</%s%s>\n", lsrns, com_name);
	return GF_OK;
}


static GF_Err DumpRouteDelete(GF_SceneDumper *sdump, GF_Command *com)
{
	DUMP_IND(sdump);
	if (sdump->XMLDump) {
		gf_fprintf(sdump->trace, "<Delete atRoute=\"");
		scene_dump_vrml_route_id(sdump, com->RouteID, com->def_name);
		gf_fprintf(sdump->trace, "\"/>\n");
	} else {
		gf_fprintf(sdump->trace, "DELETE ROUTE ");
		scene_dump_vrml_route_id(sdump, com->RouteID, com->def_name);
		gf_fprintf(sdump->trace, "\n");
	}
	return GF_OK;
}


static void EndElement(GF_SceneDumper *sdump, const char *name, Bool had_sub_el)
{
	if (!sdump->trace) return;
	if (!sdump->XMLDump) {
		DUMP_IND(sdump);
		gf_fprintf(sdump->trace, "}\n");
	} else {
		if (had_sub_el) {
			DUMP_IND(sdump);
			gf_fprintf(sdump->trace, "</%s>\n", name);
		}
	}
}


static GF_Err DumpRouteInsert(GF_SceneDumper *sdump, GF_Command *com, Bool is_scene_replace)
{
	GF_Route r;

	memset(&r, 0, sizeof(GF_Route));
	r.ID = com->RouteID;
	r.name = com->def_name;
	r.FromNode = gf_dump_find_node(sdump, com->fromNodeID);
	r.FromField.fieldIndex = com->fromFieldIndex;
	r.ToNode = gf_dump_find_node(sdump, com->toNodeID);
	r.ToField.fieldIndex = com->toFieldIndex;

	gf_list_add(sdump->inserted_routes, com);

	if (is_scene_replace) {
		gf_dump_vrml_route(sdump, &r, 0);
	} else {
		DUMP_IND(sdump);
		if (sdump->XMLDump) {
			gf_fprintf(sdump->trace, "<Insert>\n");
		} else {
			gf_fprintf(sdump->trace, "INSERT ");
		}
		gf_dump_vrml_route(sdump, &r, 2);
		if (sdump->XMLDump) gf_fprintf(sdump->trace, "</Insert>");
	}
	return GF_OK;
}


static GF_Err DumpProtos(GF_SceneDumper *sdump, GF_List *protoList)
{
#ifdef GPAC_DISABLE_VRML
	return GF_OK;
#else
	u32 i, j, count;
	GF_FieldInfo field;
	GF_Err e;
	GF_SceneGraph *prev_sg;
	GF_Proto *proto, *prev_proto;

	prev_proto = sdump->current_proto;

	i=0;
	while ((proto = (GF_Proto*)gf_list_enum(protoList, &i))) {
		sdump->current_proto = proto;

		DUMP_IND(sdump);
		if (!sdump->XMLDump) {
			gf_fprintf(sdump->trace, proto->ExternProto.count ? "EXTERNPROTO " : "PROTO ");
			gf_fprintf(sdump->trace, "%s [\n", proto->Name);
		} else {
			gf_fprintf(sdump->trace, "<ProtoDeclare name=\"%s\" protoID=\"%d\"", proto->Name, proto->ID);
			if (proto->ExternProto.count) {
				gf_fprintf(sdump->trace, " locations=\"");
				gf_dump_vrml_sffield(sdump, GF_SG_VRML_SFURL, &proto->ExternProto.vals[0], 0, NULL);
				gf_fprintf(sdump->trace, "\"");
			}
			gf_fprintf(sdump->trace, ">\n");
		}

		if (sdump->XMLDump && sdump->X3DDump) gf_fprintf(sdump->trace, "<ProtoInterface>");

		sdump->indent++;
		count = gf_list_count(proto->proto_fields);
		for (j=0; j<count; j++) {
			GF_ProtoFieldInterface *pf = (GF_ProtoFieldInterface *)gf_list_get(proto->proto_fields, j);
			field.fieldIndex = pf->ALL_index;
			field.eventType = pf->EventType;
			field.far_ptr = pf->def_value;
			field.fieldType = pf->FieldType;
			field.name = pf->FieldName;
			field.NDTtype = NDT_SFWorldNode;
			field.on_event_in = NULL;

			gf_dump_vrml_dyn_field(sdump, NULL, field, pf->QP_Type ? 1 : 0);

			if (!pf->QP_Type) continue;

			/*dump interface coding - BT/TXT extensions, not supported by any other tool*/
			sdump->indent++;
			DUMP_IND(sdump);
			if (sdump->XMLDump) {
				const char *quant_catname = "unknown";
#ifndef GPAC_DISABLE_BIFS
				switch (pf->QP_Type) {
				case QC_3DPOS: quant_catname = "position3D"; break;
				case QC_2DPOS: quant_catname = "position2D"; break;
				case QC_ORDER: quant_catname = "drawingOrder"; break;
				case QC_COLOR: quant_catname = "color"; break;
				case QC_TEXTURE_COORD: quant_catname = "textureCoordinate"; break;
				case QC_ANGLE: quant_catname = "angle"; break;
				case QC_SCALE: quant_catname = "scale"; break;
				case QC_INTERPOL_KEYS: quant_catname = "keys"; break;
				case QC_NORMALS: quant_catname = "normals"; break;
				case QC_ROTATION: quant_catname = "rotations"; break;
				case QC_SIZE_3D: quant_catname = "size3D"; break;
				case QC_SIZE_2D: quant_catname = "size2D"; break;
				case QC_LINEAR_SCALAR: quant_catname = "linear"; break;
				case QC_COORD_INDEX:quant_catname = "coordIndex"; break;
				}
#endif
				gf_fprintf(sdump->trace, "<InterfaceCodingParameters quantCategoy=\"%s\"", quant_catname);
			} else {
				gf_fprintf(sdump->trace, "{QP %d", pf->QP_Type);
			}
#ifndef GPAC_DISABLE_BIFS
			if (pf->QP_Type==QC_LINEAR_SCALAR) gf_fprintf(sdump->trace, sdump->XMLDump ? " nbBits=\"%d\"" : " nbBits %d", pf->NumBits);
			if (pf->hasMinMax) {
				switch (pf->QP_Type) {
				case QC_LINEAR_SCALAR:
				case QC_COORD_INDEX:
					if (sdump->XMLDump) {
						gf_fprintf(sdump->trace, " intMin=\"%d\" intMax=\"%d\"", *((SFInt32 *)pf->qp_min_value), *((SFInt32 *)pf->qp_max_value));
					} else {
						gf_fprintf(sdump->trace, " b {%d %d}", *((SFInt32 *)pf->qp_min_value), *((SFInt32 *)pf->qp_max_value));
					}
					break;
				default:
					if (sdump->XMLDump) {
						gf_fprintf(sdump->trace, " floatMin=\"%g\" floatMax=\"%g\"", FIX2FLT( *((SFFloat *)pf->qp_min_value) ), FIX2FLT( *((SFFloat *)pf->qp_max_value) ));
					} else {
						gf_fprintf(sdump->trace, " b {%g %g}", FIX2FLT( *((SFFloat *)pf->qp_min_value) ), FIX2FLT( *((SFFloat *)pf->qp_max_value) ) );
					}
					break;
				}
			}
#endif
			gf_fprintf(sdump->trace, sdump->XMLDump ? "/>\n" : "}\n");
			sdump->indent--;
			if (sdump->XMLDump) {
				DUMP_IND(sdump);
				gf_fprintf(sdump->trace, "</field>\n");
			}

		}

		sdump->indent--;
		DUMP_IND(sdump);
		if (!sdump->XMLDump) {
			gf_fprintf(sdump->trace, "]");
		} else if (sdump->X3DDump) gf_fprintf(sdump->trace, "</ProtoInterface>\n");

		if (proto->ExternProto.count) {
			if (!sdump->XMLDump) {
				gf_fprintf(sdump->trace, " \"");
				gf_dump_vrml_sffield(sdump, GF_SG_VRML_SFURL, &proto->ExternProto.vals[0], 0, NULL);
				gf_fprintf(sdump->trace, "\"\n\n");
			} else {
				gf_fprintf(sdump->trace, "</ProtoDeclare>\n");
			}
			continue;
		}
		if (!sdump->XMLDump) gf_fprintf(sdump->trace, " {\n");

		sdump->indent++;

		if (sdump->XMLDump && sdump->X3DDump) gf_fprintf(sdump->trace, "<ProtoBody>\n");

		e = DumpProtos(sdump, proto->sub_graph->protos);
		if (e) return e;

		/*set namespace to the proto one*/
		prev_sg = sdump->sg;
		sdump->sg = gf_sg_proto_get_graph(proto);

		count = gf_list_count(proto->node_code);
		for (j=0; j<count; j++) {
			GF_Node *n = (GF_Node*)gf_list_get(proto->node_code, j);
			gf_dump_vrml_node(sdump, n, 1, NULL);
		}
		count = gf_list_count(proto->sub_graph->Routes);
		for (j=0; j<count; j++) {
			GF_Route *r = (GF_Route *)gf_list_get(proto->sub_graph->Routes, j);
			if (r->IS_route) continue;
			gf_dump_vrml_route(sdump, r, 0);
		}

		if (sdump->XMLDump && sdump->X3DDump) gf_fprintf(sdump->trace, "</ProtoBody>\n");

		/*restore namespace*/
		sdump->sg = prev_sg;

		sdump->indent--;
		DUMP_IND(sdump);
		if (!sdump->XMLDump) {
			gf_fprintf(sdump->trace, "}\n");
		} else {
			gf_fprintf(sdump->trace, "</ProtoDeclare>\n");
		}
	}
	sdump->current_proto = prev_proto;
	return GF_OK;
#endif
}


static void gf_dump_vrml_IS_field(GF_SceneDumper *sdump, GF_Node *node, GF_FieldInfo field, Bool isScript, Bool skip_is)
{
	GF_FieldInfo pfield;

	GF_Route *r = gf_dump_vrml_get_IS(sdump, node, &field);
	if (r->FromNode) {
		pfield.fieldIndex = r->ToField.fieldIndex;
		gf_sg_proto_get_field(sdump->current_proto, NULL, &pfield);
	} else {
		pfield.fieldIndex = r->FromField.fieldIndex;
		gf_sg_proto_get_field(sdump->current_proto, NULL, &pfield);
	}

	if (!sdump->XMLDump) {
		DUMP_IND(sdump);
		if (isScript) gf_fprintf(sdump->trace, "%s %s ", gf_sg_vrml_get_event_type_name(field.eventType, sdump->X3DDump), gf_sg_vrml_get_field_type_name(field.fieldType));
		gf_fprintf(sdump->trace, "%s IS %s\n", field.name, pfield.name);
	} else {
		if (!skip_is) {
			StartElement(sdump, "IS");
			EndElementHeader(sdump, 1);
			sdump->indent++;
		}
		DUMP_IND(sdump);
		gf_fprintf(sdump->trace, "<connect nodeField=\"%s\" protoField=\"%s\"/>\n", field.name, pfield.name);
		if (!skip_is) {
			sdump->indent--;
			EndElement(sdump, "IS", 1);
		}
	}
}


void Split(string_view input, char delim, bool compress_token,
           std::vector<string_view>* output) {
  std::size_t i = 0;
  std::size_t p = 0;

  i = input.find(delim);

  while (i != string_view::npos) {
    output->emplace_back(input.substr(p, i - p));
    p = i + 1;

    if (compress_token) {
      while (p < input.size() && input[p] == delim) {
        ++p;
      }
    }

    i = input.find(delim, p);
  }

  output->emplace_back(input.substr(p, i - p));
}


void FileBody::Dump(std::ostream& os, const std::string& prefix) const {
  os << prefix << "<file: " << path_.u8string() << ">" << std::endl;
}


std::string Url::EncodeFull(string_view utf8_str) {
  return EncodeImpl(utf8_str, [](int c) -> bool {
    return !IsUnreserved(c) && !IsReserved(c);
  });
}


bool StringBody::Decompress() {
  if (!compressed_) {
    return true;  // Already decompressed.
  }

  std::string decompressed;
  if (gzip::Decompress(data_, &decompressed)) {
    data_ = std::move(decompressed);
    compressed_ = false;
    return true;
  }

  LOG_WARN("Failed to decompress the body data!");
  return false;
}


void Server::CheckDocRoot() {
  try {
    if (!fs::exists(doc_root_) || !fs::is_directory(doc_root_)) {
      LOG_ERRO("Doc root is not an existing directory!");
      return;
    }

    if (doc_root_.is_relative()) {
      doc_root_ = fs::absolute(doc_root_);
    }

    doc_root_ = fs::canonical(doc_root_);

  } catch (fs::filesystem_error& e) {
    LOG_ERRO("Doc root error: %s", e.what());
    doc_root_.clear();
  }

  LOG_INFO("Doc root: %s", doc_root_.u8string().c_str());
}


ResponsePtr Server::ServeStatic(RequestPtr request) {
  assert(request->method() == methods::kGet);

  if (doc_root_.empty()) {
    LOG_INFO("The doc root was not specified");
    return {};
  }

  std::string url_path = Url::DecodeUnsafe(request->url().path());
  fs::path sub_path = utility::TranslatePath(url_path);
  fs::path path = doc_root_ / sub_path;

  try {
    // NOTE: FileBody might throw Error::kFileError.
    auto body = std::make_shared<FileBody>(path, file_chunk_size_);

    auto response = std::make_shared<Response>(Status::kOK);

    std::string extension = path.extension().string();
    response->SetContentType(media_types::FromExtension(extension), "");

    // NOTE: Gzip compression is not supported.
    response->SetBody(body, true);

    return response;

  } catch (const Error& error) {
    LOG_ERRO("File error: %s", error.message().c_str());
    return {};
  }
}


void Server::StopWorkers() {
  LOG_INFO("Stop workers");

  // Clear/drop pending connections.
  // The connections will be closed later (see DoStop).
  // Alternatively, we can wait for the pending connections to be handled.
  if (queue_.Size() != 0) {
    LOG_INFO("Clear pending connections");
    queue_.Clear();
  }

  // Enqueue a null connection to trigger the first worker to stop.
  queue_.Push(ConnectionPtr());

  // Wait for worker threads to finish.
  for (auto& t : worker_threads_) {
    if (t.joinable()) {
      t.join();
    }
  }

  // Cleanup worker threads.
  worker_threads_.clear();

  // Clear the queue because it has a remaining null connection pushed by the
  // last worker thread.
  queue_.Clear();

  LOG_INFO("Workers stopped");
}


static gboolean avdtp_discover_resp(struct avdtp *session,
					struct discover_resp *resp, int size)
{
	int sep_count, i;
	uint8_t getcap_cmd;
	int ret = 0;
	gboolean getcap_pending = FALSE;

	if (session->version >= 0x0103)
		getcap_cmd = AVDTP_GET_ALL_CAPABILITIES;
	else
		getcap_cmd = AVDTP_GET_CAPABILITIES;

	sep_count = size / sizeof(struct seid_info);

	for (i = 0; i < sep_count; i++) {
		struct avdtp_remote_sep *sep;
		struct avdtp_stream *stream;
		struct seid_req req;

		DBG("seid %d type %d media %d in use %d",
				resp->seps[i].seid, resp->seps[i].type,
				resp->seps[i].media_type, resp->seps[i].inuse);

		stream = find_stream_by_rseid(session, resp->seps[i].seid);

		sep = find_remote_sep(session->seps, resp->seps[i].seid);
		if (sep && sep->type == resp->seps[i].type &&
				sep->media_type == resp->seps[i].media_type &&
				sep->codec) {
			sep->discovered = true;
			continue;
		}

		if (resp->seps[i].inuse && !stream)
			continue;

		sep = g_new0(struct avdtp_remote_sep, 1);
		session->seps = g_slist_append(session->seps, sep);

		sep->stream = stream;
		sep->seid = resp->seps[i].seid;
		sep->type = resp->seps[i].type;
		sep->media_type = resp->seps[i].media_type;
		sep->discovered = true;

		memset(&req, 0, sizeof(req));
		req.acp_seid = sep->seid;

		ret = send_request(session, TRUE, NULL, getcap_cmd,
							&req, sizeof(req));
		if (ret < 0)
			break;
		getcap_pending = TRUE;
	}

	if (!getcap_pending)
		finalize_discovery(session, -ret);

	return TRUE;
}


static gboolean avdtp_getconf_cmd(struct avdtp *session, uint8_t transaction,
					struct seid_req *req, int size)
{
	GSList *l;
	struct avdtp_local_sep *sep = NULL;
	int rsp_size;
	uint8_t err;
	uint8_t buf[1024];
	uint8_t *ptr = buf;

	if (size < (int) sizeof(struct seid_req)) {
		error("Too short getconf request");
		return FALSE;
	}

	memset(buf, 0, sizeof(buf));

	sep = find_local_sep_by_seid(session, req->acp_seid);
	if (!sep) {
		err = AVDTP_BAD_ACP_SEID;
		goto failed;
	}
	if (!sep->stream || !sep->stream->caps) {
		err = AVDTP_UNSUPPORTED_CONFIGURATION;
		goto failed;
	}

	for (l = sep->stream->caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
		struct avdtp_service_capability *cap = l->data;

		if (rsp_size + cap->length + 2 > (int) sizeof(buf))
			break;

		memcpy(ptr, cap, cap->length + 2);
		rsp_size += cap->length + 2;
		ptr += cap->length + 2;
	}

	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
				AVDTP_GET_CONFIGURATION, buf, rsp_size);

failed:
	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
				AVDTP_GET_CONFIGURATION, &err, sizeof(err));
}


unsigned int avdtp_add_state_cb(struct btd_device *dev,
				avdtp_session_state_cb cb, void *user_data)
{
	struct avdtp_state_callback *state_cb;
	static unsigned int id = 0;

	state_cb = g_new(struct avdtp_state_callback, 1);
	state_cb->cb = cb;
	state_cb->dev = dev;
	state_cb->id = ++id;
	state_cb->user_data = user_data;

	state_callbacks = g_slist_append(state_callbacks, state_cb);

	return state_cb->id;
}


static gboolean avdtp_get_capabilities_resp(struct avdtp *session,
						struct getcap_resp *resp,
						unsigned int size)
{
	struct avdtp_remote_sep *sep;
	uint8_t seid;

	/* Check for minimum required packet size includes:
	 *   1. getcap resp header
	 *   2. media transport capability (2 bytes)
	 *   3. media codec capability type + length (2 bytes)
	 *   4. the actual media codec elements
	 * */
	if (size < (sizeof(struct getcap_resp) + 4 +
				sizeof(struct avdtp_media_codec_capability))) {
		error("Too short getcap resp packet");
		return FALSE;
	}

	seid = ((struct seid_req *) session->req->data)->acp_seid;

	sep = find_remote_sep(session->seps, seid);

	DBG("seid %d type %d media %d", sep->seid,
					sep->type, sep->media_type);

	if (sep->caps) {
		g_slist_free_full(sep->caps, g_free);
		sep->caps = NULL;
		sep->codec = NULL;
		sep->delay_reporting = FALSE;
	}

	sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp),
					&sep->codec, &sep->delay_reporting);

	return TRUE;
}


static void avdtp_sep_set_state(struct avdtp *session,
				struct avdtp_local_sep *sep,
				avdtp_state_t state)
{
	struct avdtp_stream *stream = sep->stream;
	avdtp_state_t old_state;
	struct avdtp_error err, *err_ptr = NULL;
	GSList *l;

	if (!stream) {
		error("Error changing sep state: stream not available");
		return;
	}

	if (sep->state == state) {
		avdtp_error_init(&err, AVDTP_ERRNO, EIO);
		DBG("stream state change failed: %s", avdtp_strerror(&err));
		err_ptr = &err;
	} else {
		err_ptr = NULL;
		DBG("stream state changed: %s -> %s",
				avdtp_statestr(sep->state),
				avdtp_statestr(state));
	}

	old_state = sep->state;
	sep->state = state;

	switch (state) {
	case AVDTP_STATE_CONFIGURED:
		if (sep->info.type == AVDTP_SEP_TYPE_SINK)
			avdtp_delay_report(session, stream, stream->delay);
		break;
	case AVDTP_STATE_OPEN:
		stream->starting = FALSE;
		break;
	case AVDTP_STATE_STREAMING:
		if (stream->start_timer) {
			timeout_remove(stream->start_timer);
			stream->start_timer = 0;
		}
		stream->open_acp = FALSE;
		break;
	case AVDTP_STATE_CLOSING:
	case AVDTP_STATE_ABORTING:
		if (stream->start_timer) {
			timeout_remove(stream->start_timer);
			stream->start_timer = 0;
		}
		break;
	case AVDTP_STATE_IDLE:
		if (stream->start_timer) {
			timeout_remove(stream->start_timer);
			stream->start_timer = 0;
		}
		if (session->pending_open == stream)
			handle_transport_connect(session, NULL, 0, 0);
		if (session->req && session->req->stream == stream)
			handle_unanswered_req(session, stream);
		/* Remove pending commands for this stream from the queue */
		cleanup_queue(session, stream);
		session->streams = g_slist_remove(session->streams, stream);
		break;
	default:
		break;
	}

	l = stream->callbacks;
	while (l != NULL) {
		struct stream_callback *cb = l->data;
		l = g_slist_next(l);
		cb->cb(stream, old_state, state, err_ptr, cb->user_data);
	}

	if (state == AVDTP_STATE_IDLE)
		stream_free(stream);
}


static void stream_free(void *data)
{
	struct avdtp_stream *stream = data;
	struct avdtp_remote_sep *rsep;

	stream->lsep->info.inuse = 0;
	stream->lsep->stream = NULL;

	rsep = find_remote_sep(stream->session->seps, stream->rseid);
	if (rsep)
		rsep->stream = NULL;

	if (stream->timer)
		timeout_remove(stream->timer);

	if (stream->io)
		close_stream(stream);

	if (stream->io_id)
		g_source_remove(stream->io_id);

	g_slist_free_full(stream->callbacks, g_free);
	g_slist_free_full(stream->caps, g_free);

	g_free(stream);
}


static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
				uint8_t signal_id, void *buf, int size)
{
	switch (signal_id) {
	case AVDTP_DISCOVER:
		DBG("Received DISCOVER_CMD");
		return avdtp_discover_cmd(session, transaction, buf, size);
	case AVDTP_GET_CAPABILITIES:
		DBG("Received  GET_CAPABILITIES_CMD");
		return avdtp_getcap_cmd(session, transaction, buf, size,
									FALSE);
	case AVDTP_GET_ALL_CAPABILITIES:
		DBG("Received  GET_ALL_CAPABILITIES_CMD");
		return avdtp_getcap_cmd(session, transaction, buf, size, TRUE);
	case AVDTP_SET_CONFIGURATION:
		DBG("Received SET_CONFIGURATION_CMD");
		return avdtp_setconf_cmd(session, transaction, buf, size);
	case AVDTP_GET_CONFIGURATION:
		DBG("Received GET_CONFIGURATION_CMD");
		return avdtp_getconf_cmd(session, transaction, buf, size);
	case AVDTP_RECONFIGURE:
		DBG("Received RECONFIGURE_CMD");
		return avdtp_reconf_cmd(session, transaction, buf, size);
	case AVDTP_OPEN:
		DBG("Received OPEN_CMD");
		return avdtp_open_cmd(session, transaction, buf, size);
	case AVDTP_START:
		DBG("Received START_CMD");
		return avdtp_start_cmd(session, transaction, buf, size);
	case AVDTP_CLOSE:
		DBG("Received CLOSE_CMD");
		return avdtp_close_cmd(session, transaction, buf, size);
	case AVDTP_SUSPEND:
		DBG("Received SUSPEND_CMD");
		return avdtp_suspend_cmd(session, transaction, buf, size);
	case AVDTP_ABORT:
		DBG("Received ABORT_CMD");
		return avdtp_abort_cmd(session, transaction, buf, size);
	case AVDTP_SECURITY_CONTROL:
		DBG("Received SECURITY_CONTROL_CMD");
		return avdtp_secctl_cmd(session, transaction, buf, size);
	case AVDTP_DELAY_REPORT:
		DBG("Received DELAY_REPORT_CMD");
		return avdtp_delayreport_cmd(session, transaction, buf, size);
	default:
		DBG("Received unknown request id %u", signal_id);
		return avdtp_unknown_cmd(session, transaction, signal_id);
	}
}


static uint8_t req_get_seid(struct pending_req *req)
{
	if (req->signal_id == AVDTP_DISCOVER)
		return 0;

	return ((struct seid_req *) (req->data))->acp_seid;
}


static gboolean avdtp_start_cmd(struct avdtp *session, uint8_t transaction,
				struct start_req *req, unsigned int size)
{
	struct avdtp_local_sep *sep;
	struct avdtp_stream *stream;
	struct stream_rej rej;
	struct seid *seid;
	uint8_t err, failed_seid;
	int seid_count, i;

	if (size < sizeof(struct start_req)) {
		error("Too short start request");
		return FALSE;
	}

	seid_count = 1 + size - sizeof(struct start_req);

	seid = &req->first_seid;

	for (i = 0; i < seid_count; i++, seid++) {
		failed_seid = seid->seid;

		sep = find_local_sep_by_seid(session, seid->seid);
		if (!sep || !sep->stream) {
			err = AVDTP_BAD_ACP_SEID;
			goto failed;
		}

		stream = sep->stream;

		/* Also reject start cmd if state is not open */
		if (sep->state != AVDTP_STATE_OPEN) {
			err = AVDTP_BAD_STATE;
			goto failed;
		}
		stream->starting = TRUE;

		if (sep->ind && sep->ind->start) {
			if (!sep->ind->start(session, sep, stream, &err,
						sep->user_data))
				goto failed;
		}

		avdtp_check_collision(session, AVDTP_START, stream);

		avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
	}

	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
						AVDTP_START, NULL, 0);

failed:
	DBG("Rejecting (%d)", err);
	memset(&rej, 0, sizeof(rej));
	rej.acp_seid = failed_seid;
	rej.error = err;
	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
				AVDTP_START, &rej, sizeof(rej));
}


static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
{
	struct avdtp *session = user_data;
	char address[18];
	int err_no = EIO;

	if (err) {
		err_no = err->code;
		error("%s", err->message);
		goto failed;
	}

	if (!session->io)
		session->io = g_io_channel_ref(chan);

	/* Check if kernel supports reading packet types */
	bt_io_get(chan, NULL, BT_IO_OPT_PHY, &session->phy, BT_IO_OPT_INVALID);

	bt_io_get(chan, &err,
			BT_IO_OPT_OMTU, &session->omtu,
			BT_IO_OPT_IMTU, &session->imtu,
			BT_IO_OPT_INVALID);
	if (err) {
		err_no = err->code;
		error("%s", err->message);
		goto failed;
	}

	ba2str(device_get_address(session->device), address);
	DBG("AVDTP: connected %s channel to %s",
			session->pending_open ? "transport" : "signaling",
			address);

	if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
		DBG("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu);

		session->buf = g_malloc0(MAX(session->imtu, session->omtu));
		avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED);

		if (session->io_id)
			g_source_remove(session->io_id);

		session->io_id = g_io_add_watch(chan,
						G_IO_IN | G_IO_ERR | G_IO_HUP
						| G_IO_NVAL,
						(GIOFunc) session_cb, session);

		if (session->stream_setup)
			set_disconnect_timer(session);
	} else if (session->pending_open)
		handle_transport_connect(session, chan, session->imtu,
								session->omtu);
	else
		goto failed;

	process_queue(session);

	return;

failed:
	if (session->pending_open) {
		struct avdtp_stream *stream = session->pending_open;

		handle_transport_connect(session, NULL, 0, 0);

		if (avdtp_abort(session, stream) < 0)
			avdtp_sep_set_state(session, stream->lsep,
						AVDTP_STATE_IDLE);
	} else
		connection_lost(session, err_no);
}


static void copy_seps(void *data, void *user_data)
{
	struct avdtp_local_sep *sep = data;
	struct seid_info **p = user_data;

	memcpy(*p, &sep->info, sizeof(struct seid_info));
	*p = *p + 1;
}


int avdtp_set_configuration(struct avdtp *session,
				struct avdtp_remote_sep *rsep,
				struct avdtp_local_sep *lsep,
				GSList *caps,
				struct avdtp_stream **stream)
{
	struct setconf_req *req;
	struct avdtp_stream *new_stream;
	unsigned char *ptr;
	int err, caps_len;
	struct avdtp_service_capability *cap;
	GSList *l;

	if (session->state != AVDTP_SESSION_STATE_CONNECTED)
		return -ENOTCONN;

	if (!(lsep && rsep))
		return -EINVAL;

	DBG("%p: int_seid=%u, acp_seid=%u", session,
			lsep->info.seid, rsep->seid);

	new_stream = g_new0(struct avdtp_stream, 1);
	new_stream->session = session;
	new_stream->lsep = lsep;
	new_stream->rseid = rsep->seid;

	if (rsep->delay_reporting && lsep->delay_reporting) {
		struct avdtp_service_capability *delay_reporting;

		delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
								NULL, 0);
		caps = g_slist_append(caps, delay_reporting);
		new_stream->delay_reporting = TRUE;
	}

	g_slist_foreach(caps, copy_capabilities, &new_stream->caps);

	/* Calculate total size of request */
	for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
		cap = l->data;
		caps_len += cap->length + 2;
	}

	req = g_malloc0(sizeof(struct setconf_req) + caps_len);

	req->int_seid = lsep->info.seid;
	req->acp_seid = rsep->seid;

	/* Copy the capabilities into the request */
	for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
		cap = l->data;
		memcpy(ptr, cap, cap->length + 2);
		ptr += cap->length + 2;
	}

	err = send_request(session, FALSE, new_stream,
				AVDTP_SET_CONFIGURATION, req,
				sizeof(struct setconf_req) + caps_len);
	if (err < 0)
		stream_free(new_stream);
	else {
		lsep->info.inuse = 1;
		lsep->stream = new_stream;
		rsep->stream = new_stream;
		session->streams = g_slist_append(session->streams, new_stream);
		if (stream)
			*stream = new_stream;
		session->dc_timeout = DISCONNECT_TIMEOUT;
	}

	g_free(req);

	return err;
}


struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
						struct avdtp_stream *stream)
{
	GSList *l;

	for (l = stream->session->seps; l; l = l->next) {
		struct avdtp_remote_sep *sep = l->data;

		if (sep->seid == stream->rseid)
			return sep;
	}

	return NULL;
}


static bool request_timeout(gpointer user_data)
{
	struct avdtp *session = user_data;

	cancel_request(session, ETIMEDOUT);

	return FALSE;
}


uint8_t avdtp_get_type(struct avdtp_remote_sep *sep)
{
	return sep->type;
}


static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid)
{
	GSList *l;

	for (l = seps; l != NULL; l = g_slist_next(l)) {
		struct avdtp_remote_sep *sep = l->data;

		if (sep->seid == seid)
			return sep;
	}

	return NULL;
}


static void check_suspend_collision(struct pending_req *req, uint8_t id)
{
	struct suspend_req *suspend = req->data;
	struct seid *seid = &suspend->first_seid;
	int count = 1 + req->data_size - sizeof(struct suspend_req);
	int i;

	for (i = 0; i < count; i++, seid++) {
		if (seid->seid == id) {
			req->collided = TRUE;
			return;
		}
	}
}


sec_parse_public_key(STREAM s, uint8 * modulus, uint8 * exponent)
{
	uint32 magic, modulus_len;

	in_uint32_le(s, magic);
	if (magic != SEC_RSA_MAGIC)
	{
		logger(Protocol, Error, "sec_parse_public_key(), magic (0x%x) != SEC_RSA_MAGIC",
		       magic);
		return False;
	}

	in_uint32_le(s, modulus_len);
	modulus_len -= SEC_PADDING_SIZE;
	if ((modulus_len < SEC_MODULUS_SIZE) || (modulus_len > SEC_MAX_MODULUS_SIZE))
	{
		logger(Protocol, Error,
		       "sec_parse_public_key(), invalid public key size (%u bits) from server",
		       modulus_len * 8);
		return False;
	}

	in_uint8s(s, 8);	/* modulus_bits, unknown */
	in_uint8a(s, exponent, SEC_EXPONENT_SIZE);
	in_uint8a(s, modulus, modulus_len);
	in_uint8s(s, SEC_PADDING_SIZE);
	g_server_public_key_len = modulus_len;

	return s_check(s);
}


rdpsnd_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv)
{
	long next_pending;

	if (device_open)
		current_driver->add_fds(n, rfds, wfds, tv);

	next_pending = rdpsnd_queue_next_completion();
	if (next_pending >= 0)
	{
		long cur_timeout;

		cur_timeout = tv->tv_sec * 1000000 + tv->tv_usec;
		if (cur_timeout > next_pending)
		{
			tv->tv_sec = next_pending / 1000000;
			tv->tv_usec = next_pending % 1000000;
		}
	}
}


sec_process_mcs_data(STREAM s)
{
	uint16 tag, length;
	uint8 *next_tag;
	uint8 len;

	in_uint8s(s, 21);	/* header (T.124 ConferenceCreateResponse) */
	in_uint8(s, len);
	if (len & 0x80)
		in_uint8(s, len);
	logger(Protocol, Debug, "%s()", __func__);

	while (s->p < s->end)
	{
		in_uint16_le(s, tag);
		in_uint16_le(s, length);

		if (length <= 4)
			return;

		next_tag = s->p + length - 4;

		switch (tag)
		{
			case SEC_TAG_SRV_INFO:
				logger(Protocol, Debug, "%s(), SEC_TAG_SRV_INFO", __func__);
				sec_process_srv_info(s);
				break;

			case SEC_TAG_SRV_CRYPT:
				logger(Protocol, Debug, "%s(), SEC_TAG_SRV_CRYPT", __func__);
				sec_process_crypt_info(s);
				break;

			case SEC_TAG_SRV_CHANNELS:
				logger(Protocol, Debug, "%s(), SEC_TAG_SRV_CHANNELS", __func__);
				/* FIXME: We should parse this information and
				   use it to map RDP5 channels to MCS
				   channels */
				break;

			default:
				logger(Protocol, Warning, "Unhandled response tag 0x%x", tag);
		}

		s->p = next_tag;
	}
}


cssp_read_tsrequest(STREAM token, STREAM pubkey)
{
	STREAM s;
	int length;
	int tagval;
	struct stream packet;

	s = tcp_recv(NULL, 4);

	if (s == NULL)
		return False;

	// verify ASN.1 header
	if (s->p[0] != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED))
	{
		logger(Protocol, Error,
		       "cssp_read_tsrequest(), expected BER_TAG_SEQUENCE|BER_TAG_CONSTRUCTED, got %x",
		       s->p[0]);
		return False;
	}

	// peek at first 4 bytes to get full message length
	if (s->p[1] < 0x80)
		length = s->p[1] - 2;
	else if (s->p[1] == 0x81)
		length = s->p[2] - 1;
	else if (s->p[1] == 0x82)
		length = (s->p[2] << 8) | s->p[3];
	else
		return False;

	// receive the remainings of message
	s = tcp_recv(s, length);
	packet = *s;

	// parse the response and into nego token
	if (!ber_in_header(s, &tagval, &length) ||
	    tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED))
		return False;

	// version [0]
	if (!ber_in_header(s, &tagval, &length) ||
	    tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0))
		return False;

	if (!s_check_rem(s, length))
	{
		 rdp_protocol_error("cssp_read_tsrequest(), consume of version from stream would overrun",
				    &packet);
	}
	in_uint8s(s, length);

	// negoToken [1]
	if (token)
	{
		if (!ber_in_header(s, &tagval, &length)
		    || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 1))
			return False;
		if (!ber_in_header(s, &tagval, &length)
		    || tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED))
			return False;
		if (!ber_in_header(s, &tagval, &length)
		    || tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED))
			return False;
		if (!ber_in_header(s, &tagval, &length)
		    || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0))
			return False;

		if (!ber_in_header(s, &tagval, &length) || tagval != BER_TAG_OCTET_STRING)
			return False;

		if (!s_check_rem(s, length))
		{
			rdp_protocol_error("cssp_read_tsrequest(), consume of token from stream would overrun",
					   &packet);
		}

		s_realloc(token, length);
		s_reset(token);
		out_uint8p(token, s->p, length);
		s_mark_end(token);
	}

	// pubKey [3]
	if (pubkey)
	{
		if (!ber_in_header(s, &tagval, &length)
		    || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 3))
			return False;

		if (!ber_in_header(s, &tagval, &length) || tagval != BER_TAG_OCTET_STRING)
			return False;

		pubkey->data = pubkey->p = s->p;
		pubkey->end = pubkey->data + length;
		pubkey->size = length;
	}

	return True;
}


process_triblt(STREAM s, TRIBLT_ORDER * os, uint32 present, RD_BOOL delta)
{
	RD_HBITMAP bitmap;
	BRUSH brush;

	if (present & 0x000001)
	{
		in_uint8(s, os->cache_id);
		in_uint8(s, os->colour_table);
	}

	if (present & 0x000002)
		rdp_in_coord(s, &os->x, delta);

	if (present & 0x000004)
		rdp_in_coord(s, &os->y, delta);

	if (present & 0x000008)
		rdp_in_coord(s, &os->cx, delta);

	if (present & 0x000010)
		rdp_in_coord(s, &os->cy, delta);

	if (present & 0x000020)
		in_uint8(s, os->opcode);

	if (present & 0x000040)
		rdp_in_coord(s, &os->srcx, delta);

	if (present & 0x000080)
		rdp_in_coord(s, &os->srcy, delta);

	if (present & 0x000100)
		rdp_in_colour(s, &os->bgcolour);

	if (present & 0x000200)
		rdp_in_colour(s, &os->fgcolour);

	rdp_parse_brush(s, &os->brush, present >> 10);

	if (present & 0x008000)
		in_uint16_le(s, os->cache_idx);

	if (present & 0x010000)
		in_uint16_le(s, os->unknown);

	logger(Graphics, Debug,
	       "process_triblt(), op=0x%x, x=%d, y=%d, cx=%d, cy=%d, id=%d, idx=%d, bs=%d, bg=0x%x, fg=0x%x",
	       os->opcode, os->x, os->y, os->cx, os->cy, os->cache_id, os->cache_idx,
	       os->brush.style, os->bgcolour, os->fgcolour);

	bitmap = cache_get_bitmap(os->cache_id, os->cache_idx);
	if (bitmap == NULL)
		return;

	setup_brush(&brush, &os->brush);

	ui_triblt(os->opcode, os->x, os->y, os->cx, os->cy,
		  bitmap, os->srcx, os->srcy, &brush, os->bgcolour, os->fgcolour);
}


rdpdr_process(STREAM s)
{
	uint32 handle;
	uint16 vmin;
	uint16 component;
	uint16 pakid;
	struct stream packet = *s;

	logger(Protocol, Debug, "rdpdr_process()");
	/* hexdump(s->p, s->end - s->p); */

	in_uint16(s, component);
	in_uint16(s, pakid);

	if (component == RDPDR_CTYP_CORE)
	{
		switch (pakid)
		{
			case PAKID_CORE_DEVICE_IOREQUEST:
				rdpdr_process_irp(s);
				break;

			case PAKID_CORE_SERVER_ANNOUNCE:
				/* DR_CORE_SERVER_ANNOUNCE_REQ */
				in_uint8s(s, 2);	/* skip versionMajor */
				in_uint16_le(s, vmin);	/* VersionMinor */

				in_uint32_le(s, g_client_id);	/* ClientID */

				/* g_client_id is sent back to server,
				   so lets check that we actually got
				   valid data from stream to prevent
				   that we leak back data to server */
				if (!s_check(s))
				{
					rdp_protocol_error("rdpdr_process(), consume of g_client_id from stream did overrun", &packet);
				}

				/* The RDP client is responsibility to provide a random client id
				   if server version is < 12 */
				if (vmin < 0x000c)
					g_client_id = 0x815ed39d;	/* IP address (use 127.0.0.1) 0x815ed39d */
				g_epoch++;

#if WITH_SCARD
				/*
				 * We need to release all SCARD contexts to end all
				 * current transactions and pending calls
				 */
				scard_release_all_contexts();

				/*
				 * According to [MS-RDPEFS] 3.2.5.1.2:
				 *
				 * If this packet appears after a sequence of other packets,
				 * it is a signal that the server has reconnected to a new session
				 * and the whole sequence has been reset. The client MUST treat
				 * this packet as the beginning of a new sequence.
				 * The client MUST also cancel all outstanding requests and release
				 * previous references to all devices.
				 *
				 * If any problem arises in the future, please, pay attention to the
				 * "If this packet appears after a sequence of other packets" part
				 *
				 */

#endif

				rdpdr_send_client_announce_reply();
				rdpdr_send_client_name_request();
				break;

			case PAKID_CORE_CLIENTID_CONFIRM:
				rdpdr_send_client_device_list_announce();
				break;

			case PAKID_CORE_DEVICE_REPLY:
				in_uint32(s, handle);
				logger(Protocol, Debug,
				       "rdpdr_process(), server connected to resource %d", handle);
				break;

			case PAKID_CORE_SERVER_CAPABILITY:
				rdpdr_send_client_capability_response();
				break;

			default:
				logger(Protocol, Debug,
				       "rdpdr_process(), pakid 0x%x of component 0x%x", pakid,
				       component);
				break;

		}
	}
	else if (component == RDPDR_CTYP_PRN)
	{
		if (pakid == PAKID_PRN_CACHE_DATA)
			printercache_process(s);
	}
	else
		logger(Protocol, Warning, "rdpdr_process(), unhandled component 0x%x", component);
}


rdp_main_loop(RD_BOOL * deactivated, uint32 * ext_disc_reason)
{
	do
	{
		if (rdp_loop(deactivated, ext_disc_reason) == False)
		{
			g_exit_mainloop = True;
		}
	}
	while (g_exit_mainloop == False);
}


process_colour_pointer_pdu(STREAM s)
{
	logger(Protocol, Debug, "%s()", __func__);

	process_colour_pointer_common(s, 24);
}


rdp_process_server_caps(STREAM s, uint16 length)
{
	int n;
	uint8 *next, *start;
	uint16 ncapsets, capset_type, capset_length;

	logger(Protocol, Debug, "%s()", __func__);

	start = s->p;

	in_uint16_le(s, ncapsets);
	in_uint8s(s, 2);	/* pad */

	for (n = 0; n < ncapsets; n++)
	{
		if (s->p > start + length)
			return;

		in_uint16_le(s, capset_type);
		in_uint16_le(s, capset_length);

		next = s->p + capset_length - 4;

		switch (capset_type)
		{
			case RDP_CAPSET_GENERAL:
				rdp_process_general_caps(s);
				break;

			case RDP_CAPSET_BITMAP:
				rdp_process_bitmap_caps(s);
				break;
		}

		s->p = next;
	}
}


process_demand_active(STREAM s)
{
	uint8 type;
	uint16 len_src_descriptor, len_combined_caps;
	struct stream packet = *s;

	/* at this point we need to ensure that we have ui created */
	rd_create_ui();

	in_uint32_le(s, g_rdp_shareid);
	in_uint16_le(s, len_src_descriptor);
	in_uint16_le(s, len_combined_caps);

	if (!s_check_rem(s, len_src_descriptor))
	{
		rdp_protocol_error("rdp_demand_active(), consume of source descriptor from stream would overrun", &packet);
	}
	in_uint8s(s, len_src_descriptor);

	logger(Protocol, Debug, "process_demand_active(), shareid=0x%x", g_rdp_shareid);

	rdp_process_server_caps(s, len_combined_caps);

	rdp_send_confirm_active();
	rdp_send_synchronise();
	rdp_send_control(RDP_CTL_COOPERATE);
	rdp_send_control(RDP_CTL_REQUEST_CONTROL);
	rdp_recv(&type);	/* RDP_PDU_SYNCHRONIZE */
	rdp_recv(&type);	/* RDP_CTL_COOPERATE */
	rdp_recv(&type);	/* RDP_CTL_GRANT_CONTROL */
	rdp_send_input(0, RDP_INPUT_SYNCHRONIZE, 0,
		       g_numlock_sync ? ui_get_numlock_state(read_keyboard_state()) : 0, 0);

	if (g_rdp_version >= RDP_V5)
	{
		rdp_enum_bmpcache2();
		rdp_send_fonts(3);
	}
	else
	{
		rdp_send_fonts(1);
		rdp_send_fonts(2);
	}

	rdp_recv(&type);	/* RDP_PDU_UNKNOWN 0x28 (Fonts?) */
	reset_order_state();
}


bitmap_decompress3(uint8 * output, int width, int height, uint8 * input, int size)
{
	uint8 *end = input + size;
	uint8 *prevline = NULL, *line = NULL;
	int opcode, count, offset, isfillormix, x = width;
	int lastopcode = -1, insertmix = False, bicolour = False;
	uint8 code;
	uint8 colour1[3] = {0, 0, 0}, colour2[3] = {0, 0, 0};
	uint8 mixmask, mask = 0;
	uint8 mix[3] = {0xff, 0xff, 0xff};
	int fom_mask = 0;

	while (input < end)
	{
		fom_mask = 0;
		code = CVAL(input);
		opcode = code >> 4;
		/* Handle different opcode forms */
		switch (opcode)
		{
			case 0xc:
			case 0xd:
			case 0xe:
				opcode -= 6;
				count = code & 0xf;
				offset = 16;
				break;
			case 0xf:
				opcode = code & 0xf;
				if (opcode < 9)
				{
					count = CVAL(input);
					count |= CVAL(input) << 8;
				}
				else
				{
					count = (opcode <
						 0xb) ? 8 : 1;
				}
				offset = 0;
				break;
			default:
				opcode >>= 1;
				count = code & 0x1f;
				offset = 32;
				break;
		}
		/* Handle strange cases for counts */
		if (offset != 0)
		{
			isfillormix = ((opcode == 2) || (opcode == 7));
			if (count == 0)
			{
				if (isfillormix)
					count = CVAL(input) + 1;
				else
					count = CVAL(input) + offset;
			}
			else if (isfillormix)
			{
				count <<= 3;
			}
		}
		/* Read preliminary data */
		switch (opcode)
		{
			case 0:	/* Fill */
				if ((lastopcode == opcode) && !((x == width) && (prevline == NULL)))
					insertmix = True;
				break;
			case 8:	/* Bicolour */
				colour1[0] = CVAL(input);
				colour1[1] = CVAL(input);
				colour1[2] = CVAL(input);
				colour2[0] = CVAL(input);
				colour2[1] = CVAL(input);
				colour2[2] = CVAL(input);
				break;
			case 3:	/* Colour */
				colour2[0] = CVAL(input);
				colour2[1] = CVAL(input);
				colour2[2] = CVAL(input);
				break;
			case 6:	/* SetMix/Mix */
			case 7:	/* SetMix/FillOrMix */
				mix[0] = CVAL(input);
				mix[1] = CVAL(input);
				mix[2] = CVAL(input);
				opcode -= 5;
				break;
			case 9:	/* FillOrMix_1 */
				mask = 0x03;
				opcode = 0x02;
				fom_mask = 3;
				break;
			case 0x0a:	/* FillOrMix_2 */
				mask = 0x05;
				opcode = 0x02;
				fom_mask = 5;
				break;
		}
		lastopcode = opcode;
		mixmask = 0;
		/* Output body */
		while (count > 0)
		{
			if (x >= width)
			{
				if (height <= 0)
					return False;
				x = 0;
				height--;
				prevline = line;
				line = output + height * (width * 3);
			}
			switch (opcode)
			{
				case 0:	/* Fill */
					if (insertmix)
					{
						if (prevline == NULL)
						{
							line[x * 3] = mix[0];
							line[x * 3 + 1] = mix[1];
							line[x * 3 + 2] = mix[2];
						}
						else
						{
							line[x * 3] =
							 prevline[x * 3] ^ mix[0];
							line[x * 3 + 1] =
							 prevline[x * 3 + 1] ^ mix[1];
							line[x * 3 + 2] =
							 prevline[x * 3 + 2] ^ mix[2];
						}
						insertmix = False;
						count--;
						x++;
					}
					if (prevline == NULL)
					{
						REPEAT
						(
							line[x * 3] = 0;
							line[x * 3 + 1] = 0;
							line[x * 3 + 2] = 0;
						)
					}
					else
					{
						REPEAT
						(
							line[x * 3] = prevline[x * 3];
							line[x * 3 + 1] = prevline[x * 3 + 1];
							line[x * 3 + 2] = prevline[x * 3 + 2];
						)
					}
					break;
				case 1:	/* Mix */
					if (prevline == NULL)
					{
						REPEAT
						(
							line[x * 3] = mix[0];
							line[x * 3 + 1] = mix[1];
							line[x * 3 + 2] = mix[2];
						)
					}
					else
					{
						REPEAT
						(
							line[x * 3] =
							 prevline[x * 3] ^ mix[0];
							line[x * 3 + 1] =
							 prevline[x * 3 + 1] ^ mix[1];
							line[x * 3 + 2] =
							 prevline[x * 3 + 2] ^ mix[2];
						)
					}
					break;
				case 2:	/* Fill or Mix */
					if (prevline == NULL)
					{
						REPEAT
						(
							MASK_UPDATE();
							if (mask & mixmask)
							{
								line[x * 3] = mix[0];
								line[x * 3 + 1] = mix[1];
								line[x * 3 + 2] = mix[2];
							}
							else
							{
								line[x * 3] = 0;
								line[x * 3 + 1] = 0;
								line[x * 3 + 2] = 0;
							}
						)
					}
					else
					{
						REPEAT
						(
							MASK_UPDATE();
							if (mask & mixmask)
							{
								line[x * 3] =
								 prevline[x * 3] ^ mix [0];
								line[x * 3 + 1] =
								 prevline[x * 3 + 1] ^ mix [1];
								line[x * 3 + 2] =
								 prevline[x * 3 + 2] ^ mix [2];
							}
							else
							{
								line[x * 3] =
								 prevline[x * 3];
								line[x * 3 + 1] =
								 prevline[x * 3 + 1];
								line[x * 3 + 2] =
								 prevline[x * 3 + 2];
							}
						)
					}
					break;
				case 3:	/* Colour */
					REPEAT
					(
						line[x * 3] = colour2 [0];
						line[x * 3 + 1] = colour2 [1];
						line[x * 3 + 2] = colour2 [2];
					)
					break;
				case 4:	/* Copy */
					REPEAT
					(
						line[x * 3] = CVAL(input);
						line[x * 3 + 1] = CVAL(input);
						line[x * 3 + 2] = CVAL(input);
					)
					break;
				case 8:	/* Bicolour */
					REPEAT
					(
						if (bicolour)
						{
							line[x * 3] = colour2[0];
							line[x * 3 + 1] = colour2[1];
							line[x * 3 + 2] = colour2[2];
							bicolour = False;
						}
						else
						{
							line[x * 3] = colour1[0];
							line[x * 3 + 1] = colour1[1];
							line[x * 3 + 2] = colour1[2];
							bicolour = True;
							count++;
						}
					)
					break;
				case 0xd:	/* White */
					REPEAT
					(
						line[x * 3] = 0xff;
						line[x * 3 + 1] = 0xff;
						line[x * 3 + 2] = 0xff;
					)
					break;
				case 0xe:	/* Black */
					REPEAT
					(
						line[x * 3] = 0;
						line[x * 3 + 1] = 0;
						line[x * 3 + 2] = 0;
					)
					break;
				default:
					logger(Core, Warning, "bitmap_decompress3(), unhandled bitmap opcode 0x%x", opcode);
					return False;
			}
		}
	}
	return True;
}


rdpsnd_init(char *optarg)
{
	struct audio_driver *pos;
	char *driver = NULL, *options = NULL;

	drivers = NULL;

	packet.data = (uint8 *) xmalloc(65536);
	packet.p = packet.end = packet.data;
	packet.size = 0;

	rdpsnd_channel =
		channel_register("rdpsnd", CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP,
				 rdpsnd_process);

	rdpsnddbg_channel =
		channel_register("snddbg", CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP,
				 rdpsnddbg_process);

	if ((rdpsnd_channel == NULL) || (rdpsnddbg_channel == NULL))
	{
		logger(Sound, Error,
		       "rdpsnd_init(), failed to register rdpsnd / snddbg virtual channels");
		return False;
	}

	rdpsnd_queue_init();

	if (optarg != NULL && strlen(optarg) > 0)
	{
		driver = options = optarg;

		while (*options != '\0' && *options != ':')
			options++;

		if (*options == ':')
		{
			*options = '\0';
			options++;
		}

		if (*options == '\0')
			options = NULL;
	}

	rdpsnd_register_drivers(options);

	if (!driver)
		return True;

	pos = drivers;
	while (pos != NULL)
	{
		if (!strcmp(pos->name, driver))
		{
			logger(Sound, Debug, "rdpsnd_init(), using driver '%s'", pos->name);
			current_driver = pos;
			return True;
		}
		pos = pos->next;
	}
	return False;
}


setup_brush(BRUSH * out_brush, BRUSH * in_brush)
{
	BRUSHDATA *brush_data;
	uint8 cache_idx;
	uint8 colour_code;

	memcpy(out_brush, in_brush, sizeof(BRUSH));
	if (out_brush->style & 0x80)
	{
		colour_code = out_brush->style & 0x0f;
		cache_idx = out_brush->pattern[0];
		brush_data = cache_get_brush_data(colour_code, cache_idx);
		if ((brush_data == NULL) || (brush_data->data == NULL))
		{
			logger(Graphics, Error, "setup_brush(), error getting brush data, style %x",
			       out_brush->style);
			out_brush->bd = NULL;
			memset(out_brush->pattern, 0, 8);
		}
		else
		{
			out_brush->bd = brush_data;
		}
		out_brush->style = 3;
	}
}


process_ellipse(STREAM s, ELLIPSE_ORDER * os, uint32 present, RD_BOOL delta)
{
	if (present & 0x01)
		rdp_in_coord(s, &os->left, delta);

	if (present & 0x02)
		rdp_in_coord(s, &os->top, delta);

	if (present & 0x04)
		rdp_in_coord(s, &os->right, delta);

	if (present & 0x08)
		rdp_in_coord(s, &os->bottom, delta);

	if (present & 0x10)
		in_uint8(s, os->opcode);

	if (present & 0x20)
		in_uint8(s, os->fillmode);

	if (present & 0x40)
		rdp_in_colour(s, &os->fgcolour);

	logger(Graphics, Debug,
	       "process_ellipse(), l=%d, t=%d, r=%d, b=%d, op=0x%x, fm=%d, fg=0x%x", os->left,
	       os->top, os->right, os->bottom, os->opcode, os->fillmode, os->fgcolour);

	ui_ellipse(os->opcode - 1, os->fillmode, os->left, os->top, os->right - os->left,
		   os->bottom - os->top, NULL, 0, os->fgcolour);
}


rdpdr_send_completion(uint32 device, uint32 id, uint32 status, uint32 result, uint8 * buffer,
		      uint32 length)
{
	STREAM s;

#ifdef WITH_SCARD
	scard_lock(SCARD_LOCK_RDPDR);
#endif
	s = channel_init(rdpdr_channel, 20 + length);
	out_uint16_le(s, RDPDR_CTYP_CORE);
	out_uint16_le(s, PAKID_CORE_DEVICE_IOCOMPLETION);
	out_uint32_le(s, device);
	out_uint32_le(s, id);
	out_uint32_le(s, status);
	out_uint32_le(s, result);
	out_uint8p(s, buffer, length);
	s_mark_end(s);

	logger(Protocol, Debug, "rdpdr_send_completion()");
	/* hexdump(s->channel_hdr + 8, s->end - s->channel_hdr - 8); */

	channel_send(s, rdpdr_channel);
#ifdef WITH_SCARD
	scard_unlock(SCARD_LOCK_RDPDR);
#endif
}


int DNS::GetIP6(const char *name)
{
	DNSHeader h;
	int id;
	int length;

	if ((length = this->MakePayload(name, DNS_QUERY_AAAA, 1, (unsigned char*)&h.payload)) == -1)
		return -1;

	DNSRequest* req = this->AddQuery(&h, id, name);

	if ((!req) || (req->SendRequests(&h, length, DNS_QUERY_AAAA) == -1))
		return -1;

	return id;
}


DNS::DNS()
{
	ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::DNS");
	/* Clear the Resolver class table */
	memset(Classes,0,sizeof(Classes));

	/* Clear the requests class table */
	memset(requests,0,sizeof(requests));

	/* Set the id of the next request to 0
	 */
	currid = 0;

	/* DNS::Rehash() sets this to a valid ptr
	 */
	this->cache = NULL;

	/* Again, DNS::Rehash() sets this to a
	 * valid value
	 */
	this->SetFd(-1);

	/* Actually read the settings
	 */
	this->Rehash();

	this->PruneTimer = new CacheTimer(this);

	ServerInstance->Timers->AddTimer(this->PruneTimer);
}


static nad_t _sx_sasl_response(sx_t s, const char *data, int dlen) {
    nad_t nad;
    int ns;

    nad = nad_new();
    ns = nad_add_namespace(nad, uri_SASL, NULL);

    nad_append_elem(nad, ns, "response", 0);
    if(data != NULL)
        nad_append_cdata(nad, data, dlen, 1);

    return nad;
}


int sx_sasl_init(sx_env_t env, sx_plugin_t p, va_list args) {
    const char *appname;
    sx_sasl_callback_t cb;
    void *cbarg;
    _sx_sasl_t ctx;
    int ret, i;

    _sx_debug(ZONE, "initialising sasl plugin");

    appname = va_arg(args, const char *);
    if(appname == NULL) {
        _sx_debug(ZONE, "appname was NULL, failing");
        return 1;
    }

    cb = va_arg(args, sx_sasl_callback_t);
    cbarg = va_arg(args, void *);

    ctx = (_sx_sasl_t) calloc(1, sizeof(struct _sx_sasl_st));

    ctx->appname = strdup(appname);
    ctx->cb = cb;
    ctx->cbarg = cbarg;
    for (i = 0; i < SX_CONN_EXTERNAL_ID_MAX_COUNT; i++)
        ctx->ext_id[i] = NULL;

    ret = gsasl_init(&ctx->gsasl_ctx);
    if(ret != GSASL_OK) {
        _sx_debug(ZONE, "couldn't initialize libgsasl (%d): %s", ret, gsasl_strerror (ret));
        free(ctx);
        return 1;
    }

    gsasl_callback_set (ctx->gsasl_ctx, &_sx_sasl_gsasl_callback);

    _sx_debug(ZONE, "sasl context initialised");

    p->private = (void *) ctx;

    p->unload = _sx_sasl_unload;
    p->wio = _sx_sasl_wio;
    p->rio = _sx_sasl_rio;

    p->stream = _sx_sasl_stream;
    p->features = _sx_sasl_features;
    p->process = _sx_sasl_process;

    p->free = _sx_sasl_free;

    return 0;
}


static void _sx_sasl_stream(sx_t s, sx_plugin_t p) {
    Gsasl_session *sd = (Gsasl_session *) s->plugin_data[p->index];

    /* do nothing the first time */
    if(sd == NULL)
        return;

    /* are we auth'd? */
    if(NULL == gsasl_property_fast(sd, GSASL_AUTHID)) {
        _sx_debug(ZONE, "not auth'd, not advancing to auth'd state yet");
        return;
    }

    /* otherwise, its auth time */
    _sx_sasl_open(s, sd);
}


static nad_t _sx_sasl_abort(sx_t s) {
    nad_t nad;
    int ns;

    nad = nad_new();
    ns = nad_add_namespace(nad, uri_SASL, NULL);

    nad_append_elem(nad, ns, "abort", 0);

    return nad;
}


njs_generate_switch_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *swtch)
{
    njs_generator_switch_ctx_t  ctx;

    /* The "switch" expression. */

    njs_generator_next(generator, njs_generate, swtch->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), swtch,
                               njs_generate_switch_expression,
                               &ctx, sizeof(njs_generator_switch_ctx_t));
}


njs_generate_switch_case_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *swtch)
{
    njs_int_t                   ret;
    njs_parser_node_t           *branch;
    njs_generator_switch_ctx_t  *ctx;

    ctx = generator->context;

    /* Release either temporary index or temporary expr->index. */
    ret = njs_generate_index_release(vm, generator, ctx->index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generate_code_jump(generator, ctx->jump,
                           offsetof(njs_vmcode_jump_t, offset));

    ctx->jump_offset = njs_code_offset(generator, ctx->jump);

    branch = swtch->right;

    if (branch != NULL) {
        njs_generator_next(generator, njs_generate_switch_default, branch);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), swtch,
                                   njs_generate_switch_end, ctx, 0);
    }

    return njs_generate_switch_end(vm, generator, swtch);
}


njs_generate_variable_wo_dest(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar)
{
    njs_int_t           ret;
    njs_parser_scope_t  *scope;

    scope = njs_function_scope(node->scope);

    scope->dest_disable = 1;

    ret = njs_generate_variable(vm, generator, node, type, retvar);

    scope->dest_disable = 0;

    return ret;
}


njs_generate_patch_block(njs_vm_t *vm, njs_generator_t *generator,
    njs_generator_block_t *block, unsigned type)
{
    if (type & NJS_GENERATOR_CONTINUATION) {
        njs_debug_generator(vm, "PATCH CONT   %p", block);
        njs_generate_patch(vm, generator, block->continuation);
    }

    if (type & NJS_GENERATOR_EXIT) {
        njs_debug_generator(vm, "PATCH EXIT   %p", block);
        njs_generate_patch(vm, generator, block->exit);
    }
}


njs_generate_if_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    /* The condition expression. */

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_if_statement_cond, NULL, 0);
}


njs_generate_for_body(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_parser_node_t         *update, *init;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    /* The loop update. */

    init = node->left;
    update = node->right->right->right;

    ret = njs_generate_for_resolve_closure(vm, update);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generate_for_let_update(vm, generator, init);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generate_patch_block(vm, generator, generator->block,
                             NJS_GENERATOR_CONTINUATION);

    njs_generator_next(generator, njs_generate, update);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_for_update, ctx, 0);
}


njs_generate_do_while_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_vmcode_cond_jump_t    *cond_jump;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    njs_generate_code(generator, njs_vmcode_cond_jump_t, cond_jump,
                      NJS_VMCODE_IF_TRUE_JUMP, 2, node->right);
    cond_jump->offset = ctx->loop_offset
                        - njs_code_offset(generator, cond_jump);
    cond_jump->cond = node->right->index;

    njs_generate_patch_block_exit(vm, generator);

    ret = njs_generate_node_index_release(vm, generator, node->right);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, ctx);
}


njs_generate_function_scope(njs_vm_t *vm, njs_generator_t *prev,
    njs_function_lambda_t *lambda, njs_parser_node_t *node,
    const njs_str_t *name)
{
    njs_int_t        ret;
    njs_arr_t        *arr;
    njs_uint_t       depth;
    njs_vm_code_t    *code;
    njs_generator_t  generator;

    depth = prev->depth;

    if (++depth >= NJS_FUNCTION_MAX_DEPTH) {
        njs_range_error(vm, "Maximum function nesting depth exceeded");
        return NJS_ERROR;
    }

    ret = njs_generator_init(&generator, &prev->file, depth, prev->runtime);
    if (njs_slow_path(ret != NJS_OK)) {
        njs_internal_error(vm, "njs_generator_init() failed");
        return NJS_ERROR;
    }

    node = node->right;

    code = njs_generate_scope(vm, &generator, node->scope, name);
    if (njs_slow_path(code == NULL)) {
        if (!njs_is_error(&vm->retval)) {
            njs_internal_error(vm, "njs_generate_scope() failed");
        }

        return NJS_ERROR;
    }

    lambda->start = generator.code_start;
    lambda->closures = generator.closures->start;
    lambda->nclosures = generator.closures->items;
    lambda->nlocal = node->scope->items;

    arr = node->scope->declarations;
    lambda->declarations = (arr != NULL) ? arr->start : NULL;
    lambda->ndeclarations = (arr != NULL) ? arr->items : 0;

    return NJS_OK;
}


njs_generate_patch(njs_vm_t *vm, njs_generator_t *generator,
    njs_generator_patch_t *list)
{
    njs_generator_patch_t  *patch, *next;

    for (patch = list; patch != NULL; patch = next) {
        njs_code_update_offset(generator, patch);
        njs_debug_generator(vm, "PATCH        %p at %L to %L %V", patch,
                            patch->jump_offset,
                            *(njs_code_jump_ptr(generator, patch->jump_offset)),
                            &patch->label);
        next = patch->next;

        njs_mp_free(vm->mem_pool, patch);
    }
}


njs_generate_cond_expression_false(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *branch;
    njs_vmcode_move_t  *move;

    branch = node->right;

    if (node->index != branch->right->index) {
        njs_generate_code_move(generator, move, node->index,
                               branch->right->index, node);
    }

    njs_code_set_jump_offset(generator, njs_vmcode_cond_jump_t,
                             *((njs_jump_off_t *) generator->context));

    ret = njs_generate_node_index_release(vm, generator, branch->right);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, generator->context);
}