static GF_Err txtin_process_srt(GF_Filter *filter, GF_TXTIn *ctx)
{
	u32 i;
	u32 sh, sm, ss, sms, eh, em, es, ems, txt_line, char_len, char_line, j, rem_styles;
	Bool set_start_char, set_end_char, rem_color;
	u32 line, len;
	char szLine[2048], szText[2048], *ptr;
	unsigned short uniLine[5000], uniText[5000], *sptr;

	if (!ctx->is_setup) {
		ctx->is_setup = GF_TRUE;
		return txtin_setup_srt(filter, ctx);
	}
	if (!ctx->opid) return GF_NOT_SUPPORTED;
	if (!ctx->playstate) return GF_OK;
	else if (ctx->playstate==2) return GF_EOS;

	txt_line = 0;
	set_start_char = set_end_char = GF_FALSE;
	char_len = 0;

	if (ctx->seek_state == 1) {
		ctx->seek_state = 2;
		gf_fseek(ctx->src, 0, SEEK_SET);
	}

	while (1) {
		Bool is_empty = GF_FALSE;
		char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type);

		if (sOK) {
			REM_TRAIL_MARKS(szLine, "\r\n\t ")

			if (ctx->unicode_type<=1) is_empty = strlen(szLine) ? GF_FALSE : GF_TRUE;
			else is_empty =  (!szLine[0] && !szLine[1]) ? GF_TRUE : GF_FALSE;
		}

		if (!sOK || is_empty) {
			u32 utf_inc = (ctx->unicode_type<=1) ? 1 : 2;
			u32 nb_empty = utf_inc;
			u32 pos = (u32) gf_ftell(ctx->src);
			if (ctx->state) {
				while (!gf_feof(ctx->src)) {
					sOK = gf_text_get_utf8_line(szLine+nb_empty, 2048-nb_empty, ctx->src, ctx->unicode_type);
					if (sOK) REM_TRAIL_MARKS((szLine+nb_empty), "\r\n\t ")

					if (!sOK) {
						gf_fseek(ctx->src, pos, SEEK_SET);
						break;
					} else if (!strlen(szLine+nb_empty)) {
						nb_empty+=utf_inc;
						continue;
					} else if (	sscanf(szLine+nb_empty, "%u", &line) == 1) {
						gf_fseek(ctx->src, pos, SEEK_SET);
						break;
					} else {
						u32 k;
						for (k=0; k<nb_empty; k++) szLine[k] = '\n';
						goto force_line;
					}
				}
			}
			ctx->style.style_flags = 0;
			ctx->style.text_color = 0xFFFFFFFF;
			ctx->style.startCharOffset = ctx->style.endCharOffset = 0;
			if (txt_line) {
				if (ctx->prev_end && (ctx->start != ctx->prev_end) && (ctx->state<=2)) {
					GF_TextSample * empty_samp = gf_isom_new_text_sample();
					txtin_process_send_text_sample(ctx, empty_samp, ctx->prev_end, (u32) (ctx->start - ctx->prev_end), GF_TRUE );
					gf_isom_delete_text_sample(empty_samp);
				}

				if (ctx->state<=2) {
					txtin_process_send_text_sample(ctx, ctx->samp,  ctx->start, (u32) (ctx->end -  ctx->start), GF_TRUE);
					ctx->prev_end = ctx->end;
				}
				txt_line = 0;
				char_len = 0;
				set_start_char = set_end_char = GF_FALSE;
				ctx->style.startCharOffset = ctx->style.endCharOffset = 0;
				gf_isom_text_reset(ctx->samp);

				gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT( gf_ftell(ctx->src )) );
			}
			ctx->state = 0;
			if (!sOK) break;
			continue;
		}

force_line:
		switch (ctx->state) {
		case 0:
			if (sscanf(szLine, "%u", &line) != 1) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Bad SRT formatting - expecting number got \"%s\"\n", szLine));
				break;
			}
			if (line != ctx->curLine + 1) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Corrupted SRT frame %d after frame %d\n", line, ctx->curLine));
			}
			ctx->curLine = line;
			ctx->state = 1;
			break;
		case 1:
			if (sscanf(szLine, "%u:%u:%u,%u --> %u:%u:%u,%u", &sh, &sm, &ss, &sms, &eh, &em, &es, &ems) != 8) {
				if (sscanf(szLine, "%u:%u:%u.%u --> %u:%u:%u.%u", &sh, &sm, &ss, &sms, &eh, &em, &es, &ems) != 8) {
					sh = eh = 0;
					if (sscanf(szLine, "%u:%u,%u --> %u:%u,%u", &sm, &ss, &sms, &em, &es, &ems) != 6) {
						if (sscanf(szLine, "%u:%u.%u --> %u:%u.%u", &sm, &ss, &sms, &em, &es, &ems) != 6) {
							GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Error scanning SRT frame %d timing\n", ctx->curLine));
				    		ctx->state = 0;
							break;
						}
					}
				}
			}
			ctx->start = (3600*sh + 60*sm + ss)*1000 + sms;
			if (ctx->start < ctx->end) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Overlapping SRT frame %d - starts "LLD" ms is before end of previous one "LLD" ms - adjusting time stamps\n", ctx->curLine, ctx->start, ctx->end));
				ctx->start = ctx->end;
			}

			ctx->end = (3600*eh + 60*em + es)*1000 + ems;
			/*make stream start at 0 by inserting a fake AU*/
			if (ctx->first_samp && (ctx->start > 0)) {
				txtin_process_send_text_sample(ctx, ctx->samp, 0, (u32) ctx->start, GF_TRUE);
			}
			ctx->style.style_flags = 0;
			ctx->state = 2;
			if (ctx->end <= ctx->prev_end) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Overlapping SRT frame %d end "LLD" is at or before previous end "LLD" - removing\n", ctx->curLine, ctx->end, ctx->prev_end));
				ctx->start = ctx->end;
				ctx->state = 3;
			}
			break;

		default:
			/*reset only when text is present*/
			ctx->first_samp = GF_FALSE;

			/*go to line*/
			if (txt_line) {
				gf_isom_text_add_text(ctx->samp, "\n", 1);
				char_len += 1;
			}

			ptr = (char *) szLine;
			{
				len = gf_utf8_mbstowcs(uniLine, 5000, (const char **) &ptr);
				if (len == GF_UTF8_FAIL) {
					GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Invalid UTF data (line %d)\n", ctx->curLine));
					ctx->state = 0;
					len = 0;
				}
			}
			i=j=0;
			rem_styles = 0;
			rem_color = 0;
			while (i<len) {
				u32 font_style = 0;
				u32 style_nb_chars = 0;
				u32 style_def_type = 0;

				if ( (uniLine[i]=='<') && (uniLine[i+2]=='>')) {
					style_nb_chars = 3;
					style_def_type = 1;
				}
				else if ( (uniLine[i]=='<') && (uniLine[i+1]=='/') && (uniLine[i+3]=='>')) {
					style_def_type = 2;
					style_nb_chars = 4;
				}
				else if (uniLine[i]=='<')  {
					const unsigned short* src = uniLine + i;
					u32 alen = gf_utf8_wcstombs(szLine, 2048, (const unsigned short**) & src);
					if (alen == GF_UTF8_FAIL) alen = 0;
					szLine[alen] = 0;
					strlwr(szLine);
					if (!strncmp(szLine, "');
								if (e_sep) {
									style_nb_chars = (u32) (1 + e_sep - szLine);
									style_def_type = 1;
								}
							}

						}
					}
					else if (!strncmp(szLine, "", 7) ) {
						style_nb_chars = 7;
						style_def_type = 2;
						font_style = 0xFFFFFFFF;
					}

					else if (!strncmp(szLine, "", 8) ) {
						style_nb_chars = 8;
						style_def_type = 1;
					}
					else if (!strncmp(szLine, "", 9) ) {
						style_nb_chars = 9;
						style_def_type = 2;
						font_style = 0xFFFFFFFF;
					}
					//skip unknown
					else {
						char *a_sep = strstr(szLine, ">");
						if (a_sep) {
							style_nb_chars = (u32) (1 + a_sep - szLine);
							i += style_nb_chars;
							continue;
						}
					}

				}

				/*start of new style*/
				if (style_def_type==1)  {
					/*store prev style*/
					if (set_end_char) {
						assert(set_start_char);
						gf_isom_text_add_style(ctx->samp, &ctx->style);
						set_end_char = set_start_char = GF_FALSE;
						ctx->style.style_flags &= ~rem_styles;
						rem_styles = 0;
						if (rem_color) {
							ctx->style.text_color = ctx->default_color;
							rem_color = 0;
						}
					}
					if (set_start_char && (ctx->style.startCharOffset != j)) {
						ctx->style.endCharOffset = char_len + j;
						if (ctx->style.style_flags) gf_isom_text_add_style(ctx->samp, &ctx->style);
					}
					switch (uniLine[i+1]) {
					case 'b':
					case 'B':
						ctx->style.style_flags |= GF_TXT_STYLE_BOLD;
						set_start_char = GF_TRUE;
						ctx->style.startCharOffset = char_len + j;
						break;
					case 'i':
					case 'I':
						ctx->style.style_flags |= GF_TXT_STYLE_ITALIC;
						set_start_char = GF_TRUE;
						ctx->style.startCharOffset = char_len + j;
						break;
					case 'u':
					case 'U':
						ctx->style.style_flags |= GF_TXT_STYLE_UNDERLINED;
						set_start_char = GF_TRUE;
						ctx->style.startCharOffset = char_len + j;
						break;
					case 's':
					case 'S':
						ctx->style.style_flags |= GF_TXT_STYLE_STRIKETHROUGH;
						set_start_char = GF_TRUE;
						ctx->style.startCharOffset = char_len + j;
						break;
					case 'f':
					case 'F':
						if (font_style) {
							ctx->style.text_color = font_style;
							set_start_char = GF_TRUE;
							ctx->style.startCharOffset = char_len + j;
						}
						break;
					}
					i += style_nb_chars;
					continue;
				}

				/*end of prev style*/
				if (style_def_type==2)  {
					switch (uniLine[i+2]) {
					case 'b':
					case 'B':
						rem_styles |= GF_TXT_STYLE_BOLD;
						set_end_char = GF_TRUE;
						ctx->style.endCharOffset = char_len + j;
						break;
					case 'i':
					case 'I':
						rem_styles |= GF_TXT_STYLE_ITALIC;
						set_end_char = GF_TRUE;
						ctx->style.endCharOffset = char_len + j;
						break;
					case 'u':
					case 'U':
						rem_styles |= GF_TXT_STYLE_UNDERLINED;
						set_end_char = GF_TRUE;
						ctx->style.endCharOffset = char_len + j;
						break;
					case 's':
					case 'S':
						rem_styles |= GF_TXT_STYLE_STRIKETHROUGH;
						set_end_char = GF_TRUE;
						ctx->style.endCharOffset = char_len + j;
						break;
					case 'f':
					case 'F':
						if (font_style) {
							rem_color = 1;
							set_end_char = GF_TRUE;
							ctx->style.endCharOffset = char_len + j;
						}
					}
					i+=style_nb_chars;
					continue;
				}
				/*store style*/
				if (set_end_char) {
					gf_isom_text_add_style(ctx->samp, &ctx->style);
					set_end_char = GF_FALSE;
					set_start_char = GF_TRUE;
					ctx->style.startCharOffset = char_len + j;
					ctx->style.style_flags &= ~rem_styles;
					rem_styles = 0;
					ctx->style.text_color = ctx->default_color;
					rem_color = 0;
				}

				uniText[j] = uniLine[i];
				j++;
				i++;
			}
			/*store last style*/
			if (set_end_char) {
				gf_isom_text_add_style(ctx->samp, &ctx->style);
				set_end_char = GF_FALSE;
				set_start_char = GF_TRUE;
				ctx->style.startCharOffset = char_len + j;
				ctx->style.style_flags &= ~rem_styles;
			}

			char_line = j;
			uniText[j] = 0;

			sptr = (u16 *) uniText;
			len = gf_utf8_wcstombs(szText, 5000, (const u16 **) &sptr);
			if (len == GF_UTF8_FAIL) len = 0;

			gf_isom_text_add_text(ctx->samp, szText, len);
			char_len += char_line;
			txt_line ++;
			break;
		}

		if (gf_filter_pid_would_block(ctx->opid))
			return GF_OK;
	}

	/*final flush*/
	if (ctx->end && ! ctx->noflush) {
		gf_isom_text_reset(ctx->samp);
		txtin_process_send_text_sample(ctx, ctx->samp, ctx->end, 0, GF_TRUE);
		ctx->end = 0;
	}
	gf_isom_text_reset(ctx->samp);

	return GF_EOS;
}


void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) {
        assert(t);
        assert(source);

        /* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. If the state is still PENDING,
           we are still in the loop that adds further DNSSEC transactions, hence don't check if we are ready yet. If
           the state is VALIDATING however, we should check if we are complete now. */

        if (t->state == DNS_TRANSACTION_VALIDATING)
                dns_transaction_process_dnssec(t);
}


void dns_server_warn_downgrade(DnsServer *server) {
        assert(server);

        if (server->warned_downgrade)
                return;

        log_struct(LOG_NOTICE,
                   "MESSAGE_ID=" SD_MESSAGE_DNSSEC_DOWNGRADE_STR,
                   LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)),
                   "DNS_SERVER=%s", dns_server_string(server),
                   "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level));

        server->warned_downgrade = true;
}


int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) {
        size_t packet_size;
        bool edns_do;
        int r;

        assert(server);
        assert(packet);
        assert(packet->protocol == DNS_PROTOCOL_DNS);

        /* Fix the OPT field in the packet to match our current feature level. */

        r = dns_packet_truncate_opt(packet);
        if (r < 0)
                return r;

        if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0)
                return 0;

        edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO;

        if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE)
                packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
        else
                packet_size = server->received_udp_packet_max;

        return dns_packet_append_opt(packet, packet_size, edns_do, 0, NULL);
}


static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) {
        bool changed;
        int r;

        assert(t);

        /* Removes all DNSKEY/DS objects from t->validated_keys that
         * our trust anchors database considers revoked. */

        do {
                DnsResourceRecord *rr;

                changed = false;

                DNS_ANSWER_FOREACH(rr, t->validated_keys) {
                        r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr);
                        if (r < 0)
                                return r;
                        if (r > 0) {
                                r = dns_answer_remove_by_rr(&t->validated_keys, rr);
                                if (r < 0)
                                        return r;

                                assert(r > 0);
                                changed = true;
                                break;
                        }
                }
        } while (changed);

        return 0;
}


void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t size) {
        assert(s);

        if (protocol == IPPROTO_UDP) {
                if (s->possible_feature_level == level)
                        s->n_failed_udp = 0;
        } else if (protocol == IPPROTO_TCP) {
                if (DNS_SERVER_FEATURE_LEVEL_IS_TLS(level)) {
                        if (s->possible_feature_level == level)
                                s->n_failed_tls = 0;
                } else {
                        if (s->possible_feature_level == level)
                                s->n_failed_tcp = 0;

                        /* Successful TCP connections are only useful to verify the TCP feature level. */
                        level = DNS_SERVER_FEATURE_LEVEL_TCP;
                }
        }

        /* If the RRSIG data is missing, then we can only validate EDNS0 at max */
        if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO)
                level = DNS_SERVER_FEATURE_LEVEL_IS_TLS(level) ? DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN : DNS_SERVER_FEATURE_LEVEL_EDNS0;

        /* If the OPT RR got lost, then we can only validate UDP at max */
        if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0)
                level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1;

        /* Even if we successfully receive a reply to a request announcing support for large packets,
                that does not mean we can necessarily receive large packets. */
        if (level == DNS_SERVER_FEATURE_LEVEL_LARGE)
                level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1;

        dns_server_verified(s, level);

        /* Remember the size of the largest UDP packet we received from a server,
           we know that we can always announce support for packets with at least
           this size. */
        if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size)
                s->received_udp_packet_max = size;
}


void dns_server_mark_all(DnsServer *first) {
        if (!first)
                return;

        first->marked = true;
        dns_server_mark_all(first->servers_next);
}


void dns_server_flush_cache(DnsServer *s) {
        DnsServer *current;
        DnsScope *scope;

        assert(s);

        /* Flush the cache of the scope this server belongs to */

        current = s->link ? s->link->current_dns_server : s->manager->current_dns_server;
        if (current != s)
                return;

        scope = s->link ? s->link->unicast_scope : s->manager->unicast_scope;
        if (!scope)
                return;

        dns_cache_flush(&scope->cache);
}


static uint16_t dns_port_for_feature_level(DnsServerFeatureLevel level) {
        return DNS_SERVER_FEATURE_LEVEL_IS_TLS(level) ? 853 : 53;
}


void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
        DnsQueryCandidate *c;
        DnsZoneItem *z;
        DnsTransaction *d;
        const char *st;
        char key_str[DNS_RESOURCE_KEY_STRING_MAX];

        assert(t);
        assert(!DNS_TRANSACTION_IS_LIVE(state));

        if (state == DNS_TRANSACTION_DNSSEC_FAILED) {
                dns_resource_key_to_string(t->key, key_str, sizeof key_str);

                log_struct(LOG_NOTICE,
                           "MESSAGE_ID=" SD_MESSAGE_DNSSEC_FAILURE_STR,
                           LOG_MESSAGE("DNSSEC validation failed for question %s: %s", key_str, dnssec_result_to_string(t->answer_dnssec_result)),
                           "DNS_TRANSACTION=%" PRIu16, t->id,
                           "DNS_QUESTION=%s", key_str,
                           "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result),
                           "DNS_SERVER=%s", dns_server_string(t->server),
                           "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level));
        }

        /* Note that this call might invalidate the query. Callers
         * should hence not attempt to access the query or transaction
         * after calling this function. */

        if (state == DNS_TRANSACTION_ERRNO)
                st = errno_to_name(t->answer_errno);
        else
                st = dns_transaction_state_to_string(state);

        log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).",
                  t->id,
                  dns_resource_key_to_string(t->key, key_str, sizeof key_str),
                  dns_protocol_to_string(t->scope->protocol),
                  t->scope->link ? t->scope->link->name : "*",
                  af_to_name_short(t->scope->family),
                  st,
                  t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source),
                  t->answer_authenticated ? "authenticated" : "unsigned");

        t->state = state;

        dns_transaction_close_connection(t);
        dns_transaction_stop_timeout(t);

        /* Notify all queries that are interested, but make sure the
         * transaction isn't freed while we are still looking at it */
        t->block_gc++;

        SET_FOREACH_MOVE(c, t->notify_query_candidates_done, t->notify_query_candidates)
                dns_query_candidate_notify(c);
        SWAP_TWO(t->notify_query_candidates, t->notify_query_candidates_done);

        SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items)
                dns_zone_item_notify(z);
        SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done);
        if (t->probing && t->state == DNS_TRANSACTION_ATTEMPTS_MAX_REACHED)
                (void) dns_scope_announce(t->scope, false);

        SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions)
                dns_transaction_notify(d, t);
        SWAP_TWO(t->notify_transactions, t->notify_transactions_done);

        t->block_gc--;
        dns_transaction_gc(t);
}


DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
        DnsServerFeatureLevel best;

        assert(s);

        /* Determine the best feature level we care about. If DNSSEC mode is off there's no point in using anything
         * better than EDNS0, hence don't even try. */
        if (dns_server_get_dnssec_mode(s) != DNSSEC_NO)
                best = dns_server_get_dns_over_tls_mode(s) == DNS_OVER_TLS_NO ?
                        DNS_SERVER_FEATURE_LEVEL_LARGE :
                        DNS_SERVER_FEATURE_LEVEL_TLS_DO;
        else
                best = dns_server_get_dns_over_tls_mode(s) == DNS_OVER_TLS_NO ?
                        DNS_SERVER_FEATURE_LEVEL_EDNS0 :
                        DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN;

        /* Clamp the feature level the highest level we care about. The DNSSEC mode might have changed since the last
         * time, hence let's downgrade if we are still at a higher level. */
        if (s->possible_feature_level > best)
                s->possible_feature_level = best;

        if (s->possible_feature_level < best && dns_server_grace_period_expired(s)) {

                s->possible_feature_level = best;

                dns_server_reset_counters(s);

                s->packet_bad_opt = false;
                s->packet_rrsig_missing = false;

                log_info("Grace period over, resuming full feature set (%s) for DNS server %s.",
                         dns_server_feature_level_to_string(s->possible_feature_level),
                         dns_server_string(s));

                dns_server_flush_cache(s);

        } else if (s->possible_feature_level <= s->verified_feature_level)
                s->possible_feature_level = s->verified_feature_level;
        else {
                DnsServerFeatureLevel p = s->possible_feature_level;

                if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
                    s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) {

                        /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't
                         * work. Upgrade back to UDP again. */
                        log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again...");
                        s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP;
                } else if (s->n_failed_tls > 0 &&
                           DNS_SERVER_FEATURE_LEVEL_IS_TLS(s->possible_feature_level)) {

                        /* We tried to connect using DNS-over-TLS, and it didn't work. Downgrade to plaintext UDP
                         * if we don't require DNS-over-TLS */

                        log_debug("Server doesn't support DNS-over-TLS, downgrading protocol...");
                        s->possible_feature_level--;
                } else if (s->packet_bad_opt &&
                           s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) {

                        /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below
                         * EDNS0 levels. After all, some records generate different responses with and without OPT RR
                         * in the request. Example:
                         * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */

                        log_debug("Server doesn't support EDNS(0) properly, downgrading feature level...");
                        s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP;

                } else if (s->packet_rrsig_missing &&
                           s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) {

                        /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't
                         * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore,
                         * after all some servers generate different replies depending if an OPT RR is in the query or
                         * not. */

                        log_debug("Detected server responses lack RRSIG records, downgrading feature level...");
                        s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_IS_TLS(s->possible_feature_level) ? DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN : DNS_SERVER_FEATURE_LEVEL_EDNS0;

                } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
                           s->possible_feature_level >= (dns_server_get_dnssec_mode(s) == DNSSEC_YES ? DNS_SERVER_FEATURE_LEVEL_LARGE : DNS_SERVER_FEATURE_LEVEL_UDP)) {

                        /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the
                         * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good
                         * idea. We might downgrade all the way down to TCP this way.
                         *
                         * If strict DNSSEC mode is used we won't downgrade below DO level however, as packet loss
                         * might have many reasons, a broken DNSSEC implementation being only one reason. And if the
                         * user is strict on DNSSEC, then let's assume that DNSSEC is not the fault here. */

                        log_debug("Lost too many UDP packets, downgrading feature level...");
                        s->possible_feature_level--;

                } else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
                           s->packet_truncated &&
                           s->possible_feature_level > (dns_server_get_dnssec_mode(s) == DNSSEC_YES ? DNS_SERVER_FEATURE_LEVEL_LARGE : DNS_SERVER_FEATURE_LEVEL_UDP)) {

                         /* We got too many TCP connection failures in a row, we had at least one truncated packet, and
                          * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0
                          * data we hope to make the packet smaller, so that it still works via UDP given that TCP
                          * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't
                          * go further down, since that's TCP, and TCP failed too often after all. */

                        log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level...");
                        s->possible_feature_level--;
                }

                if (p != s->possible_feature_level) {

                        /* We changed the feature level, reset the counting */
                        dns_server_reset_counters(s);

                        log_warning("Using degraded feature set (%s) for DNS server %s.",
                                    dns_server_feature_level_to_string(s->possible_feature_level),
                                    dns_server_string(s));
                }
        }

        return s->possible_feature_level;
}


static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) {
        int r;

        assert(t);

        /* Checks whether the answer is negative, and lacks NSEC/NSEC3
         * RRs to prove it */

        r = dns_transaction_has_positive_answer(t, NULL);
        if (r < 0)
                return r;
        if (r > 0)
                return false;

        /* Is this key explicitly listed as a negative trust anchor?
         * If so, it's nothing we need to care about */
        r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key));
        if (r < 0)
                return r;
        if (r > 0)
                return false;

        /* The answer does not contain any RRs that match to the
         * question. If so, let's see if there are any NSEC/NSEC3 RRs
         * included. If not, the answer is unsigned. */

        r = dns_answer_contains_nsec_or_nsec3(t->answer);
        if (r < 0)
                return r;
        if (r > 0)
                return false;

        return true;
}


void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) {
        assert(s);

        if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0)
                return;

        /* If the OPT RR got lost, we have to downgrade what we previously verified */
        if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0)
                s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1;

        s->packet_bad_opt = true;
}


static int nla_client_recv_pub_key_auth(rdpNla* nla)
{
	WINPR_ASSERT(nla);

	/* Verify Server Public Key Echo */
	if (nla->peerVersion < 5)
		nla->status = nla_decrypt_public_key_echo(nla);
	else
		nla->status = nla_decrypt_public_key_hash(nla);

	nla_buffer_free(nla);

	if (nla->status != SEC_E_OK)
	{
		WLog_ERR(TAG, "Could not verify public key echo %s [0x%08" PRIX32 "]",
		         GetSecurityStatusString(nla->status), nla->status);
		return -1;
	}

	/* Send encrypted credentials */
	nla->status = nla_encrypt_ts_credentials(nla);
	if (nla->status != SEC_E_OK)
		return -1;

	if (!nla_send(nla))
	{
		nla_buffer_free(nla);
		return -1;
	}

	nla_buffer_free(nla);

	if (SecIsValidHandle(&nla->credentials))
	{
		nla->table->FreeCredentialsHandle(&nla->credentials);
		SecInvalidateHandle(&nla->credentials);
	}

	if (nla->status != SEC_E_OK)
	{
		WLog_ERR(TAG, "FreeCredentialsHandle status %s [0x%08" PRIX32 "]",
		         GetSecurityStatusString(nla->status), nla->status);
	}

	if (nla->status != SEC_E_OK)
		return -1;

	nla_set_state(nla, NLA_STATE_AUTH_INFO);
	return 1;
}


static BOOL kerberos_mk_tgt_token(SecBuffer* buf, int msg_type, char* sname, char* host,
                                  const krb5_data* ticket)
{
	WinPrAsn1Encoder* enc;
	WinPrAsn1_MemoryChunk data;
	wStream s;
	size_t len;
	sspi_gss_data token;
	BOOL ret = FALSE;

	WINPR_ASSERT(buf);

	if (msg_type != KRB_TGT_REQ && msg_type != KRB_TGT_REP)
		return FALSE;
	if (msg_type == KRB_TGT_REP && !ticket)
		return FALSE;

	enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
	if (!enc)
		return FALSE;

	/* KERB-TGT-REQUEST (SEQUENCE) */
	if (!WinPrAsn1EncSeqContainer(enc))
		goto cleanup;

	/* pvno [0] INTEGER */
	if (!WinPrAsn1EncContextualInteger(enc, 0, 5))
		goto cleanup;

	/* msg-type [1] INTEGER */
	if (!WinPrAsn1EncContextualInteger(enc, 1, msg_type))
		goto cleanup;

	if (msg_type == KRB_TGT_REQ && sname)
	{
		/* server-name [2] PrincipalName (SEQUENCE) */
		if (!WinPrAsn1EncContextualSeqContainer(enc, 2))
			goto cleanup;

		/* name-type [0] INTEGER */
		if (!WinPrAsn1EncContextualInteger(enc, 0, KRB5_NT_SRV_HST))
			goto cleanup;

		/* name-string [1] SEQUENCE OF GeneralString */
		if (!WinPrAsn1EncContextualSeqContainer(enc, 1))
			goto cleanup;

		if (!WinPrAsn1EncGeneralString(enc, sname))
			goto cleanup;

		if (host && !WinPrAsn1EncGeneralString(enc, host))
			goto cleanup;

		if (!WinPrAsn1EncEndContainer(enc) || !WinPrAsn1EncEndContainer(enc))
			goto cleanup;
	}
	else if (msg_type == KRB_TGT_REP)
	{
		/* ticket [2] Ticket */
		data.data = (BYTE*)ticket->data;
		data.len = ticket->length;
		if (!WinPrAsn1EncContextualRawContent(enc, 2, &data))
			goto cleanup;
	}

	if (!WinPrAsn1EncEndContainer(enc))
		goto cleanup;

	if (!WinPrAsn1EncStreamSize(enc, &len) || len > buf->cbBuffer)
		goto cleanup;

	Stream_StaticInit(&s, buf->pvBuffer, len);
	if (!WinPrAsn1EncToStream(enc, &s))
		goto cleanup;

	token.data = buf->pvBuffer;
	token.length = (UINT)len;
	if (sspi_gss_wrap_token(buf, &kerberos_u2u_OID,
	                        msg_type == KRB_TGT_REQ ? TOK_ID_TGT_REQ : TOK_ID_TGT_REP, &token))
		ret = TRUE;

cleanup:
	WinPrAsn1Encoder_Free(&enc);
	return ret;
}


static int nla_server_authenticate(rdpNla* nla)
{
	int res = -1;

	WINPR_ASSERT(nla);

	if (nla_server_init(nla) < 1)
		goto fail_auth;

	/* Client is starting, here es the state machine:
	 *
	 *  -- NLA_STATE_INITIAL	--> NLA_STATE_INITIAL
	 * ----->> sending...
	 *    ----->> protocol version 6
	 *    ----->> nego token
	 *    ----->> client nonce
	 * <<----- receiving...
	 *    <<----- protocol version 6
	 *    <<----- nego token
	 * ----->> sending...
	 *    ----->> protocol version 6
	 *    ----->> nego token
	 *    ----->> public key auth
	 *    ----->> client nonce
	 * -- NLA_STATE_NEGO_TOKEN	--> NLA_STATE_PUB_KEY_AUTH
	 * <<----- receiving...
	 *    <<----- protocol version 6
	 *    <<----- public key info
	 * ----->> sending...
	 *    ----->> protocol version 6
	 *    ----->> auth info
	 *    ----->> client nonce
	 * -- NLA_STATE_PUB_KEY_AUTH	--> NLA_STATE
	 */

	while (TRUE)
	{
		int rc = -1;
		SecBuffer inputBuffer = { 0 };
		SecBuffer outputBuffer = { 0 };
		SecBufferDesc inputBufferDesc = { 0 };
		SecBufferDesc outputBufferDesc = { 0 };

		/* receive authentication token */
		inputBufferDesc.ulVersion = SECBUFFER_VERSION;
		inputBufferDesc.cBuffers = 1;
		inputBufferDesc.pBuffers = &inputBuffer;

		if (nla_server_recv(nla) < 0)
			goto fail_auth;

		WLog_DBG(TAG, "Receiving Authentication Token");
		if (!nla_sec_buffer_alloc_from_buffer(&inputBuffer, &nla->negoToken, 0))
		{
			WLog_ERR(TAG, "CredSSP: invalid negoToken!");
			goto fail;
		}

		outputBufferDesc.ulVersion = SECBUFFER_VERSION;
		outputBufferDesc.cBuffers = 1;
		outputBufferDesc.pBuffers = &outputBuffer;

		if (!nla_sec_buffer_alloc(&outputBuffer, nla->cbMaxToken))
			goto fail;

		nla->status = nla->table->AcceptSecurityContext(
		    &nla->credentials, nla->haveContext ? &nla->context : NULL, &inputBufferDesc,
		    nla->fContextReq, SECURITY_NATIVE_DREP, &nla->context, &outputBufferDesc,
		    &nla->pfContextAttr, &nla->expiration);
		WLog_VRB(TAG, "AcceptSecurityContext status %s [0x%08" PRIX32 "]",
		         GetSecurityStatusString(nla->status), nla->status);

		if (!nla_sec_buffer_alloc_from_buffer(&nla->negoToken, &outputBuffer, 0))
			goto fail;

		if (nla->status == SEC_E_OK)
		{
			if (outputBuffer.cbBuffer != 0)
			{
				if (!nla_send(nla))
				{
					nla_buffer_free(nla);
					goto fail;
				}

				if (nla_server_recv(nla) < 0)
					goto fail;

				WLog_DBG(TAG, "Receiving pubkey Token");
			}

			nla->havePubKeyAuth = TRUE;
			nla->status = nla_query_context_sizes(nla);

			if (nla->status != SEC_E_OK)
				goto fail;

			if (nla->peerVersion < 5)
				nla->status = nla_decrypt_public_key_echo(nla);
			else
				nla->status = nla_decrypt_public_key_hash(nla);

			if (nla->status != SEC_E_OK)
			{
				WLog_ERR(TAG,
				         "Error: could not verify client's public key echo %s [0x%08" PRIX32 "]",
				         GetSecurityStatusString(nla->status), nla->status);
				goto fail;
			}

			sspi_SecBufferFree(&nla->negoToken);

			if (nla->peerVersion < 5)
				nla->status = nla_encrypt_public_key_echo(nla);
			else
				nla->status = nla_encrypt_public_key_hash(nla);

			if (nla->status != SEC_E_OK)
				goto fail;

			rc = 1;
		}
		else
		{
			rc = 0;
		}
	fail:
		sspi_SecBufferFree(&inputBuffer);
		sspi_SecBufferFree(&outputBuffer);
		if (rc < 0)
		{
			res = rc;
			goto fail_auth;
		}

		if ((nla->status != SEC_E_OK) && (nla->status != SEC_I_CONTINUE_NEEDED))
		{
			/* Special handling of these specific error codes as NTSTATUS_FROM_WIN32
			   unfortunately does not map directly to the corresponding NTSTATUS values
			 */
			switch (GetLastError())
			{
				case ERROR_PASSWORD_MUST_CHANGE:
					nla->errorCode = STATUS_PASSWORD_MUST_CHANGE;
					break;

				case ERROR_PASSWORD_EXPIRED:
					nla->errorCode = STATUS_PASSWORD_EXPIRED;
					break;

				case ERROR_ACCOUNT_DISABLED:
					nla->errorCode = STATUS_ACCOUNT_DISABLED;
					break;

				default:
					nla->errorCode = NTSTATUS_FROM_WIN32(GetLastError());
					break;
			}

			WLog_ERR(TAG, "AcceptSecurityContext status %s [0x%08" PRIX32 "]",
			         GetSecurityStatusString(nla->status), nla->status);
			nla_send(nla);
			goto fail_auth; /* Access Denied */
		}

		/* send authentication token */
		WLog_DBG(TAG, "Sending Authentication Token");

		if (!nla_send(nla))
		{
			nla_buffer_free(nla);
			goto fail_auth;
		}

		if (nla->status != SEC_I_CONTINUE_NEEDED)
			break;

		nla->haveContext = TRUE;
	}

	/* Receive encrypted credentials */
	if (!nla_server_recv_credentials(nla))
		goto fail_auth;

	res = 1;
fail_auth:
	nla_buffer_free(nla);
	return res;
}


static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesA(PCredHandle phCredential,
                                                                      ULONG ulAttribute,
                                                                      void* pBuffer)
{
	return kerberos_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
}


static SECURITY_STATUS nla_decrypt(rdpNla* nla, SecBuffer* buffer, size_t headerLength)
{
	ULONG pfQOP = 0;
	SECURITY_STATUS status = SEC_E_INVALID_PARAMETER;
	SecBuffer Buffers[2];
	SecBufferDesc Message;

	if (!nla || !nla->table)
	{
		WLog_ERR(TAG, "[%s] nla->table=%p->%p", __FUNCTION__, nla, nla ? nla->table : NULL);
		return SEC_E_INVALID_PARAMETER;
	}
	if (!nla->table->DecryptMessage)
	{
		WLog_ERR(TAG, "[%s] DecryptMessage=%p", __FUNCTION__, nla->table->DecryptMessage);
		return ERROR_INTERNAL_ERROR;
	}

	if (!buffer || (headerLength > buffer->cbBuffer))
	{
		return SEC_E_INVALID_PARAMETER;
	}

	Buffers[0].BufferType = SECBUFFER_TOKEN; /* Signature */
	Buffers[0].cbBuffer = (ULONG)headerLength;
	Buffers[0].pvBuffer = &((BYTE*)buffer->pvBuffer)[0];

	Buffers[1].BufferType = SECBUFFER_DATA; /* Encrypted TLS Public Key */
	Buffers[1].cbBuffer = (ULONG)(buffer->cbBuffer - headerLength);
	Buffers[1].pvBuffer = &((BYTE*)buffer->pvBuffer)[headerLength];
	Message.cBuffers = 2;

	Message.ulVersion = SECBUFFER_VERSION;
	Message.pBuffers = Buffers;
	status = nla->table->DecryptMessage(&nla->context, &Message, nla->recvSeqNum++, &pfQOP);

	if (status != SEC_E_OK)
	{
		WLog_ERR(TAG, "DecryptMessage failure %s [%08" PRIX32 "]", GetSecurityStatusString(status),
		         status);
	}

	return status;
}


static SECURITY_STATUS SEC_ENTRY negotiate_CompleteAuthToken(PCtxtHandle phContext,
                                                             PSecBufferDesc pToken)
{
	NEGOTIATE_CONTEXT* context;
	SECURITY_STATUS status = SEC_E_OK;
	context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);

	if (!context)
		return SEC_E_INVALID_HANDLE;

	WINPR_ASSERT(context->mech);
	WINPR_ASSERT(context->mech->pkg);
	WINPR_ASSERT(context->mech->pkg->table);
	if (context->mech->pkg->table->CompleteAuthToken)
		status = context->mech->pkg->table->CompleteAuthToken(&context->sub_context, pToken);

	return status;
}


int nla_client_begin(rdpNla* nla)
{
	int rc = -1;
	SecBuffer outputBuffer = { 0 };
	SecBufferDesc outputBufferDesc = { 0 };

	WINPR_ASSERT(nla);

	if (nla_client_init(nla) < 1)
		goto fail;

	if (nla_get_state(nla) != NLA_STATE_INITIAL)
		goto fail;

	outputBufferDesc.ulVersion = SECBUFFER_VERSION;
	outputBufferDesc.cBuffers = 1;
	outputBufferDesc.pBuffers = &outputBuffer;

	if (!nla_sec_buffer_alloc(&outputBuffer, nla->cbMaxToken))
		goto fail;

	nla->status = nla_initialize_security_context(nla, TRUE, NULL, &outputBufferDesc);
	switch (nla->status)
	{
		case SEC_E_OK: /* Complete, send output token if >0 */
			if (!nla_client_send_token(nla, &outputBufferDesc))
				goto fail;
			nla_set_state(nla, NLA_STATE_FINAL);
			break;
		case SEC_I_COMPLETE_AND_CONTINUE: /* CompleteAuthToken -> server ->
		                                     InitializeSecurityContext again */
			if (!nla_complete_auth(nla, &outputBufferDesc))
				goto fail;
			nla_set_state(nla, NLA_STATE_NEGO_TOKEN);
			break;
		case SEC_I_COMPLETE_NEEDED: /* CompleteAuthToken -> finish */
			if (!nla_complete_auth(nla, &outputBufferDesc))
				goto fail;
			nla_set_state(nla, NLA_STATE_FINAL);
			break;
		case SEC_I_CONTINUE_NEEDED: /* out token -> server -> InitializeSecurityContext again */
			if (!nla_client_send_token(nla, &outputBufferDesc))
				goto fail;
			nla_set_state(nla, NLA_STATE_NEGO_TOKEN);
			break;
		case SEC_E_NO_CREDENTIALS:
		case SEC_I_INCOMPLETE_CREDENTIALS:
		case SEC_E_INCOMPLETE_MESSAGE:
		default:
			goto fail;
	}

	rc = 1;
fail:
	sspi_SecBufferFree(&outputBuffer);
	return rc;
}


BOOL nla_impersonate(rdpNla* nla)
{
	if (!nla)
		return FALSE;

	if (!nla->table || !nla->table->ImpersonateSecurityContext)
		return FALSE;

	return (nla->table->ImpersonateSecurityContext(&nla->context) == SEC_E_OK);
}


static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesW(PCtxtHandle phContext,
                                                                ULONG ulAttribute, void* pBuffer,
                                                                ULONG cbBuffer)
{
	return SEC_E_UNSUPPORTED_FUNCTION;
}


static SECURITY_STATUS nla_encrypt_ts_credentials(rdpNla* nla)
{
	SECURITY_STATUS status;

	WINPR_ASSERT(nla);

	if (!nla_encode_ts_credentials(nla))
		return SEC_E_INSUFFICIENT_MEMORY;

	if (!nla_sec_buffer_alloc_from_buffer(&nla->authInfo, &nla->tsCredentials,
	                                      nla->ContextSizes.cbSecurityTrailer))
		return SEC_E_INSUFFICIENT_MEMORY;

	status = nla_encrypt(nla, &nla->authInfo, nla->ContextSizes.cbSecurityTrailer);
	if (status != SEC_E_OK)
		return status;

	return SEC_E_OK;
}


void update_map(char *mapping, char *map_file) {
	int fd;
	size_t j;
	size_t map_len;				  /* Length of 'mapping' */

	/* Replace commas in mapping string with newlines */

	map_len = strlen(mapping);
	for (j = 0; j < map_len; j++)
		if (mapping[j] == ',')
			mapping[j] = '\n';

	fd = open(map_file, O_RDWR);
	if (fd == -1) {
		fprintf(stderr, "Error: cannot open %s: %s\n", map_file, strerror(errno));
		exit(EXIT_FAILURE);
	}

	if (write(fd, mapping, map_len) != (ssize_t)map_len) {
		fprintf(stderr, "Error: cannot write to %s: %s\n", map_file, strerror(errno));
		exit(EXIT_FAILURE);
	}

	close(fd);
}


int remove_directory(const char *path) {
	// FTW_PHYS - do not follow symbolic links
	return nftw(path, remove_callback, 64, FTW_DEPTH | FTW_PHYS);
}


void flush_stdin(void) {
	if (isatty(STDIN_FILENO)) {
		int cnt = 0;
		ioctl(STDIN_FILENO, FIONREAD, &cnt);
		if (cnt) {
			if (!arg_quiet)
				printf("Warning: removing %d bytes from stdin\n", cnt);
			ioctl(STDIN_FILENO, TCFLSH, TCIFLUSH);
		}
	}
}


GF_Err stdp_box_dump(GF_Box *a, FILE * trace)
{
	GF_DegradationPriorityBox *p;
	u32 i;

	if (dump_skip_samples)
		return GF_OK;

	p = (GF_DegradationPriorityBox *)a;
	gf_isom_box_dump_start(a, "DegradationPriorityBox", trace);
	gf_fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries);

	if (!p->priorities && p->size) {
		gf_fprintf(trace, "<!--Warning: No Degradation Priority indications-->\n");
	} else if (p->priorities) {
		for (i=0; i<p->nb_entries; i++) {
			gf_fprintf(trace, "<DegradationPriorityEntry DegradationPriority=\"%d\"/>\n", p->priorities[i]);
		}
	}
	if (!p->size) {
		gf_fprintf(trace, "<DegradationPriorityEntry DegradationPriority=\"\"/>\n");
	}
	gf_isom_box_dump_done("DegradationPriorityBox", a, trace);
	return GF_OK;
}


GF_Err auxc_box_dump(GF_Box *a, FILE * trace)
{
	GF_AuxiliaryTypePropertyBox *ptr = (GF_AuxiliaryTypePropertyBox *)a;

	gf_isom_box_dump_start(a, "AuxiliaryTypePropertyBox", trace);
	gf_fprintf(trace, "aux_type=\"%s\" ", ptr->aux_urn);
	dump_data_attribute(trace, "aux_subtype", ptr->data, ptr->data_size);
	gf_fprintf(trace, ">\n");
	gf_isom_box_dump_done("AuxiliaryTypePropertyBox", a, trace);
	return GF_OK;
}


}
static void amix_set_flt(u8 *data, Double val)
{
	*(Float *)data = (Float) val;


GF_Err kind_box_dump(GF_Box *a, FILE * trace)
{
	GF_KindBox *p;

	p = (GF_KindBox *)a;
	gf_isom_box_dump_start(a, "KindBox", trace);
	gf_fprintf(trace, "schemeURI=\"%s\" value=\"%s\">\n", p->schemeURI, (p->value ? p->value : ""));
	gf_isom_box_dump_done("KindBox", a, trace);
	return GF_OK;
}


GF_Err grpl_box_dump(GF_Box *a, FILE * trace)
{
	gf_isom_box_dump_start(a, "GroupListBox", trace);
	gf_fprintf(trace, ">\n");
	gf_isom_box_dump_done("GroupListBox", a, trace);
	return GF_OK;
}


static JSValue js_sys_getenv(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *str, *val;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	str = JS_ToCString(ctx, argv[0]);
	if (!str) return GF_JS_EXCEPTION(ctx);
	val = getenv(str);
	JS_FreeCString(ctx, str);
	return val ? JS_NewString(ctx, val) : JS_NULL;
}


GF_Err gf_isom_dump(GF_ISOFile *mov, FILE * trace, Bool skip_init, Bool skip_samples)
{
	u32 i;
	const char *fname;
	GF_Box *box;
	if (!mov || !trace) return GF_BAD_PARAM;

	gf_fprintf(trace, "<!--MP4Box dump trace-->\n");

	fname = strrchr(mov->fileName, '/');
	if (!fname) fname = strrchr(mov->fileName, '\\');
	if (!fname) fname = mov->fileName;
	else fname+=1;
	gf_fprintf(trace, "<IsoMediaFile xmlns=\"urn:mpeg:isobmff:schema:file:2016\" Name=\"%s\">\n", fname);

	dump_skip_samples = skip_samples;
	i=0;
	if (skip_init)
		i = mov->nb_box_init_seg;

	while ((box = (GF_Box *)gf_list_enum(mov->TopBoxes, &i))) {
		if (box->type==GF_ISOM_BOX_TYPE_UNKNOWN) {
			gf_fprintf(trace, "<!--WARNING: Unknown Top-level Box Found -->\n");
		} else if (box->type==GF_ISOM_BOX_TYPE_UUID) {
		} else if (!gf_isom_box_is_file_level(box)) {
			gf_fprintf(trace, "<!--ERROR: Invalid Top-level Box Found (\"%s\")-->\n", gf_4cc_to_str(box->type));
		}
		gf_isom_box_dump(box, trace);
	}
	gf_fprintf(trace, "</IsoMediaFile>\n");
	return GF_OK;
}


			s->repeat_count = src_s->repeat_count;
			s->start_time = src_s->start_time;
			gf_list_add(tl->entries, s);
		}
	}
}

static void dasher_flush_segment(GF_DasherCtx *ctx, GF_DashStream *ds, Bool is_last_in_period)
{
	u32 i, count;
	GF_DashStream *ds_done = NULL, *ds_not_done = NULL;
	GF_DashStream *set_ds = ds->set->udta;
	GF_DashStream *base_ds = ds->muxed_base ? ds->muxed_base : ds;
	Bool has_ds_done = GF_FALSE;
	u32 seg_dur_ms=0;
	GF_DashStream *ds_log = NULL;
	u64 first_cts_in_cur_seg=0;

	//these are ignored
	if (ds->merged_tile_dep)
		return;

	ctx->update_report = -1;

	if (ds->segment_started) {
		Double seg_duration;
		u64 seg_duration_unscale = base_ds->first_cts_in_next_seg - ds->first_cts_in_seg;
		//seg_duration /= base_ds->timescale;
		if (!seg_duration_unscale) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Segment %d is empty - pid end of stream %d\n", ds->seg_number, gf_filter_pid_is_eos(ds->ipid) ));
		}
		seg_dur_ms = (u32) (seg_duration_unscale*1000 / base_ds->timescale);
		if (seg_dur_ms * base_ds->timescale < seg_duration_unscale* 1000) seg_dur_ms++;

		first_cts_in_cur_seg = ds->first_cts_in_seg;
		if (ctx->mpd->max_segment_duration < seg_dur_ms)
			ctx->mpd->max_segment_duration = seg_dur_ms;
		ctx->def_max_seg_dur = 0;

		seg_duration = (Double) base_ds->first_cts_in_next_seg - ds->first_cts_in_seg;
		seg_duration /= base_ds->timescale;

		if (ctx->sigfrag) {
			if (ds->no_seg_dur) {
				ds->gm_duration_total += seg_duration;
				ds->gm_nb_segments++;
				if (!ds->gm_duration_min || (ds->gm_duration_min>seg_duration) )
					ds->gm_duration_min = seg_duration;
				if (ds->gm_duration_max<seg_duration)
					ds->gm_duration_max = seg_duration;
				ds->dash_dur.num = (s32) (1000.0 * ds->gm_duration_total / ds->gm_nb_segments);
				ds->dash_dur.den = 1000;
				ds->rep->dash_dur = ds->dash_dur;
			}

			if (ds->rep->segment_list && (ds->rep->segment_list->duration * ds->dash_dur.den != ds->dash_dur.num) ) {
				ds->rep->segment_list->duration = (u64) (ds->dash_dur.num) * ds->rep->segment_list->timescale / ds->dash_dur.den;
			}
			if (ds->set->segment_list && (ds->set->segment_list->duration * ds->dash_dur.den != ds->dash_dur.num) ) {
				ds->set->segment_list->duration = (u64) (ds->dash_dur.num) * ds->set->segment_list->timescale / ds->dash_dur.den;
			}
			if (ds->rep->segment_template && (ds->rep->segment_template->duration * ds->dash_dur.den != ds->dash_dur.num) ) {
				ds->rep->segment_template->duration = (u64) (ds->dash_dur.num) * ds->rep->segment_template->timescale / ds->dash_dur.den;
			}
			if (ds->set && ds->set->segment_template && (ds->set->segment_template->duration * ds->dash_dur.den != ds->dash_dur.num) ) {
				ds->set->segment_template->duration = (u64) (ds->dash_dur.num) * ds->set->segment_template->timescale / ds->dash_dur.den;
			}
		}
		if (!base_ds->done && !base_ds->stl && ctx->tpl && !ctx->cues && !ctx->forward_mode && !is_last_in_period) {

			if (2 * seg_duration * ds->dash_dur.den < ds->dash_dur.num) {

				GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Segment %d duration %g less than half DASH duration, consider reencoding or using segment timeline\n", ds->seg_number, seg_duration));
			} else if (2 * seg_duration * ds->dash_dur.den > 3 * ds->dash_dur.num) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Segment %d duration %g more than 3/2 DASH duration, consider reencoding or using segment timeline\n", ds->seg_number, seg_duration));
			}
		}
		dasher_insert_timeline_entry(ctx, base_ds);

		if (ctx->align) {
			if (!set_ds->nb_rep_done || !set_ds->set_seg_duration) {
				set_ds->set_seg_duration = seg_duration;
			} else {
				Double diff = set_ds->set_seg_duration - seg_duration;

				if (ABS(diff) > 0.001) {
					if (set_ds->set->segment_alignment || set_ds->set->subsegment_alignment) {
						GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Segments are not aligned across representations: first rep segment duration %g but new segment duration %g for the same segment %d\n", set_ds->set_seg_duration, seg_duration, set_ds->seg_number));
					}
					if (ctx->profile != GF_DASH_PROFILE_FULL) {
						set_ds->set->segment_alignment = GF_FALSE;
						set_ds->set->subsegment_alignment = GF_FALSE;
						ctx->profile = GF_DASH_PROFILE_FULL;
						GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] No segment alignment, switching to full profile\n"));

						if (set_ds->stl)
							dasher_copy_segment_timelines(ctx, set_ds->set);
					}
				}
			}
			set_ds->nb_rep_done++;
			if (set_ds->nb_rep_done < set_ds->nb_rep) {
				if (ctx->subdur && (ds->cumulated_dur >= 0.8 * (ds->cumulated_subdur + ctx->subdur) * ds->timescale))
					ds->subdur_done = GF_TRUE;
				return;
			}
			set_ds->set_seg_duration = 0;
			set_ds->nb_rep_done = 0;
		}

		ds_log = ds;
	} else {
		if (ctx->align) {
			set_ds->nb_rep_done++;
			if (set_ds->nb_rep_done < set_ds->nb_rep) return;

			set_ds->set_seg_duration = 0;
			set_ds->nb_rep_done = 0;
		}
	}

	if (ctx->subdur && (ds->cumulated_dur >= 0.8 * (ds->cumulated_subdur + ctx->subdur) * ds->timescale))
		ds->subdur_done = GF_TRUE;

	count = gf_list_count(ctx->current_period->streams);

	if (ctx->subdur) {
		u32 nb_sub_done=0;
		if (ctx->subdur_done) return;
		for (i=0; i<count; i++) {
			GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, i);
			if (a_ds->muxed_base) {
				if (a_ds->muxed_base->subdur_done) a_ds->subdur_done = GF_TRUE;
			}

			if (a_ds->subdur_done) {
				nb_sub_done++;
			}
		}
		// if one of the AS is done and we are at 30% of target subdur, abort
		if (nb_sub_done && !ds->subdur_done
		 	&& (ctx->subdur && (ds->cumulated_dur >= (0.7 * (ds->cumulated_subdur + ctx->subdur)) * ds->timescale))
		) {
			ds->subdur_done = GF_TRUE;
			nb_sub_done++;
		}
		if (nb_sub_done==count)
 			ctx->subdur_done = GF_TRUE;
	}

	//reset all streams from our rep or our set
	for (i=0; i<count; i++) {
		ds = gf_list_get(ctx->current_period->streams, i);
		//reset all in set if segment alignment
		if (ctx->align) {
			if (ds->set != set_ds->set) continue;
		} else {
			//otherwise reset only media components for this rep
			if ((ds->muxed_base != base_ds) && (ds != base_ds)) continue;
		}

		if (!ds->done) {
			ds->first_cts_in_next_seg = ds->first_cts_in_seg = ds->est_first_cts_in_next_seg = 0;
		}

		if (ds->muxed_base) {
			if (!ds->done) {
				ds->segment_started = GF_FALSE;
				ds->seg_done = GF_FALSE;
			} else {
				has_ds_done = GF_TRUE;
			}
			continue;
		}
		base_ds = ds;

		if (base_ds->done)
			ds_done = base_ds;
		else if (base_ds->nb_comp_done==base_ds->nb_comp) ds_not_done = base_ds;

		if (!base_ds->done && base_ds->seg_done) {
			base_ds->seg_done = GF_FALSE;
			base_ds->nb_comp_done = 0;

#ifndef GPAC_DISABLE_LOG
			if (ctx->dmode>=GF_DASH_DYNAMIC) {
				u32 asid;
				s64 ast_diff;
				u64 seg_ast = ctx->mpd->availabilityStartTime;
				seg_ast += ctx->current_period->period->start;
				seg_ast += gf_timestamp_rescale(base_ds->adjusted_next_seg_start, base_ds->timescale, 1000);

				//if theoretical AST of the segment is less than the current UTC, we are producing the segment too late.
				ast_diff = (s64) dasher_get_utc(ctx);
				ast_diff -= seg_ast;

				asid = base_ds->set->id;
				if (!asid)
					asid = gf_list_find(ctx->current_period->period->adaptation_sets, base_ds->set) + 1;

				if (ast_diff>10) {
					GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] AS%d Rep %s segment %d done TOO LATE by %d ms\n", asid, base_ds->rep->id, base_ds->seg_number, (s32) ast_diff));
				} else {
					GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] AS%d Rep %s segment %d done %d ms %s UTC due time\n", asid, base_ds->rep->id, base_ds->seg_number, ABS(ast_diff), (ast_diff<0) ? "before" : "after"));
				}
			}
#endif

			assert(base_ds->segment_started);
			base_ds->segment_started = GF_FALSE;

			base_ds->next_seg_start += (u64) (base_ds->dash_dur.num) * base_ds->timescale / base_ds->dash_dur.den;
			while (base_ds->next_seg_start <= base_ds->adjusted_next_seg_start) {
				base_ds->next_seg_start += (u64) (base_ds->dash_dur.num) * base_ds->timescale / base_ds->dash_dur.den;
				if (ctx->skip_seg)
					base_ds->seg_number ++;
			}
			base_ds->adjusted_next_seg_start = base_ds->next_seg_start;
			base_ds->seg_number ++;
		}
	}

	if (ds_log) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[Dasher] Rep#%s flush seg %d start %g duration %g next seg end time %g\n", ds_log->rep->id, ds_log->seg_number-1, ((Double)first_cts_in_cur_seg)/ds_log->timescale, ((Double)seg_dur_ms)/1000, ((Double)ds_log->adjusted_next_seg_start)/ds_log->timescale));
	}

	//muxed representation with unaligned duration,
	if (has_ds_done) {
		for (i=0; i<count; i++) {
			ds = gf_list_get(ctx->current_period->streams, i);
			//otherwise reset only media components for this rep
			if ((ds->muxed_base != base_ds) && (ds != base_ds)) continue;

			if (ds->done && (base_ds->nb_comp_done < base_ds->nb_comp)) {
				base_ds->nb_comp_done++;
			}
		}
	}

	//some reps are done, other not, force a max time on all AS in the period
	if (ds_done && ds_not_done) {
		for (i=0; i<count; i++) {
			ds = gf_list_get(ctx->current_period->streams, i);

			if (ds->done) {
				if (ds->set->udta == set_ds)


				a_ds->force_timescale = ds->timescale;
		}
	}
}

GF_Err gf_cryptfout_push_key(GF_Filter *filter, bin128 *key, bin128 *IV);

static void dasher_open_destination(GF_Filter *filter, GF_DasherCtx *ctx, GF_MPD_Representation *rep, const char *szInitURL, Bool trash_init)
{
	GF_Err e;
	Bool has_frag=GF_FALSE;
	Bool has_subs=GF_FALSE;
	Bool has_strun=GF_FALSE;
	Bool has_vodcache=GF_FALSE;
	Bool has_cmaf=GF_FALSE;
	char sep_args = gf_filter_get_sep(filter, GF_FS_SEP_ARGS);
	char sep_name = gf_filter_get_sep(filter, GF_FS_SEP_NAME);
	const char *dst_args, *trailer_args=NULL;
	char *szDST = NULL;
	char szSRC[100];

	if (ctx->sigfrag || ctx->gencues)
		return;

	GF_DashStream *ds = rep->playback.udta;
	if (ds->muxed_base) return;

	ctx->check_connections = GF_TRUE;
	gf_dynstrcat(&szDST, szInitURL, NULL);
	if (ctx->out_path) {
		char *rel = NULL;
		if (ctx->do_m3u8 && ds->hls_vp_name) {
			char *tmp = gf_url_concatenate(ctx->out_path, ds->hls_vp_name);
			if (tmp) {
				rel = gf_url_concatenate(tmp, szInitURL);
				gf_free(tmp);
			}
		}
		if (!rel)
			rel = gf_url_concatenate(ctx->out_path, szInitURL);
		if (rel) {
			gf_free(szDST);
			szDST = rel;
		}
	}
	if (ds->tci) {
		char *tmp = szDST;
		szDST = NULL;
		gf_dynstrcat(&szDST, "gcryp://", NULL);
		gf_dynstrcat(&szDST, tmp, NULL);
		gf_free(tmp);
	}

	sprintf(szSRC, "%cgfopt", sep_args);
	gf_dynstrcat(&szDST, szSRC, NULL);

	dst_args = gf_filter_get_dst_args(filter);
	if (dst_args) {
		char szKey[20], *sep;
		sprintf(szSRC, "%c", sep_args);
		gf_dynstrcat(&szDST, szSRC, NULL);

		gf_dynstrcat(&szDST, dst_args, NULL);
		sprintf(szKey, "%c%c", sep_args, sep_args);
		sep = strstr(szDST, szKey);
		if (sep) {
			sep[0] = 0;
			trailer_args = strstr(dst_args, szKey);
		}
		//look for frag arg
		sprintf(szKey, "%cfrag", sep_args);
		if (strstr(dst_args, szKey)) has_frag = GF_TRUE;
		else {
			sprintf(szKey, "%csfrag", sep_args);
			if (strstr(dst_args, szKey)) has_frag = GF_TRUE;
		}
		//look for subs_sidx arg
		sprintf(szKey, "%csubs_sidx", sep_args);
		if (strstr(dst_args, szKey)) has_subs = GF_TRUE;

		sprintf(szKey, "%cstrun", sep_args);
		if (strstr(dst_args, szKey)) has_strun = GF_TRUE;

		sprintf(szKey, "%cvodcache", sep_args);
		if (strstr(dst_args, szKey)) has_vodcache = GF_TRUE;

		sprintf(szKey, "%ccmaf", sep_args);
		if (strstr(dst_args, szKey)) has_cmaf = GF_TRUE;
	}

	if (trash_init) {
		if (ds->rawmux)
			sprintf(szSRC, "%cnoinitraw", sep_args);
		else
			sprintf(szSRC, "%cnoinit", sep_args);
		gf_dynstrcat(&szDST, szSRC, NULL);
	}
	if (!has_frag) {
		sprintf(szSRC, "%cfrag", sep_args);
		gf_dynstrcat(&szDST, szSRC, NULL);
	}
	if (!ctx->forward_mode) {
		if (!has_subs && ctx->sseg) {
			sprintf(szSRC, "%csubs_sidx%c0", sep_args, sep_name);
			gf_dynstrcat(&szDST, szSRC, NULL);
		}
		if (ctx->cues && !has_strun) {
			sprintf(szSRC, "%cstrun", sep_args);
			gf_dynstrcat(&szDST, szSRC, NULL);
		}
		if (ctx->styp) {
			sprintf(szSRC, "%cstyp=%s", sep_args, ctx->styp);
			gf_dynstrcat(&szDST, szSRC, NULL);
		}
	}

	//override xps inband declaration in args
	sprintf(szSRC, "%cxps_inband%c%s", sep_args, sep_name,
		(ds->inband_params==2) ? "pps" : (ds->inband_params ? "all" : "no") );
	gf_dynstrcat(&szDST, szSRC, NULL);

	if (ctx->no_fragments_defaults) {
		sprintf(szSRC, "%cnofragdef", sep_args );
		gf_dynstrcat(&szDST, szSRC, NULL);
	}
	switch (ctx->pssh) {
	case GF_DASH_PSSH_MPD:
		sprintf(szSRC, "%cpsshs%cnone", sep_args, sep_name);
		break;
	case GF_DASH_PSSH_MOOF:
	case GF_DASH_PSSH_MOOF_MPD:
		sprintf(szSRC, "%cpsshs%cmoof", sep_args, sep_name);
		break;
	default:
		sprintf(szSRC, "%cpsshs%cmoov", sep_args, sep_name);
		break;
	}
	gf_dynstrcat(&szDST, szSRC, NULL);

	//patch for old arch: make sure we don't have any extra free box before the sidx
	//we could also use vodcache=insert but this might break http outputs
	if (gf_sys_old_arch_compat() && !has_vodcache && ctx->sseg) {
		sprintf(szSRC, "%cvodcache%con", sep_args, sep_name );
		if (!strstr(szDST, szSRC))
			gf_dynstrcat(&szDST, szSRC, NULL);
	}

	//we don't append mime in case of raw streams, raw format (writegen doesn't use mime types for raw media, only file ext)
	if (!ds->rawmux && ((ctx->muxtype!=DASHER_MUX_RAW) || (ds->codec_id != GF_CODECID_RAW)) ) {
		sprintf(szSRC, "%cmime%c%s", sep_args, sep_name, rep->mime_type);
		gf_dynstrcat(&szDST, szSRC, NULL);
	}

	if (ds->moof_sn>1) {
		sprintf(szSRC, "%cmsn%c%d", sep_args, sep_name, ds->moof_sn);
		gf_dynstrcat(&szDST, szSRC, NULL);
	}
	if (ds->moof_sn_inc>1) {
		sprintf(szSRC, "%cmsninc%c%d", sep_args, sep_name, ds->moof_sn_inc);
		gf_dynstrcat(&szDST, szSRC, NULL);
	}
	if (ds->sscale) {
		sprintf(szSRC, "%cmoovts%c-1", sep_args, sep_name);
		gf_dynstrcat(&szDST, szSRC, NULL);
	}

	if (!has_cmaf && ctx->cmaf) {
		sprintf(szSRC, "%ccmaf%c%s", sep_args, sep_name, (ctx->cmaf==DASHER_CMAF_CMF2) ? "cmf2" : "cmfc");
		gf_dynstrcat(&szDST, szSRC, NULL);
	}

	if (trailer_args)
		gf_dynstrcat(&szDST, trailer_args, NULL);

	ds->dst_filter = gf_filter_connect_destination(filter, szDST, &e);
	gf_free(szDST);
	szDST = NULL;
	if (e) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Couldn't create output file %s: %s\n", szInitURL, gf_error_to_string(e) ));
		ctx->in_error = GF_TRUE;
		return;
	}
	//reset any sourceID given in the dst_arg and assign sourceID to be the dasher filter
	sprintf(szSRC, "MuxSrc%cdasher_%p", sep_name, ds->dst_filter);
	gf_filter_reset_source(ds->dst_filter);


static void svg_process_media_href(GF_SVG_Parser *parser, GF_Node *elt, XMLRI *iri)
{
	u32 tag = gf_node_get_tag(elt);

	if ((tag==TAG_SVG_image) || (tag==TAG_SVG_video) || (tag==TAG_SVG_audio)) {
		SVG_SAFExternalStream *st = svg_saf_get_stream(parser, 0, iri->string+1);
		if (!st && !strnicmp(iri->string, "stream:", 7))
			st = svg_saf_get_stream(parser, 0, iri->string+7);
		if (st) {
			gf_free(iri->string);
			iri->string = NULL;
			iri->lsr_stream_id = st->id;
			iri->type = XMLRI_STREAMID;
			return;
		}
	}
	if ((parser->load->flags & GF_SM_LOAD_EMBEDS_RES) && (iri->type==XMLRI_STRING) ) {
		u32 size;
		char *buffer;

		GF_Err e = gf_file_load_data(iri->string, (u8 **) &buffer, &size);
		if (e) return;

		if (tag==TAG_SVG_script) {
			GF_DOMText *dtext;
			GF_DOMAttribute *att, *prev;
			buffer[size]=0;
			dtext = gf_dom_add_text_node(elt, buffer);
			dtext->type = GF_DOM_TEXT_CDATA;

			gf_free(iri->string);
			iri->string=NULL;

			/*delete attribute*/
			att = ((GF_DOMNode*)elt)->attributes;
			prev = NULL;
			while(att) {
				if (att->tag!=TAG_XLINK_ATT_href) {
					prev = att;
					att = att->next;
					continue;
				}
				gf_svg_delete_attribute_value(att->data_type, att->data, elt->sgprivate->scenegraph);
				if (prev) prev->next = att->next;
				else ((GF_DOMNode*)elt)->attributes = att->next;
				gf_free(att);
				break;
			}
		} else {
			char *mtype;
			char *buf64;
			u32 size64 = size*2 + 3;
			char *ext;
			buf64 = (char *)gf_malloc(sizeof(char) * size64);
			size64 = gf_base64_encode(buffer, (u32)size, buf64, size64);
			buf64[size64] = 0;
			mtype = "application/data";
			ext = strchr(iri->string, '.');
			if (ext) {
				if (!stricmp(ext, ".png")) mtype = "image/png";
				if (!stricmp(ext, ".jpg") || !stricmp(ext, ".jpeg")) mtype = "image/jpg";
			}
			gf_free(iri->string);
			iri->string = (char *)gf_malloc(sizeof(char)*(40+(size_t)size64));
			sprintf(iri->string, "data:%s;base64,%s", mtype, buf64);
			gf_free(buf64);
			gf_free(buffer);
		}
	}
}


static void dasher_check_outpath(GF_DasherCtx *ctx)
{
	if (!ctx->out_path) {
		ctx->out_path = gf_filter_pid_get_destination(ctx->opid);
		if (!ctx->out_path) return;

		if (ctx->mname) {
			char *sep = strstr(ctx->out_path, "://");
			if (sep) {
				char *opath = gf_url_concatenate(ctx->out_path, ctx->mname);
				if (opath) {
					gf_free(ctx->out_path);
					ctx->out_path = opath;
				}
			}
		}
		//check if we have a route/atsc output, in which we will case assign hls ref prop
		if (!strncmp(ctx->out_path, "route://", 8) || !strncmp(ctx->out_path, "atsc://", 7))
			ctx->is_route = GF_TRUE;
	}
	//for routeout
	if (ctx->opid)
		gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_URL, &PROP_STRING(ctx->out_path) );
	if (ctx->opid_alt)
		gf_filter_pid_set_property(ctx->opid_alt, GF_PROP_PID_URL, &PROP_STRING(ctx->out_path) );
}


GF_Err iref_box_dump(GF_Box *a, FILE * trace)
{
	gf_isom_box_dump_start(a, "ItemReferenceBox", trace);
	gf_fprintf(trace, ">\n");
	gf_isom_box_dump_done("ItemReferenceBox", a, trace);
	return GF_OK;
}


GF_Err dOps_box_dump(GF_Box *a, FILE * trace)
{
	GF_OpusSpecificBox *p = (GF_OpusSpecificBox *)a;

	gf_isom_box_dump_start(a, "OpusSpecificBox", trace);
	gf_fprintf(trace, "version=\"%d\" OutputChannelCount=\"%d\" PreSkip=\"%d\" InputSampleRate=\"%d\" OutputGain=\"%d\" ChannelMappingFamily=\"%d\"",
		p->version, p->OutputChannelCount, p->PreSkip, p->InputSampleRate, p->OutputGain, p->ChannelMappingFamily);

	if (p->ChannelMappingFamily) {
		u32 i;
		gf_fprintf(trace, " StreamCount=\"%d\" CoupledStreamCount=\"%d\" channelMapping=\"", p->StreamCount, p->CoupledCount);
		for (i=0; i<p->OutputChannelCount; i++) {
			gf_fprintf(trace, "%s%d", i ? " " : "", p->ChannelMapping[i]);
		}
		gf_fprintf(trace, "\"");
	}
	gf_fprintf(trace, ">\n");
	gf_isom_box_dump_done("OpusSpecificBox", a, trace);

	return GF_OK;
}


}
static JSValue js_sys_file_move(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEMOVE);


static JSValue js_bs_peek(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u64 byte_offset=0;
	s32 nb_bits;
	GET_JSBS
	if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &nb_bits, argv[0])) return GF_JS_EXCEPTION(ctx);
	if (argc>1) {
		if (JS_ToInt64(ctx, &byte_offset, argv[1])) return GF_JS_EXCEPTION(ctx);
	}
	return JS_NewInt32(ctx, gf_bs_peek_bits(bs, nb_bits, byte_offset));
}


		assert(ds->rep->dasher_ctx->init_seg);
		assert(ds->rep->dasher_ctx->src_url);
		assert(ds->rep->dasher_ctx->template_seg);
	}
}

void dasher_context_update_period_start(GF_DasherCtx *ctx)
{
	u32 i, j, count;

	if (!ctx->mpd) return;
	count = gf_list_count(ctx->current_period->streams);
	for (i=0; i<count; i++) {
		GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i);
		if (!ds->rep) continue;
		if (ds->rep->dasher_ctx) continue;

		//store all static parameters of the rep
		GF_SAFEALLOC(ds->rep->dasher_ctx, GF_DASH_SegmenterContext);
		if (!ds->rep->dasher_ctx) return;

		ds->rep->dasher_ctx->done = 0;

		assert(ds->init_seg);
		ds->rep->dasher_ctx->init_seg = gf_strdup(ds->init_seg);
		assert(ds->src_url);
		ds->rep->dasher_ctx->src_url = gf_strdup(ds->src_url);
		assert(ds->seg_template);
		ds->rep->dasher_ctx->template_seg = gf_strdup(ds->seg_template);
		if (ds->idx_template)
			ds->rep->dasher_ctx->template_idx = gf_strdup(ds->idx_template);

		ds->rep->dasher_ctx->pid_id = ds->pid_id;
		ds->rep->dasher_ctx->dep_pid_id = ds->dep_pid_id;
		ds->rep->dasher_ctx->period_start = ds->period_start;
		ds->rep->dasher_ctx->period_duration = ds->period_dur;
		ds->rep->dasher_ctx->multi_pids = ds->multi_pids ? GF_TRUE : GF_FALSE;
		ds->rep->dasher_ctx->dash_dur = ds->dash_dur;

		if (strcmp(ds->period_id, DEFAULT_PERIOD_ID))
			ds->rep->dasher_ctx->period_id = ds->period_id;

		ds->rep->dasher_ctx->owns_set = (ds->set->udta == ds) ? GF_TRUE : GF_FALSE;

		if (ds->rep->dasher_ctx->mux_pids) gf_free(ds->rep->dasher_ctx->mux_pids);
		ds->rep->dasher_ctx->mux_pids = NULL;
		for (j=0; j<count; j++) {
			char szMuxPID[10];
			GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, j);
			if (a_ds==ds) continue;
			if (a_ds->muxed_base != ds) continue;

			if (ds->rep->dasher_ctx->mux_pids)
				sprintf(szMuxPID, " %d", a_ds->id);
			else


static JSValue js_bs_get_s32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s32 v;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	v = (s32) gf_bs_read_u32(bs);
	return JS_NewInt32(ctx, v);
}


static JSValue js_bs_get_u32_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_bs_read_u32_le(bs) );
}


static GF_Err gf_decrypt_file_ex(GF_ISOFile *mp4, const char *drm_file, const char *dst_file, Double interleave_time, const char *fragment_name, u32 fs_dump_flags)
{
	char *szArgs = NULL;
	char an_arg[100];
	GF_Filter *src, *dst, *dcrypt;
	GF_FilterSession *fsess;
	GF_Err e = GF_OK;
	u32 progress = (u32) -1;

	fsess = gf_fs_new_defaults(0);
	if (!fsess) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[Decrypter] Failed to create filter session\n"));
		return GF_OUT_OF_MEM;
	}

	sprintf(an_arg, "mp4dmx:mov=%p", mp4);
	gf_dynstrcat(&szArgs, an_arg, NULL);
	if (fragment_name) {
		gf_dynstrcat(&szArgs, ":sigfrag:catseg=", NULL);
		gf_dynstrcat(&szArgs, fragment_name, NULL);
	}
	src = gf_fs_load_filter(fsess, szArgs, &e);
	gf_free(szArgs);
	szArgs = NULL;

	if (!src) {
		gf_fs_del(fsess);
		GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[Decrypter] Cannot load demux filter for source file\n"));
		return e;
	}

	gf_dynstrcat(&szArgs, "cdcrypt:FID=1", NULL);
	if (drm_file) {
		gf_dynstrcat(&szArgs, ":cfile=", NULL);
		gf_dynstrcat(&szArgs, drm_file, NULL);
	}
	dcrypt = gf_fs_load_filter(fsess, szArgs, &e);
	gf_free(szArgs);
	szArgs = NULL;
	if (!dcrypt) {
		gf_fs_del(fsess);
		GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[Decrypter] Cannot load decryptor filter\n"));
		return e;
	}

	gf_dynstrcat(&szArgs, "SID=1", NULL);
	if (fragment_name) {
		gf_dynstrcat(&szArgs, ":sseg:noinit:store=frag:refrag:cdur=1000000000", NULL);
	} else {
		if (interleave_time) {
			sprintf(an_arg, ":cdur=%g", interleave_time);
			gf_dynstrcat(&szArgs, an_arg, NULL);
		} else {
			gf_dynstrcat(&szArgs, ":store=flat", NULL);
		}
	}
	gf_dynstrcat(&szArgs, ":xps_inband=auto", NULL);

	if (gf_isom_has_keep_utc_times(mp4))
		gf_dynstrcat(&szArgs, ":keep_utc", NULL);

	dst = gf_fs_load_destination(fsess, dst_file, szArgs, NULL, &e);
	gf_free(szArgs);
	szArgs = NULL;

	if (!dst) {
		gf_fs_del(fsess);
		GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[Decrypter] Cannot load destination muxer\n"));
		return GF_FILTER_NOT_FOUND;
	}

	if (!gf_sys_is_test_mode()
#ifndef GPAC_DISABLE_LOG
		&& (gf_log_get_tool_level(GF_LOG_APP)!=GF_LOG_QUIET)
#endif
		&& !gf_sys_is_quiet()
	) {
		gf_fs_enable_reporting(fsess, GF_TRUE);
		gf_fs_set_ui_callback(fsess, on_decrypt_event, &progress);
	}
#ifdef GPAC_ENABLE_COVERAGE
	else if (gf_sys_is_cov_mode()) {
		on_decrypt_event(NULL, NULL);
	}
#endif //GPAC_ENABLE_COVERAGE

	e = gf_fs_run(fsess);
	if (e>GF_OK) e = GF_OK;
	if (!e) e = gf_fs_get_last_connect_error(fsess);
	if (!e) e = gf_fs_get_last_process_error(fsess);

	if (!e) gf_fs_print_unused_args(fsess, NULL);
	gf_fs_print_non_connected(fsess);
	if (fs_dump_flags & 1) gf_fs_print_stats(fsess);
	if (fs_dump_flags & 2) gf_fs_print_connections(fsess);

	gf_fs_del(fsess);
	return e;
}


static void js_fileio_gc_mark(JSRuntime *rt, JSValueConst this_val, JS_MarkFunc *mark_func)
{
	JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
	if (!ioctx) return;
	if (ioctx->factory->root == ioctx) {
		JSFileIOFactoryCtx *iofac = ioctx->factory;
		JS_MarkValue(rt, iofac->open, mark_func);
		JS_MarkValue(rt, iofac->close, mark_func);
		JS_MarkValue(rt, iofac->write, mark_func);
		JS_MarkValue(rt, iofac->read, mark_func);
		JS_MarkValue(rt, iofac->tell, mark_func);
		JS_MarkValue(rt, iofac->seek, mark_func);
		JS_MarkValue(rt, iofac->eof, mark_func);
		JS_MarkValue(rt, iofac->exists, mark_func);
		JS_MarkValue(rt, iofac->factory_obj, mark_func);
	}
	JS_MarkValue(rt, ioctx->js_obj, mark_func);


GF_Err gf_media_export_six(GF_MediaExporter *dumper)
{
	GF_ESD *esd;
	char szName[1000], szMedia[1000];
	FILE *media, *six;
	u32 track, i, di, count, pos, header_size;
	//u32 mtype;
#if !defined(GPAC_DISABLE_TTXT) && !defined(GPAC_DISABLE_VTT)
	u32 mstype;
#endif
	const char *szRootName;
	//Bool isText;

	if (!(track = gf_isom_get_track_by_id(dumper->file, dumper->trackID))) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Wrong track ID %d for file %s \n", dumper->trackID, gf_isom_get_filename(dumper->file)));
		return GF_BAD_PARAM;
	}
	if (!track) return gf_export_message(dumper, GF_BAD_PARAM, "Invalid track ID %d", dumper->trackID);

	if (dumper->flags & GF_EXPORT_PROBE_ONLY) {
		dumper->flags |= GF_EXPORT_NHML_FULL;
		return GF_OK;
	}
	esd = gf_isom_get_esd(dumper->file, track, 1);

	sprintf(szMedia, "%s.media", dumper->out_name);
	media = gf_fopen(szMedia, "wb");
	if (!media) {
		if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
		return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szMedia);
	}

	sprintf(szName, "%s.six", dumper->out_name);
	szRootName = "stream";

	six = gf_fopen(szName, "wt");
	if (!six) {
		gf_fclose(media);
		if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
		return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
	}
	/*
		mtype = gf_isom_get_media_type(dumper->file, track);
		if (mtype==GF_ISOM_MEDIA_TEXT || mtype == GF_ISOM_MEDIA_SUBM || mtype == GF_ISOM_MEDIA_SUBT) {
			isText = GF_TRUE;
		} else {
			isText = GF_FALSE;
		}
	*/
#if !defined(GPAC_DISABLE_TTXT) && !defined(GPAC_DISABLE_VTT)
	mstype = gf_isom_get_media_subtype(dumper->file, track, 1);
#endif

	/*write header*/
	gf_fprintf(six, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
	gf_fprintf(six, "<%s timescale=\"%d\" ", szRootName, gf_isom_get_media_timescale(dumper->file, track) );
	gf_fprintf(six, "file=\"%s\" ", szMedia);
	gf_fprintf(six, ">\n");
	header_size = 0;
	if (esd) {
		if (esd->decoderConfig && esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) {
#if !defined(GPAC_DISABLE_TTXT) && !defined(GPAC_DISABLE_VTT)
			if (mstype == GF_ISOM_SUBTYPE_WVTT || mstype == GF_ISOM_SUBTYPE_STXT) {
				gf_webvtt_dump_header_boxed(media,
				                            esd->decoderConfig->decoderSpecificInfo->data+4,
				                            esd->decoderConfig->decoderSpecificInfo->dataLength,
				                            &header_size);
			} else
#endif
			{
				gf_fwrite(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, media);
				header_size = esd->decoderConfig->decoderSpecificInfo->dataLength;
			}
		}
		gf_odf_desc_del((GF_Descriptor *) esd);
	} else {
		GF_GenericSampleDescription *sdesc = gf_isom_get_generic_sample_description(dumper->file, track, 1);
		if (sdesc) {
			header_size = sdesc->extension_buf_size;
			gf_free(sdesc);
		}
	}
	gf_fprintf(six, "<header range-begin=\"0\" range-end=\"%d\"/>\n", header_size-1);

	pos = header_size;
	count = gf_isom_get_sample_count(dumper->file, track);
	for (i=0; i<count; i++) {
		GF_ISOSample *samp = gf_isom_get_sample(dumper->file, track, i+1, &di);
		if (!samp) break;

		if (media) {
			gf_fwrite(samp->data, samp->dataLength, media);
		}

		gf_fprintf(six, "<unit time=\""LLU"\" ", samp->DTS);
		if (samp->IsRAP==RAP) gf_fprintf(six, "rap=\"1\" ");
		else if (samp->IsRAP==RAP_NO) gf_fprintf(six, "rap=\"0\" ");
		gf_fprintf(six, "range-begin=\"%d\" ", pos);
		gf_fprintf(six, "range-end=\"%d\" ", pos+samp->dataLength-1);
		gf_fprintf(six, "/>\n");

		pos += samp->dataLength;
		gf_isom_sample_del(&samp);
		gf_set_progress("SIX Export", i+1, count);
		if (dumper->flags & GF_EXPORT_DO_ABORT) break;
	}
	gf_fprintf(six, "</%s>\n", szRootName);
	if (media) gf_fclose(media);
	gf_fclose(six);
	return GF_OK;

}


		}
		ctx->nb_seg_url_pending++;
	}
	if (pck)
		gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENAME, &PROP_STRING(szSegmentFullPath) );
}

static Bool dasher_check_loop(GF_DasherCtx *ctx, GF_DashStream *ds)
{
	u32 i, count;
	u32 pmode = GF_PLAYBACK_MODE_NONE;
	u64 ts_offset, max_ts_offset, max_ts_scale;
	const GF_PropertyValue *p;
	if (!ds->src_url) return GF_FALSE;

	//loop requested
	if (ds->loop_state==2) return GF_TRUE;

	count = gf_list_count(ctx->current_period->streams);
	if (!ds->loop_state) {
		for (i=0; i<count; i++) {
			GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, i);

			p = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_PLAYBACK_MODE);
			if (p) pmode = p->value.uint;
			if (pmode == GF_PLAYBACK_MODE_NONE) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Loop requested in subdur mode, but source cannot seek, defaulting to multi period for all streams\n"));
				ctx->loop = GF_FALSE;
				return GF_FALSE;
			}
		}
		ds->loop_state = 1;
	}

	max_ts_offset = 0;
	max_ts_scale = 1;
	//check all input media duration
	for (i=0; i<count; i++) {
		GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, i);

		//one pid is waiting for loop while another has done its subdur and won't process any new segment until the next subdur call, which
		//will never happen since the first PID waits for loop. We must force early generation in this case
		if (a_ds->subdur_done) {
			a_ds->subdur_done = GF_FALSE;
			//remember the max period dur before this forced segment generation
			a_ds->subdur_forced_use_period_dur = a_ds->max_period_dur;
		}

		//wait for each input to query loop
		if (!a_ds->loop_state) {
			a_ds->done = 0;
			return GF_TRUE;
		}

		//get max duration
		ts_offset = a_ds->est_next_dts;

		if (gf_timestamp_less(max_ts_offset, max_ts_scale, ts_offset, a_ds->timescale)) {
			max_ts_offset = ts_offset;
			max_ts_scale = a_ds->timescale;
		}
	}

	//assign ts offset and send stop/play
	for (i=0; i<count; i++) {
		GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, i);

		if (a_ds->subdur_done)
			continue;

		ts_offset = gf_timestamp_rescale(max_ts_offset, max_ts_scale, a_ds->timescale);

		a_ds->ts_offset = ts_offset;
		if (a_ds->done) continue;
		if (a_ds->ts_offset > a_ds->est_next_dts) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Looping streams of unequal duration, inserting "LLU" us of timestamp delay in pid %s from %s\n", ((a_ds->ts_offset - a_ds->est_next_dts) * 1000000) / a_ds->timescale, gf_filter_pid_get_name(a_ds->ipid), a_ds->src_url));
		}

		a_ds->seek_to_pck = 0;
		a_ds->nb_pck = 0;
		a_ds->clamp_done = GF_FALSE;

		a_ds->loop_state = 2;

		if (ctx->subdur) {
			GF_FilterEvent evt;

			GF_FEVT_INIT(evt, GF_FEVT_STOP, a_ds->ipid);
			gf_filter_pid_send_event(a_ds->ipid, &evt);

			gf_filter_pid_set_discard(a_ds->ipid, GF_FALSE);

			dasher_send_encode_hints(ctx, ds);

			GF_FEVT_INIT(evt, GF_FEVT_PLAY, a_ds->ipid);


GF_Err fpar_box_dump(GF_Box *a, FILE * trace)
{
	u32 i;
	FilePartitionBox *ptr = (FilePartitionBox *) a;
	gf_isom_box_dump_start(a, "FilePartitionBox", trace);

	gf_fprintf(trace, "itemID=\"%d\" FEC_encoding_ID=\"%d\" FEC_instance_ID=\"%d\" max_source_block_length=\"%d\" encoding_symbol_length=\"%d\" max_number_of_encoding_symbols=\"%d\" ", ptr->itemID, ptr->FEC_encoding_ID, ptr->FEC_instance_ID, ptr->max_source_block_length, ptr->encoding_symbol_length, ptr->max_number_of_encoding_symbols);

	if (ptr->scheme_specific_info)
		dump_data_attribute(trace, "scheme_specific_info", (char*)ptr->scheme_specific_info, (u32)strlen(ptr->scheme_specific_info) );

	gf_fprintf(trace, ">\n");

	for (i=0; i<ptr->nb_entries; i++) {
		gf_fprintf(trace, "<FilePartitionBoxEntry block_count=\"%d\" block_size=\"%d\"/>\n", ptr->entries[i].block_count, ptr->entries[i].block_size);
	}
	if (!ptr->size) {
		gf_fprintf(trace, "<FilePartitionBoxEntry block_count=\"\" block_size=\"\"/>\n");
	}

	gf_isom_box_dump_done("FilePartitionBox", a, trace);
	return GF_OK;
}


GF_Err url_box_dump(GF_Box *a, FILE * trace)
{
	GF_DataEntryURLBox *p;

	p = (GF_DataEntryURLBox *)a;
	gf_isom_box_dump_start(a, "URLDataEntryBox", trace);
	if (p->location) {
		gf_fprintf(trace, " URL=\"%s\">\n", p->location);
	} else {
		gf_fprintf(trace, ">\n");
		if (p->size) {
			if (! (p->flags & 1) ) {
				gf_fprintf(trace, "<!--ERROR: No location indicated-->\n");
			} else {
				gf_fprintf(trace, "<!--Data is contained in the movie file-->\n");
			}
		}
	}
	gf_isom_box_dump_done("URLDataEntryBox", a, trace);
	return GF_OK;
}


static JSValue js_file_gets(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	char *data=NULL;
	char temp[1025];
	JSValue res;
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);

	temp[1024] = 0;
	while (1) {
		u32 rlen;
		temp[0] = 0;
		const char *read = gf_fgets(temp, 1024, f);
		if (!read) break;
		gf_dynstrcat(&data, temp, NULL);
		if (!data) return js_throw_err(ctx, GF_OUT_OF_MEM);
		rlen = (u32) strlen(temp);
		if (rlen <= 1024)
			break;
		if (temp[rlen-1] == '\n')
			break;
	}
	res = JS_NewString(ctx, data);
	gf_free(data);
	return res;


static void nhmldump_send_frame(GF_NHMLDumpCtx *ctx, char *data, u32 data_size, GF_FilterPacket *pck)
{
	GF_FilterPacket *dst_pck;
	char nhml[1024];
	const GF_PropertyValue *p;
	u32 size;
	u8 *output;
	GF_FilterSAPType sap = gf_filter_pck_get_sap(pck);
	u64 dts = gf_filter_pck_get_dts(pck);
	u64 cts = gf_filter_pck_get_cts(pck);

	if (dts==GF_FILTER_NO_TS) dts = cts;
	if (cts==GF_FILTER_NO_TS) cts = dts;

	ctx->pck_num++;
	sprintf(nhml, "<NHNTSample number=\"%d\" DTS=\""LLU"\" dataLength=\"%d\" ", ctx->pck_num, dts, data_size);
	gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
	if (ctx->pckp || (cts != dts) ) {
		sprintf(nhml, "CTSOffset=\"%d\" ", (s32) ((s64)cts - (s64)dts));
		gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
	}
	if (sap==GF_FILTER_SAP_1) {
		sprintf(nhml, "isRAP=\"yes\" ");
		gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
	} else if (sap) {
		sprintf(nhml, "SAPType=\"%d\" ", sap);
		gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
	} else if (ctx->pckp) {
		sprintf(nhml, "isRAP=\"no\" ");
		gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
		if ((sap==GF_FILTER_SAP_4) || (sap==GF_FILTER_SAP_4_PROL)) {
			s32 roll = gf_filter_pck_get_roll_info(pck);
			sprintf(nhml, "SAPType=\"4\" %s=\"%d\" ", (sap==GF_FILTER_SAP_4_PROL) ? "prol" : "roll", roll);
			gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
		}
	}

	if (ctx->pckp) {
		u64 bo;
		u32 duration, idx;
		sprintf(nhml, "mediaOffset=\""LLU"\" ", ctx->mdia_pos);
		gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));

		bo = gf_filter_pck_get_byte_offset(pck);
		if (bo!=GF_FILTER_NO_BO) {
			sprintf(nhml, "sourceByteOffset=\""LLU"\" ", bo);
			gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
		}
		duration = gf_filter_pck_get_duration(pck);
		if (duration) {
			sprintf(nhml, "duration=\"%d\" ", duration);
			gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
		}
		idx = gf_filter_pck_get_carousel_version(pck);
		if (idx) {
			sprintf(nhml, "carouselVersion=\"%d\" ", idx);
			gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
		}
		idx = 0;
		while (1) {
			u32 prop_4cc;
			const char *prop_name;
			p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name);
			if (!p) break;
			if (prop_4cc == GF_PROP_PCK_SUBS) continue;
			nhmldump_pck_property(ctx, prop_4cc, prop_name, p);
		}
	}

	if (ctx->chksum) {
		if (ctx->chksum==1) {
			u32 crc = gf_crc_32(data, data_size);
			sprintf(nhml, "crc=\"%08X\" ", crc);
			gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
		} else {
			u32 j;
			u8 hash[GF_SHA1_DIGEST_SIZE];
			gf_sha1_csum(data, data_size, hash);
			sprintf(nhml, "sha1=\"");
			gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
			for (j=0; j<20; j++) {
				sprintf(nhml, "%02X", hash[j]);
				gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
			}
			sprintf(nhml, "\" ");
			gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
		}
	}

	sprintf(nhml, ">\n");
	gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));

	p = gf_filter_pck_get_property(pck, GF_PROP_PCK_SUBS);
	if (p) {
		u32 offset_in_sample = 0;
		Bool first_subs = GF_TRUE;
		if (!ctx->bs_r) ctx->bs_r = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ);
		else gf_bs_reassign_buffer(ctx->bs_r, p->value.data.ptr, p->value.data.size);

		//(data) binary blob containing N [(u32)flags(u32)size(u32)reserved(u8)priority(u8) discardable]
		while (gf_bs_available(ctx->bs_r)) {
			u32 s_flags = gf_bs_read_u32(ctx->bs_r);
			u32 s_size = gf_bs_read_u32(ctx->bs_r);
			u32 s_res = gf_bs_read_u32(ctx->bs_r);
			u8 s_prio = gf_bs_read_u8(ctx->bs_r);
			u8 s_discard = gf_bs_read_u8(ctx->bs_r);

			if (offset_in_sample + s_size > data_size) {
					GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Wrong subsample info: sample size %d vs subsample offset+size %dn", data_size, offset_in_sample + s_size));
				break;
			}

			if (ctx->is_stpp && ctx->nhmlonly) {
				if (first_subs) {
					sprintf(nhml, "<NHNTSubSample>\n");
					gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));

					gf_bs_write_data(ctx->bs_w, data, s_size);

					sprintf(nhml, "</NHNTSubSample>\n");
					gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
				} else {
					u32 d_size;
					if (ctx->b64_buffer_size < 2*s_size + 3) {
						ctx->b64_buffer_size = 2 * s_size + 3;
						ctx->b64_buffer = gf_realloc(ctx->b64_buffer, ctx->b64_buffer_size);
					}
					d_size = gf_base64_encode(data + offset_in_sample, s_size, ctx->b64_buffer, ctx->b64_buffer_size);
					ctx->b64_buffer[d_size] = 0;
					sprintf(nhml, "<NHNTSubSample data=\"data:application/octet-string;base64,");
					gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
					gf_bs_write_data(ctx->bs_w, ctx->b64_buffer, d_size);
					sprintf(nhml, "\">\n");
					gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
				}
			} else {
				sprintf(nhml, "<NHNTSubSample size=\"%d\" flags=\"%d\" reserved=\"%d\" priority=\"%d\" discard=\"%d\" />\n", s_size, s_flags, s_res, s_prio, s_discard);
				gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
			}
			first_subs = GF_FALSE;
		}
	} else if (ctx->is_stpp && ctx->nhmlonly) {
		sprintf(nhml, "<NHNTSubSample><![CDATA[\n");
		gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
		gf_bs_write_data(ctx->bs_w, data, data_size);
		sprintf(nhml, "]]></NHNTSubSample>\n");
		gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));
	}
	sprintf(nhml, "</NHNTSample>\n");
	gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml));

	gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->nhml_buffer, &size, &ctx->nhml_buffer_size);

	if (ctx->filep) {
		gf_fwrite(ctx->nhml_buffer, size, ctx->filep);
		return;
	}

	dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhml, size, &output);
	if (dst_pck) {
		memcpy(output, ctx->nhml_buffer, size);
		gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE);
		gf_filter_pck_send(dst_pck);
	}

	ctx->mdia_pos += data_size;

	if (ctx->opid_mdia) {
		//send the complete data packet
		dst_pck = gf_filter_pck_new_ref(ctx->opid_mdia, 0, data_size, pck);
		if (!dst_pck) return;

		gf_filter_pck_merge_properties(pck, dst_pck);
		//keep byte offset ?
//		gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO);

		gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE);
		gf_filter_pck_send(dst_pck);
	}
}


static JSValue js_bs_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	gf_bs_flush(bs);
	return JS_UNDEFINED;
}


GF_Err schm_box_dump(GF_Box *a, FILE * trace)
{
	GF_SchemeTypeBox *p;
	p = (GF_SchemeTypeBox *)a;
	gf_isom_box_dump_start(a, "SchemeTypeBox", trace);
	gf_fprintf(trace, "scheme_type=\"%s\" scheme_version=\"%d\" ", gf_4cc_to_str(p->scheme_type), p->scheme_version);
	if (p->URI) gf_fprintf(trace, "scheme_uri=\"%s\"", p->URI);
	gf_fprintf(trace, ">\n");

	gf_isom_box_dump_done("SchemeTypeBox", a, trace);
	return GF_OK;
}


GF_Err saio_box_dump(GF_Box *a, FILE * trace)
{
	u32 i;
	GF_SampleAuxiliaryInfoOffsetBox *ptr = (GF_SampleAuxiliaryInfoOffsetBox*) a;
	if (!a) return GF_BAD_PARAM;

	if (dump_skip_samples)
		return GF_OK;

	gf_isom_box_dump_start(a, "SampleAuxiliaryInfoOffsetBox", trace);

	gf_fprintf(trace, "entry_count=\"%d\"", ptr->entry_count);
	if (ptr->flags & 1) {
		if (isalnum(ptr->aux_info_type>>24)) {
			gf_fprintf(trace, " aux_info_type=\"%s\" aux_info_type_parameter=\"%d\"", gf_4cc_to_str(ptr->aux_info_type), ptr->aux_info_type_parameter);
		} else {
			gf_fprintf(trace, " aux_info_type=\"%d\" aux_info_type_parameter=\"%d\"", ptr->aux_info_type, ptr->aux_info_type_parameter);
		}
	}

	gf_fprintf(trace, ">\n");

	if (ptr->version==0) {
		for (i=0; i<ptr->entry_count; i++) {
			gf_fprintf(trace, "<SAIChunkOffset offset=\"%d\"/>\n", (u32) ptr->offsets[i]);
		}
	} else {
		for (i=0; i<ptr->entry_count; i++) {
			gf_fprintf(trace, "<SAIChunkOffset offset=\""LLD"\"/>\n", ptr->offsets[i]);
		}
	}
	if (!ptr->size) {
			gf_fprintf(trace, "<SAIChunkOffset offset=\"\"/>\n");
	}
	gf_isom_box_dump_done("SampleAuxiliaryInfoOffsetBox", a, trace);
	return GF_OK;
}


		gf_free(info);
	}
	gf_list_del(pinfos);

	return nb_periods;
}

static void dasher_init_utc(GF_Filter *filter, GF_DasherCtx *ctx)
{
	const char *cache_name;
	u32 size;
	u8 *data;
	u64 remote_utc;
	GF_Err e;
	GF_DownloadSession *sess;
	GF_DownloadManager *dm;
	char *url;
	DasherUTCTimingType def_type = DASHER_UTCREF_NONE;

	ctx->utc_initialized = GF_TRUE;
	ctx->utc_timing_type = DASHER_UTCREF_NONE;
	if (!ctx->utcs) {
		return;
	}
	url = ctx->utcs;
	if (!strncmp(url, "xsd@", 4)) {
		def_type = DASHER_UTCREF_XSDATE;
		url += 4;
	}

	dm  = gf_filter_get_download_manager(filter);
	if (!dm) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to get download manager, cannot sync to remote UTC clock\n"));
		return;
	}
	if (!strcmp(ctx->utcs, "inband")) {
		ctx->utc_timing_type = DASHER_UTCREF_INBAND;
		return;
	}

	sess = gf_dm_sess_new(dm, url, GF_NETIO_SESSION_MEMORY_CACHE|GF_NETIO_SESSION_NOT_THREADED, NULL, NULL, &e);
	if (e) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to create session for remote UTC source %s: %s - local clock will be used instead\n", url, gf_error_to_string(e) ));
		return;
	}
	while (1) {
		GF_NetIOStatus status;
		e = gf_dm_sess_process(sess);
		if (e) break;
		gf_dm_sess_get_stats(sess, NULL, NULL, NULL, NULL, NULL, &status);
		if (status>=GF_NETIO_DATA_TRANSFERED) break;
	}
	if (e<0) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to fetch remote UTC source %s: %s\n", url, gf_error_to_string(e) ));
		gf_dm_sess_del(sess);
		return;
	}
	cache_name = gf_dm_sess_get_cache_name(sess);
	gf_blob_get(cache_name, &data, &size, NULL);
	if (data) {
		//xsDate or isoDate - we always signal using iso
		if (strchr(data, 'T')) {
			remote_utc = gf_net_parse_date(data);
			if (remote_utc)
				ctx->utc_timing_type = def_type ? def_type : DASHER_UTCREF_ISO;
		}
		//ntp
		else if (sscanf(data, LLU, &remote_utc) == 1) {
			//ntp value not counted since 1900, assume format is seconds till 1 jan 1970
			if (remote_utc<=GF_NTP_SEC_1900_TO_1970) {
				remote_utc = remote_utc*1000;
			} else {
				remote_utc = gf_net_ntp_to_utc(remote_utc);
			}
			if (remote_utc)
				ctx->utc_timing_type = DASHER_UTCREF_NTP;
		}
	}
	gf_blob_release(cache_name);

	//not match, try http date
	if (!ctx->utc_timing_type) {
		const char *hdr = gf_dm_sess_get_header(sess, "Date");
		if (hdr) {
			//http-head
			remote_utc = gf_net_parse_date(hdr);
			if (remote_utc)
				ctx->utc_timing_type = DASHER_UTCREF_HTTP_HEAD;
		}
	}

	if (!ctx->utc_timing_type) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to parse response %s from remote UTC source %s\n", data, url ));
	} else {
		ctx->utc_diff = (s32) ( (s64) gf_net_get_utc() - (s64) remote_utc );
		if (ABS(ctx->utc_diff) > 3600000) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Diff between local clock and remote %s is %d, way too large! Assuming 0 ms UTC diff\n", url, ctx->utc_diff));
			ctx->utc_diff = 0;
		} else {
			GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] Synchronized clock to remote %s - UTC diff (local - remote) %d ms\n", url, ctx->utc_diff));
		}

		if (!gf_list_count(ctx->mpd->utc_timings) ) {
			Bool dashif_ok = GF_FALSE;
			GF_MPD_Descriptor *utc_t;
			GF_SAFEALLOC(utc_t, GF_MPD_Descriptor);
			utc_t->value = gf_strdup(url);
			switch (ctx->utc_timing_type) {
			case DASHER_UTCREF_HTTP_HEAD:
				utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:http-head:2014");
				break;
			case DASHER_UTCREF_XSDATE:
				utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:http-xsdate:2014");
				dashif_ok = GF_TRUE;
				break;
			case DASHER_UTCREF_ISO:
				utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:http-iso:2014");
				dashif_ok = GF_TRUE;
				break;
			case DASHER_UTCREF_NTP:
				utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:http-ntp:2014");
				dashif_ok = GF_TRUE;
				break;
			case DASHER_UTCREF_INBAND:
				utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:direct:2014");
				break;
			default:
				break;
			}
			if (!dashif_ok && (ctx->profile==GF_DASH_PROFILE_DASHIF_LL)) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] UTC reference %s allowed in DASH-IF Low Latency profile\n\tswitching to regular live profile\n", utc_t->scheme_id_uri));
				ctx->profile = GF_DASH_PROFILE_LIVE;
			}


GF_CryptInfo *gf_crypt_info_load(const char *file, GF_Err *out_err)
{
	GF_Err e;
	GF_CryptInfo *info;
	GF_SAXParser *sax;
	GF_SAFEALLOC(info, GF_CryptInfo);
	if (!info) {
		if (out_err) *out_err = GF_OUT_OF_MEM;
		return NULL;
	}
	info->tcis = gf_list_new();
	sax = gf_xml_sax_new(cryptinfo_node_start, cryptinfo_node_end, cryptinfo_text, info);
	e = gf_xml_sax_parse_file(sax, file, NULL);
	if (e<0) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[DRM] Failed to parse DRM config file: %s", gf_xml_sax_get_error(sax) ));
		if (out_err) *out_err = e;
		gf_crypt_info_del(info);
		info = NULL;
	} else if (info->last_parse_error) {
		if (out_err) *out_err = info->last_parse_error;
		gf_crypt_info_del(info);
		info = NULL;
	} else {
		if (out_err) *out_err = GF_OK;
	}
	gf_xml_sax_del(sax);
	return info;
}


static JSValue js_sys_rmdir(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_RMDIR);


static void js_fileio_finalize(JSRuntime *rt, JSValue obj)
{
	FILE *f = JS_GetOpaque(obj, file_class_id);
	if (!f) return;
	gf_fclose(f);


static JSValue js_bs_put_s8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 1);
}


static JSValue js_bs_put_s16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 4);
}


GF_Err dvvC_box_dump(GF_Box *a, FILE * trace)
{
	return dvcC_box_dump(a, trace);
}


static void svg_check_namespace(GF_SVG_Parser *parser, const GF_XMLAttribute *attributes, u32 nb_attributes, Bool *has_ns)
{
	u32 i;
	/*parse all att to check for namespaces, to:
	   - add the prefixed namespaces (xmlns:xxxx='...')
	   - change the namespace for this element if no prefix (xmlns='...')*/
 	for (i=0; i<nb_attributes; i++) {
		GF_XMLAttribute *att = (GF_XMLAttribute *)&attributes[i];
		if (!att->value || !strlen(att->value)) continue;
		if (!strncmp(att->name, "xmlns", 5)) {
			/* check if we have a prefix for this namespace */
			char *qname = strchr(att->name, ':');
			if (qname) {
				qname++;
			}
			/* Adds the namespace to the scene graph, either with a prefix or with NULL */
			gf_sg_add_namespace(parser->load->scene_graph, att->value, qname);

			if (!qname) {
				/* Only if there was no prefix, we change the namespace for the current element */
				parser->current_ns = gf_sg_get_namespace_code_from_name(parser->load->scene_graph, att->value);
			}
			/* Signal that when ending this element, namespaces will have to be removed */
			*has_ns = GF_TRUE;
		}
	}
}


}
static JSValue js_sys_dir_exists(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_DIREXISTS);


static SVG_SAFExternalStream *svg_saf_get_stream(GF_SVG_Parser *parser, u32 id, const char *name)
{
	SVG_SAFExternalStream *st;
	st = parser->streams;
	while (st) {
		if (id == st->id) return st;
		if (name && st->stream_name && !strcmp(name, st->stream_name) ) return st;
		st = st->next;
	}
	return NULL;
}


GF_Err segr_box_dump(GF_Box *a, FILE * trace)
{
	u32 i, k;
	FDSessionGroupBox *ptr = (FDSessionGroupBox *) a;
	gf_isom_box_dump_start(a, "FDSessionGroupBox", trace);
	gf_fprintf(trace, ">\n");

	for (i=0; i<ptr->num_session_groups; i++) {
		gf_fprintf(trace, "<FDSessionGroupBoxEntry groupIDs=\"");
		for (k=0; k<ptr->session_groups[i].nb_groups; k++) {
			gf_fprintf(trace, "%d ", ptr->session_groups[i].group_ids[k]);
		}
		gf_fprintf(trace, "\" channels=\"");
		for (k=0; k<ptr->session_groups[i].nb_channels; k++) {
			gf_fprintf(trace, "%d ", ptr->session_groups[i].channels[k]);
		}
		gf_fprintf(trace, "\"/>\n");
	}
	if (!ptr->size) {
		gf_fprintf(trace, "<FDSessionGroupBoxEntry groupIDs=\"\" channels=\"\"/>\n");
	}

	gf_isom_box_dump_done("FDSessionGroupBox", a, trace);
	return GF_OK;
}