multi_add_mbuf(struct multi_context *m,
               struct multi_instance *mi,
               struct mbuf_buffer *mb)
{
    if (multi_output_queue_ready(m, mi))
    {
        struct mbuf_item item;
        item.buffer = mb;
        item.instance = mi;
        mbuf_add_item(m->mbuf, &item);
    }
    else
    {
        msg(D_MULTI_DROPPED, "MULTI: packet dropped due to output saturation (multi_add_mbuf)");
    }
}


is_exit_restart(int sig)
{
    return (sig == SIGUSR1 || sig == SIGTERM || sig == SIGHUP || sig == SIGINT);
}

Fix illegal client float (CVE-2020-11810)

There is a time frame between allocating peer-id and initializing data
channel key (which is performed on receiving push request or on async
push-reply) in which the existing peer-id float checks do not work right.

If a "rogue" data channel packet arrives during that time frame from
another address and  with same peer-id, this would cause client to float
to that new address. This is because:

 - tls_pre_decrypt() sets packet length to zero if
   data channel key has not been initialized, which leads to

 - openvpn_decrypt() returns true if packet length is zero,
   which leads to

 - process_incoming_link_part1() returns true, which
   calls multi_process_float(), which commits float

Note that problem doesn't happen when data channel key is initialized,
since in this case openvpn_decrypt() returns false.

The net effect of this behaviour is that the VPN session for the
"victim client" is broken.  Since the "attacker client" does not have
suitable keys, it can not inject or steal VPN traffic from the other
session.  The time window is small and it can not be used to attack
a specific client's session, unless some other way is found to make it
disconnect and reconnect first.

CVE-2020-11810 has been assigned to acknowledge this risk.

Fix illegal float by adding buffer length check ("is this packet still
considered valid") before calling multi_process_float().

Trac: #1272
CVE: 2020-11810

Signed-off-by: Lev Stipakov <lev@openvpn.net>
Acked-by: Arne Schwabe <arne@rfc2549.org>
Acked-by: Antonio Quartulli <antonio@openvpn.net>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20200415073017.22839-1-lstipakov@gmail.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg19720.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>


gremlin_flood_clients(struct multi_context *m)
{
    const int level = GREMLIN_PACKET_FLOOD_LEVEL(m->top.options.gremlin);
    if (level)
    {
        struct gc_arena gc = gc_new();
        struct buffer buf = alloc_buf_gc(BUF_SIZE(&m->top.c2.frame), &gc);
        struct packet_flood_parms parm = get_packet_flood_parms(level);
        int i;

        ASSERT(buf_init(&buf, FRAME_HEADROOM(&m->top.c2.frame)));
        parm.packet_size = min_int(parm.packet_size, MAX_RW_SIZE_TUN(&m->top.c2.frame));

        msg(D_GREMLIN, "GREMLIN_FLOOD_CLIENTS: flooding clients with %d packets of size %d",
            parm.n_packets,
            parm.packet_size);

        for (i = 0; i < parm.packet_size; ++i)
        {
            ASSERT(buf_write_u8(&buf, get_random() & 0xFF));
        }

        for (i = 0; i < parm.n_packets; ++i)
        {
            multi_bcast(m, &buf, NULL, NULL, 0);
        }

        gc_free(&gc);
    }
}


stale_route_check_trigger(struct multi_context *m)
{
    struct timeval null;
    CLEAR(null);
    return event_timeout_trigger(&m->stale_routes_check_et, &null, ETT_DEFAULT);
}


multi_del_iroutes(struct multi_context *m,
                  struct multi_instance *mi)
{
    const struct iroute *ir;
    const struct iroute_ipv6 *ir6;
    if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
    {
        for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)
        {
            mroute_helper_del_iroute46(m->route_helper, ir->netbits);
        }

        for (ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next)
        {
            mroute_helper_del_iroute46(m->route_helper, ir6->netbits);
        }
    }
}


management_callback_kill_by_addr(void *arg, const in_addr_t addr, const int port)
{
    struct multi_context *m = (struct multi_context *) arg;
    struct hash_iterator hi;
    struct hash_element *he;
    struct openvpn_sockaddr saddr;
    struct mroute_addr maddr;
    int count = 0;

    CLEAR(saddr);
    saddr.addr.in4.sin_family = AF_INET;
    saddr.addr.in4.sin_addr.s_addr = htonl(addr);
    saddr.addr.in4.sin_port = htons(port);
    if (mroute_extract_openvpn_sockaddr(&maddr, &saddr, true))
    {
        hash_iterator_init(m->iter, &hi);
        while ((he = hash_iterator_next(&hi)))
        {
            struct multi_instance *mi = (struct multi_instance *) he->value;
            if (!mi->halt && mroute_addr_equal(&maddr, &mi->real))
            {
                multi_signal_instance(m, mi, SIGTERM);
                ++count;
            }
        }
        hash_iterator_free(&hi);
    }
    return count;
}


multi_init(struct multi_context *m, struct context *t, bool tcp_mode, int thread_mode)
{
    int dev = DEV_TYPE_UNDEF;

    msg(D_MULTI_LOW, "MULTI: multi_init called, r=%d v=%d",
        t->options.real_hash_size,
        t->options.virtual_hash_size);

    /*
     * Get tun/tap/null device type
     */
    dev = dev_type_enum(t->options.dev, t->options.dev_type);

    /*
     * Init our multi_context object.
     */
    CLEAR(*m);

    m->thread_mode = thread_mode;

    /*
     * Real address hash table (source port number is
     * considered to be part of the address).  Used
     * to determine which client sent an incoming packet
     * which is seen on the TCP/UDP socket.
     */
    m->hash = hash_init(t->options.real_hash_size,
                        get_random(),
                        mroute_addr_hash_function,
                        mroute_addr_compare_function);

    /*
     * Virtual address hash table.  Used to determine
     * which client to route a packet to.
     */
    m->vhash = hash_init(t->options.virtual_hash_size,
                         get_random(),
                         mroute_addr_hash_function,
                         mroute_addr_compare_function);

    /*
     * This hash table is a clone of m->hash but with a
     * bucket size of one so that it can be used
     * for fast iteration through the list.
     */
    m->iter = hash_init(1,
                        get_random(),
                        mroute_addr_hash_function,
                        mroute_addr_compare_function);

#ifdef MANAGEMENT_DEF_AUTH
    m->cid_hash = hash_init(t->options.real_hash_size,
                            0,
                            cid_hash_function,
                            cid_compare_function);
#endif

#ifdef ENABLE_ASYNC_PUSH
    /*
     * Mapping between inotify watch descriptors and
     * multi_instances.
     */
    m->inotify_watchers = hash_init(t->options.real_hash_size,
                                    get_random(),
                                    int_hash_function,
                                    int_compare_function);
#endif

    /*
     * This is our scheduler, for time-based wakeup
     * events.
     */
    m->schedule = schedule_init();

    /*
     * Limit frequency of incoming connections to control
     * DoS.
     */
    m->new_connection_limiter = frequency_limit_init(t->options.cf_max,
                                                     t->options.cf_per);

    /*
     * Allocate broadcast/multicast buffer list
     */
    m->mbuf = mbuf_init(t->options.n_bcast_buf);

    /*
     * Different status file format options are available
     */
    m->status_file_version = t->options.status_file_version;

    /*
     * Possibly allocate an ifconfig pool, do it
     * differently based on whether a tun or tap style
     * tunnel.
     */
    if (t->options.ifconfig_pool_defined)
    {
        int pool_type = IFCONFIG_POOL_INDIV;

        if (dev == DEV_TYPE_TUN && t->options.topology == TOP_NET30)
        {
            pool_type = IFCONFIG_POOL_30NET;
        }

        m->ifconfig_pool = ifconfig_pool_init(pool_type,
                                              t->options.ifconfig_pool_start,
                                              t->options.ifconfig_pool_end,
                                              t->options.duplicate_cn,
                                              t->options.ifconfig_ipv6_pool_defined,
                                              t->options.ifconfig_ipv6_pool_base,
                                              t->options.ifconfig_ipv6_pool_netbits );

        /* reload pool data from file */
        if (t->c1.ifconfig_pool_persist)
        {
            ifconfig_pool_read(t->c1.ifconfig_pool_persist, m->ifconfig_pool);
        }
    }

    /*
     * Help us keep track of routing table.
     */
    m->route_helper = mroute_helper_init(MULTI_CACHE_ROUTE_TTL);

    /*
     * Initialize route and instance reaper.
     */
    m->reaper = multi_reap_new(reap_buckets_per_pass(t->options.virtual_hash_size));

    /*
     * Get local ifconfig address
     */
    CLEAR(m->local);
    ASSERT(t->c1.tuntap);
    mroute_extract_in_addr_t(&m->local, t->c1.tuntap->local);

    /*
     * Per-client limits
     */
    m->max_clients = t->options.max_clients;

    m->instances = calloc(m->max_clients, sizeof(struct multi_instance *));

    /*
     * Initialize multi-socket TCP I/O wait object
     */
    if (tcp_mode)
    {
        m->mtcp = multi_tcp_init(t->options.max_clients, &m->max_clients);
    }
    m->tcp_queue_limit = t->options.tcp_queue_limit;

    /*
     * Allow client <-> client communication, without going through
     * tun/tap interface and network stack?
     */
    m->enable_c2c = t->options.enable_c2c;

    /* initialize stale routes check timer */
    if (t->options.stale_routes_check_interval > 0)
    {
        msg(M_INFO, "Initializing stale route check timer to run every %i seconds and to removing routes with activity timeout older than %i seconds",
            t->options.stale_routes_check_interval, t->options.stale_routes_ageing_time);
        event_timeout_init(&m->stale_routes_check_et, t->options.stale_routes_check_interval, 0);
    }

    m->deferred_shutdown_signal.signal_received = 0;
}


multi_create_instance(struct multi_context *m, const struct mroute_addr *real)
{
    struct gc_arena gc = gc_new();
    struct multi_instance *mi;

    perf_push(PERF_MULTI_CREATE_INSTANCE);

    msg(D_MULTI_MEDIUM, "MULTI: multi_create_instance called");

    ALLOC_OBJ_CLEAR(mi, struct multi_instance);

    mi->gc = gc_new();
    multi_instance_inc_refcount(mi);
    mi->vaddr_handle = -1;
    mi->created = now;
    mroute_addr_init(&mi->real);

    if (real)
    {
        mi->real = *real;
        generate_prefix(mi);
    }

    mi->did_open_context = true;
    inherit_context_child(&mi->context, &m->top);
    if (IS_SIG(&mi->context))
    {
        goto err;
    }

    mi->context.c2.context_auth = CAS_PENDING;

    if (hash_n_elements(m->hash) >= m->max_clients)
    {
        msg(D_MULTI_ERRORS, "MULTI: new incoming connection would exceed maximum number of clients (%d)", m->max_clients);
        goto err;
    }

    if (!real) /* TCP mode? */
    {
        if (!multi_tcp_instance_specific_init(m, mi))
        {
            goto err;
        }
        generate_prefix(mi);
    }

    if (!hash_add(m->iter, &mi->real, mi, false))
    {
        msg(D_MULTI_LOW, "MULTI: unable to add real address [%s] to iterator hash table",
            mroute_addr_print(&mi->real, &gc));
        goto err;
    }
    mi->did_iter = true;

#ifdef MANAGEMENT_DEF_AUTH
    do
    {
        mi->context.c2.mda_context.cid = m->cid_counter++;
    } while (!hash_add(m->cid_hash, &mi->context.c2.mda_context.cid, mi, false));
    mi->did_cid_hash = true;
#endif

    mi->context.c2.push_reply_deferred = true;

#ifdef ENABLE_ASYNC_PUSH
    mi->context.c2.push_request_received = false;
    mi->inotify_watch = -1;
#endif

    if (!multi_process_post(m, mi, MPP_PRE_SELECT))
    {
        msg(D_MULTI_ERRORS, "MULTI: signal occurred during client instance initialization");
        goto err;
    }

    perf_pop();
    gc_free(&gc);
    return mi;

err:
    multi_close_instance(m, mi, false);
    perf_pop();
    gc_free(&gc);
    return NULL;
}


int_compare_function(const void *key1, const void *key2)
{
    return (unsigned long)key1 == (unsigned long)key2;
}


multi_connection_established(struct multi_context *m, struct multi_instance *mi)
{
    if (tls_authentication_status(mi->context.c2.tls_multi, 0) == TLS_AUTHENTICATION_SUCCEEDED)
    {
        struct gc_arena gc = gc_new();
        unsigned int option_types_found = 0;

        const unsigned int option_permissions_mask =
            OPT_P_INSTANCE
            | OPT_P_INHERIT
            | OPT_P_PUSH
            | OPT_P_TIMER
            | OPT_P_CONFIG
            | OPT_P_ECHO
            | OPT_P_COMP
            | OPT_P_SOCKFLAGS;

        int cc_succeeded = true; /* client connect script status */
        int cc_succeeded_count = 0;

        ASSERT(mi->context.c1.tuntap);

        /* lock down the common name and cert hashes so they can't change during future TLS renegotiations */
        tls_lock_common_name(mi->context.c2.tls_multi);
        tls_lock_cert_hash_set(mi->context.c2.tls_multi);

        /* generate a msg() prefix for this client instance */
        generate_prefix(mi);

        /* delete instances of previous clients with same common-name */
        if (!mi->context.options.duplicate_cn)
        {
            multi_delete_dup(m, mi);
        }

        /* reset pool handle to null */
        mi->vaddr_handle = -1;

        /*
         * Try to source a dynamic config file from the
         * --client-config-dir directory.
         */
        if (mi->context.options.client_config_dir)
        {
            const char *ccd_file;

            ccd_file = platform_gen_path(mi->context.options.client_config_dir,
                                         tls_common_name(mi->context.c2.tls_multi,
                                                         false),
                                         &gc);

            /* try common-name file */
            if (platform_test_file(ccd_file))
            {
                options_server_import(&mi->context.options,
                                      ccd_file,
                                      D_IMPORT_ERRORS|M_OPTERR,
                                      option_permissions_mask,
                                      &option_types_found,
                                      mi->context.c2.es);
            }
            else /* try default file */
            {
                ccd_file = platform_gen_path(mi->context.options.client_config_dir,
                                             CCD_DEFAULT,
                                             &gc);

                if (platform_test_file(ccd_file))
                {
                    options_server_import(&mi->context.options,
                                          ccd_file,
                                          D_IMPORT_ERRORS|M_OPTERR,
                                          option_permissions_mask,
                                          &option_types_found,
                                          mi->context.c2.es);
                }
            }
        }

        /*
         * Select a virtual address from either --ifconfig-push in --client-config-dir file
         * or --ifconfig-pool.
         */
        multi_select_virtual_addr(m, mi);

        /* do --client-connect setenvs */
        multi_client_connect_setenv(m, mi);

#ifdef ENABLE_PLUGIN
        /*
         * Call client-connect plug-in.
         */

        /* deprecated callback, use a file for passing back return info */
        if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
        {
            struct argv argv = argv_new();
            const char *dc_file = platform_create_temp_file(mi->context.options.tmp_dir,
                                                            "cc", &gc);

            if (!dc_file)
            {
                cc_succeeded = false;
                goto script_depr_failed;
            }

            argv_printf(&argv, "%s", dc_file);
            if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT, &argv, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
            {
                msg(M_WARN, "WARNING: client-connect plugin call failed");
                cc_succeeded = false;
            }
            else
            {
                multi_client_connect_post(m, mi, dc_file, option_permissions_mask, &option_types_found);
                ++cc_succeeded_count;
            }

            if (!platform_unlink(dc_file))
            {
                msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
                    dc_file);
            }

script_depr_failed:
            argv_free(&argv);
        }

        /* V2 callback, use a plugin_return struct for passing back return info */
        if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2))
        {
            struct plugin_return pr;

            plugin_return_init(&pr);

            if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2, NULL, &pr, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
            {
                msg(M_WARN, "WARNING: client-connect-v2 plugin call failed");
                cc_succeeded = false;
            }
            else
            {
                multi_client_connect_post_plugin(m, mi, &pr, option_permissions_mask, &option_types_found);
                ++cc_succeeded_count;
            }

            plugin_return_free(&pr);
        }
#endif /* ifdef ENABLE_PLUGIN */

        /*
         * Run --client-connect script.
         */
        if (mi->context.options.client_connect_script && cc_succeeded)
        {
            struct argv argv = argv_new();
            const char *dc_file = NULL;

            setenv_str(mi->context.c2.es, "script_type", "client-connect");

            dc_file = platform_create_temp_file(mi->context.options.tmp_dir,
                                                "cc", &gc);
            if (!dc_file)
            {
                cc_succeeded = false;
                goto script_failed;
            }

            argv_parse_cmd(&argv, mi->context.options.client_connect_script);
            argv_printf_cat(&argv, "%s", dc_file);

            if (openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-connect"))
            {
                multi_client_connect_post(m, mi, dc_file, option_permissions_mask, &option_types_found);
                ++cc_succeeded_count;
            }
            else
            {
                cc_succeeded = false;
            }

            if (!platform_unlink(dc_file))
            {
                msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
                    dc_file);
            }

script_failed:
            argv_free(&argv);
        }

        /*
         * Check for client-connect script left by management interface client
         */
#ifdef MANAGEMENT_DEF_AUTH
        if (cc_succeeded && mi->cc_config)
        {
            multi_client_connect_mda(m, mi, mi->cc_config, option_permissions_mask, &option_types_found);
            ++cc_succeeded_count;
        }
#endif

        /*
         * Check for "disable" directive in client-config-dir file
         * or config file generated by --client-connect script.
         */
        if (mi->context.options.disable)
        {
            msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to 'disable' directive");
            cc_succeeded = false;
            cc_succeeded_count = 0;
        }

        if (cc_succeeded)
        {
            /*
             * Process sourced options.
             */
            do_deferred_options(&mi->context, option_types_found);

            /*
             * make sure we got ifconfig settings from somewhere
             */
            if (!mi->context.c2.push_ifconfig_defined)
            {
                msg(D_MULTI_ERRORS, "MULTI: no dynamic or static remote --ifconfig address is available for %s",
                    multi_instance_string(mi, false, &gc));
            }

            /*
             * make sure that ifconfig settings comply with constraints
             */
            if (!ifconfig_push_constraint_satisfied(&mi->context))
            {
                /* JYFIXME -- this should cause the connection to fail */
                msg(D_MULTI_ERRORS, "MULTI ERROR: primary virtual IP for %s (%s) violates tunnel network/netmask constraint (%s/%s)",
                    multi_instance_string(mi, false, &gc),
                    print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),
                    print_in_addr_t(mi->context.options.push_ifconfig_constraint_network, 0, &gc),
                    print_in_addr_t(mi->context.options.push_ifconfig_constraint_netmask, 0, &gc));
            }

            /*
             * For routed tunnels, set up internal route to endpoint
             * plus add all iroute routes.
             */
            if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
            {
                if (mi->context.c2.push_ifconfig_defined)
                {
                    multi_learn_in_addr_t(m, mi, mi->context.c2.push_ifconfig_local, -1, true);
                    msg(D_MULTI_LOW, "MULTI: primary virtual IP for %s: %s",
                        multi_instance_string(mi, false, &gc),
                        print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc));
                }

                if (mi->context.c2.push_ifconfig_ipv6_defined)
                {
                    multi_learn_in6_addr(m, mi, mi->context.c2.push_ifconfig_ipv6_local, -1, true);
                    /* TODO: find out where addresses are "unlearned"!! */
                    msg(D_MULTI_LOW, "MULTI: primary virtual IPv6 for %s: %s",
                        multi_instance_string(mi, false, &gc),
                        print_in6_addr(mi->context.c2.push_ifconfig_ipv6_local, 0, &gc));
                }

                /* add routes locally, pointing to new client, if
                 * --iroute options have been specified */
                multi_add_iroutes(m, mi);

                /*
                 * iroutes represent subnets which are "owned" by a particular
                 * client.  Therefore, do not actually push a route to a client
                 * if it matches one of the client's iroutes.
                 */
                remove_iroutes_from_push_route_list(&mi->context.options);
            }
            else if (mi->context.options.iroutes)
            {
                msg(D_MULTI_ERRORS, "MULTI: --iroute options rejected for %s -- iroute only works with tun-style tunnels",
                    multi_instance_string(mi, false, &gc));
            }

            /* set our client's VPN endpoint for status reporting purposes */
            mi->reporting_addr = mi->context.c2.push_ifconfig_local;
            mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;

            /* set context-level authentication flag */
            mi->context.c2.context_auth = CAS_SUCCEEDED;

#ifdef ENABLE_ASYNC_PUSH
            /* authentication complete, send push reply */
            if (mi->context.c2.push_request_received)
            {
                process_incoming_push_request(&mi->context);
            }
#endif
        }
        else
        {
            /* set context-level authentication flag */
            mi->context.c2.context_auth = cc_succeeded_count ? CAS_PARTIAL : CAS_FAILED;
        }

        /* set flag so we don't get called again */
        mi->connection_established_flag = true;

        /* increment number of current authenticated clients */
        ++m->n_clients;
        update_mstat_n_clients(m->n_clients);
        --mi->n_clients_delta;

#ifdef MANAGEMENT_DEF_AUTH
        if (management)
        {
            management_connection_established(management, &mi->context.c2.mda_context, mi->context.c2.es);
        }
#endif

        gc_free(&gc);
    }

    /*
     * Reply now to client's PUSH_REQUEST query
     */
    mi->context.c2.push_reply_deferred = false;
}


multi_close_instance(struct multi_context *m,
                     struct multi_instance *mi,
                     bool shutdown)
{
    perf_push(PERF_MULTI_CLOSE_INSTANCE);

    ASSERT(!mi->halt);
    mi->halt = true;

    dmsg(D_MULTI_DEBUG, "MULTI: multi_close_instance called");

    /* adjust current client connection count */
    m->n_clients += mi->n_clients_delta;
    update_mstat_n_clients(m->n_clients);
    mi->n_clients_delta = 0;

    /* prevent dangling pointers */
    if (m->pending == mi)
    {
        multi_set_pending(m, NULL);
    }
    if (m->earliest_wakeup == mi)
    {
        m->earliest_wakeup = NULL;
    }

    if (!shutdown)
    {
        if (mi->did_real_hash)
        {
            ASSERT(hash_remove(m->hash, &mi->real));
        }
        if (mi->did_iter)
        {
            ASSERT(hash_remove(m->iter, &mi->real));
        }
#ifdef MANAGEMENT_DEF_AUTH
        if (mi->did_cid_hash)
        {
            ASSERT(hash_remove(m->cid_hash, &mi->context.c2.mda_context.cid));
        }
#endif

#ifdef ENABLE_ASYNC_PUSH
        if (mi->inotify_watch != -1)
        {
            hash_remove(m->inotify_watchers, (void *) (unsigned long)mi->inotify_watch);
            mi->inotify_watch = -1;
        }
#endif

        if (mi->context.c2.tls_multi->peer_id != MAX_PEER_ID)
        {
            m->instances[mi->context.c2.tls_multi->peer_id] = NULL;
        }

        schedule_remove_entry(m->schedule, (struct schedule_entry *) mi);

        ifconfig_pool_release(m->ifconfig_pool, mi->vaddr_handle, false);

        if (mi->did_iroutes)
        {
            multi_del_iroutes(m, mi);
            mi->did_iroutes = false;
        }

        if (m->mtcp)
        {
            multi_tcp_dereference_instance(m->mtcp, mi);
        }

        mbuf_dereference_instance(m->mbuf, mi);
    }

#ifdef MANAGEMENT_DEF_AUTH
    set_cc_config(mi, NULL);
#endif

    multi_client_disconnect_script(mi);

    if (mi->did_open_context)
    {
        close_context(&mi->context, SIGTERM, CC_GC_FREE);
    }

    multi_tcp_instance_specific_free(mi);

    ungenerate_prefix(mi);

    /*
     * Don't actually delete the instance memory allocation yet,
     * because virtual routes may still point to it.  Let the
     * vhash reaper deal with it.
     */
    multi_instance_dec_refcount(mi);

    perf_pop();
}


management_callback_status(void *arg, const int version, struct status_output *so)
{
    struct multi_context *m = (struct multi_context *) arg;

    if (!version)
    {
        multi_print_status(m, so, m->status_file_version);
    }
    else
    {
        multi_print_status(m, so, version);
    }
}


multi_process_float(struct multi_context *m, struct multi_instance *mi)
{
    struct mroute_addr real;
    struct hash *hash = m->hash;
    struct gc_arena gc = gc_new();

    if (!mroute_extract_openvpn_sockaddr(&real, &m->top.c2.from.dest, true))
    {
        goto done;
    }

    const uint32_t hv = hash_value(hash, &real);
    struct hash_bucket *bucket = hash_bucket(hash, hv);

    /* make sure that we don't float to an address taken by another client */
    struct hash_element *he = hash_lookup_fast(hash, bucket, &real, hv);
    if (he)
    {
        struct multi_instance *ex_mi = (struct multi_instance *) he->value;

        struct tls_multi *m1 = mi->context.c2.tls_multi;
        struct tls_multi *m2 = ex_mi->context.c2.tls_multi;

        /* do not float if target address is taken by client with another cert */
        if (!cert_hash_compare(m1->locked_cert_hash_set, m2->locked_cert_hash_set))
        {
            msg(D_MULTI_LOW, "Disallow float to an address taken by another client %s",
                multi_instance_string(ex_mi, false, &gc));

            mi->context.c2.buf.len = 0;

            goto done;
        }

        msg(D_MULTI_MEDIUM, "closing instance %s", multi_instance_string(ex_mi, false, &gc));
        multi_close_instance(m, ex_mi, false);
    }

    msg(D_MULTI_MEDIUM, "peer %" PRIu32 " (%s) floated from %s to %s",
        mi->context.c2.tls_multi->peer_id,
        tls_common_name(mi->context.c2.tls_multi, false),
        mroute_addr_print(&mi->real, &gc),
        print_link_socket_actual(&m->top.c2.from, &gc));

    /* remove old address from hash table before changing address */
    ASSERT(hash_remove(m->hash, &mi->real));
    ASSERT(hash_remove(m->iter, &mi->real));

    /* change external network address of the remote peer */
    mi->real = real;
    generate_prefix(mi);

    mi->context.c2.from = m->top.c2.from;
    mi->context.c2.to_link_addr = &mi->context.c2.from;

    /* inherit parent link_socket and link_socket_info */
    mi->context.c2.link_socket = m->top.c2.link_socket;
    mi->context.c2.link_socket_info->lsa->actual = m->top.c2.from;

    tls_update_remote_addr(mi->context.c2.tls_multi, &mi->context.c2.from);

    ASSERT(hash_add(m->hash, &mi->real, mi, false));
    ASSERT(hash_add(m->iter, &mi->real, mi, false));

#ifdef MANAGEMENT_DEF_AUTH
    ASSERT(hash_add(m->cid_hash, &mi->context.c2.mda_context.cid, mi, true));
#endif

done:
    gc_free(&gc);
}


multi_instance_string(const struct multi_instance *mi, bool null, struct gc_arena *gc)
{
    if (mi)
    {
        struct buffer out = alloc_buf_gc(MULTI_PREFIX_MAX_LENGTH, gc);
        const char *cn = tls_common_name(mi->context.c2.tls_multi, true);

        if (cn)
        {
            buf_printf(&out, "%s/", cn);
        }
        buf_printf(&out, "%s", mroute_addr_print(&mi->real, gc));
        return BSTR(&out);
    }
    else if (null)
    {
        return NULL;
    }
    else
    {
        return "UNDEF";
    }
}


multi_client_connect_mda(struct multi_context *m,
                         struct multi_instance *mi,
                         const struct buffer_list *config,
                         unsigned int option_permissions_mask,
                         unsigned int *option_types_found)
{
    if (config)
    {
        struct buffer_entry *be;

        for (be = config->head; be != NULL; be = be->next)
        {
            const char *opt = BSTR(&be->buf);
            options_string_import(&mi->context.options,
                                  opt,
                                  D_IMPORT_ERRORS|M_OPTERR,
                                  option_permissions_mask,
                                  option_types_found,
                                  mi->context.c2.es);
        }

        /*
         * If the --client-connect script generates a config file
         * with an --ifconfig-push directive, it will override any
         * --ifconfig-push directive from the --client-config-dir
         * directory or any --ifconfig-pool dynamic address.
         */
        multi_select_virtual_addr(m, mi);
        multi_set_virtual_addr_env(mi);
    }
}


multi_client_connect_post_plugin(struct multi_context *m,
                                 struct multi_instance *mi,
                                 const struct plugin_return *pr,
                                 unsigned int option_permissions_mask,
                                 unsigned int *option_types_found)
{
    struct plugin_return config;

    plugin_return_get_column(pr, &config, "config");

    /* Did script generate a dynamic config file? */
    if (plugin_return_defined(&config))
    {
        int i;
        for (i = 0; i < config.n; ++i)
        {
            if (config.list[i] && config.list[i]->value)
            {
                options_string_import(&mi->context.options,
                                      config.list[i]->value,
                                      D_IMPORT_ERRORS|M_OPTERR,
                                      option_permissions_mask,
                                      option_types_found,
                                      mi->context.c2.es);
            }
        }

        /*
         * If the --client-connect script generates a config file
         * with an --ifconfig-push directive, it will override any
         * --ifconfig-push directive from the --client-config-dir
         * directory or any --ifconfig-pool dynamic address.
         */
        multi_select_virtual_addr(m, mi);
        multi_set_virtual_addr_env(mi);
    }
}


int_hash_function(const void *key, uint32_t iv)
{
    return (unsigned long)key;
}


multi_push_restart_schedule_exit(struct multi_context *m, bool next_server)
{
    struct hash_iterator hi;
    struct hash_element *he;
    struct timeval tv;

    /* tell all clients to restart */
    hash_iterator_init(m->iter, &hi);
    while ((he = hash_iterator_next(&hi)))
    {
        struct multi_instance *mi = (struct multi_instance *) he->value;
        if (!mi->halt)
        {
            send_control_channel_string(&mi->context, next_server ? "RESTART,[N]" : "RESTART", D_PUSH);
            multi_schedule_context_wakeup(m, mi);
        }
    }
    hash_iterator_free(&hi);

    /* reschedule signal */
    ASSERT(!openvpn_gettimeofday(&m->deferred_shutdown_signal.wakeup, NULL));
    tv.tv_sec = 2;
    tv.tv_usec = 0;
    tv_add(&m->deferred_shutdown_signal.wakeup, &tv);

    m->deferred_shutdown_signal.signal_received = m->top.sig->signal_received;

    schedule_add_entry(m->schedule,
                       (struct schedule_entry *) &m->deferred_shutdown_signal,
                       &m->deferred_shutdown_signal.wakeup,
                       compute_wakeup_sigma(&m->deferred_shutdown_signal.wakeup));

    m->top.sig->signal_received = 0;
}


multi_learn_in6_addr(struct multi_context *m,
                     struct multi_instance *mi,
                     struct in6_addr a6,
                     int netbits,   /* -1 if host route, otherwise # of network bits in address */
                     bool primary)
{
    struct mroute_addr addr;

    addr.len = 16;
    addr.type = MR_ADDR_IPV6;
    addr.netbits = 0;
    addr.v6.addr = a6;

    if (netbits >= 0)
    {
        addr.type |= MR_WITH_NETBITS;
        addr.netbits = (uint8_t) netbits;
        mroute_addr_mask_host_bits( &addr );
    }

    {
        struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
#ifdef MANAGEMENT_DEF_AUTH
        if (management && owner)
        {
            management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
        }
#endif
        return owner;
    }
}


ifconfig_push_constraint_satisfied(const struct context *c)
{
    const struct options *o = &c->options;
    if (o->push_ifconfig_constraint_defined && c->c2.push_ifconfig_defined)
    {
        return (o->push_ifconfig_constraint_netmask & c->c2.push_ifconfig_local) == o->push_ifconfig_constraint_network;
    }
    else
    {
        return true;
    }
}


multi_close_instance_on_signal(struct multi_context *m, struct multi_instance *mi)
{
    remap_signal(&mi->context);
    set_prefix(mi);
    print_signal(mi->context.sig, "client-instance", D_MULTI_LOW);
    clear_prefix();
    multi_close_instance(m, mi, false);
}


management_client_auth(void *arg,
                       const unsigned long cid,
                       const unsigned int mda_key_id,
                       const bool auth,
                       const char *reason,
                       const char *client_reason,
                       struct buffer_list *cc_config)  /* ownership transferred */
{
    struct multi_context *m = (struct multi_context *) arg;
    struct multi_instance *mi = lookup_by_cid(m, cid);
    bool cc_config_owned = true;
    bool ret = false;

    if (mi)
    {
        ret = tls_authenticate_key(mi->context.c2.tls_multi, mda_key_id, auth, client_reason);
        if (ret)
        {
            if (auth)
            {
                if (!mi->connection_established_flag)
                {
                    set_cc_config(mi, cc_config);
                    cc_config_owned = false;
                }
            }
            else
            {
                if (reason)
                {
                    msg(D_MULTI_LOW, "MULTI: connection rejected: %s, CLI:%s", reason, np(client_reason));
                }
                if (mi->connection_established_flag)
                {
                    send_auth_failed(&mi->context, client_reason); /* mid-session reauth failed */
                    multi_schedule_context_wakeup(m, mi);
                }
            }
        }
    }
    if (cc_config_owned && cc_config)
    {
        buffer_list_free(cc_config);
    }
    return ret;
}


cid_compare_function(const void *key1, const void *key2)
{
    const unsigned long *k1 = (const unsigned long *)key1;
    const unsigned long *k2 = (const unsigned long *)key2;
    return *k1 == *k2;
}


multi_get_queue(struct mbuf_set *ms)
{
    struct mbuf_item item;

    if (mbuf_extract_item(ms, &item)) /* cleartext IP packet */
    {
        unsigned int pip_flags = PIPV4_PASSTOS | PIPV6_IMCP_NOHOST_SERVER;

        set_prefix(item.instance);
        item.instance->context.c2.buf = item.buffer->buf;
        if (item.buffer->flags & MF_UNICAST) /* --mssfix doesn't make sense for broadcast or multicast */
        {
            pip_flags |= PIP_MSSFIX;
        }
        process_ip_header(&item.instance->context, pip_flags, &item.instance->context.c2.buf);
        encrypt_sign(&item.instance->context, true);
        mbuf_free_buf(item.buffer);

        dmsg(D_MULTI_DEBUG, "MULTI: C2C/MCAST/BCAST");

        clear_prefix();
        return item.instance;
    }
    else
    {
        return NULL;
    }
}


multi_client_disconnect_script(struct multi_instance *mi)
{
    if ((mi->context.c2.context_auth == CAS_SUCCEEDED && mi->connection_established_flag)
        || mi->context.c2.context_auth == CAS_PARTIAL)
    {
        multi_client_disconnect_setenv(mi);

        if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT))
        {
            if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
            {
                msg(M_WARN, "WARNING: client-disconnect plugin call failed");
            }
        }

        if (mi->context.options.client_disconnect_script)
        {
            struct argv argv = argv_new();
            setenv_str(mi->context.c2.es, "script_type", "client-disconnect");
            argv_parse_cmd(&argv, mi->context.options.client_disconnect_script);
            openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-disconnect");
            argv_free(&argv);
        }
#ifdef MANAGEMENT_DEF_AUTH
        if (management)
        {
            management_notify_client_close(management, &mi->context.c2.mda_context, mi->context.c2.es);
        }
#endif

    }
}


multi_learn_in_addr_t(struct multi_context *m,
                      struct multi_instance *mi,
                      in_addr_t a,
                      int netbits,  /* -1 if host route, otherwise # of network bits in address */
                      bool primary)
{
    struct openvpn_sockaddr remote_si;
    struct mroute_addr addr;

    CLEAR(remote_si);
    remote_si.addr.in4.sin_family = AF_INET;
    remote_si.addr.in4.sin_addr.s_addr = htonl(a);
    ASSERT(mroute_extract_openvpn_sockaddr(&addr, &remote_si, false));

    if (netbits >= 0)
    {
        addr.type |= MR_WITH_NETBITS;
        addr.netbits = (uint8_t) netbits;
    }

    {
        struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
#ifdef MANAGEMENT_DEF_AUTH
        if (management && owner)
        {
            management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
        }
#endif
        return owner;
    }
}


management_callback_kill_by_cn(void *arg, const char *del_cn)
{
    struct multi_context *m = (struct multi_context *) arg;
    struct hash_iterator hi;
    struct hash_element *he;
    int count = 0;

    hash_iterator_init(m->iter, &hi);
    while ((he = hash_iterator_next(&hi)))
    {
        struct multi_instance *mi = (struct multi_instance *) he->value;
        if (!mi->halt)
        {
            const char *cn = tls_common_name(mi->context.c2.tls_multi, false);
            if (cn && !strcmp(cn, del_cn))
            {
                multi_signal_instance(m, mi, SIGTERM);
                ++count;
            }
        }
    }
    hash_iterator_free(&hi);
    return count;
}


multi_process_drop_outgoing_tun(struct multi_context *m, const unsigned int mpp_flags)
{
    struct multi_instance *mi = m->pending;

    ASSERT(mi);

    set_prefix(mi);

    msg(D_MULTI_ERRORS, "MULTI: Outgoing TUN queue full, dropped packet len=%d",
        mi->context.c2.to_tun.len);

    buf_reset(&mi->context.c2.to_tun);

    multi_process_post(m, mi, mpp_flags);
    clear_prefix();
}


multi_client_connect_post(struct multi_context *m,
                          struct multi_instance *mi,
                          const char *dc_file,
                          unsigned int option_permissions_mask,
                          unsigned int *option_types_found)
{
    /* Did script generate a dynamic config file? */
    if (platform_test_file(dc_file))
    {
        options_server_import(&mi->context.options,
                              dc_file,
                              D_IMPORT_ERRORS|M_OPTERR,
                              option_permissions_mask,
                              option_types_found,
                              mi->context.c2.es);

        /*
         * If the --client-connect script generates a config file
         * with an --ifconfig-push directive, it will override any
         * --ifconfig-push directive from the --client-config-dir
         * directory or any --ifconfig-pool dynamic address.
         */
        multi_select_virtual_addr(m, mi);
        multi_set_virtual_addr_env(mi);
    }
}


GF_Err GetESDForTime(GF_MovieBox *moov, GF_ISOTrackID trackID, u64 CTS, GF_ESD **outESD)
{
	GF_Err e;
	u32 sampleDescIndex;
	GF_TrackBox *trak;

	trak = gf_isom_get_track(moov, gf_isom_get_tracknum_from_id(moov, trackID));
	if (!trak) return GF_ISOM_INVALID_FILE;

	e = Media_GetSampleDescIndex(trak->Media, CTS, &sampleDescIndex );
	if (e) return e;
	return GetESD(moov, trackID, sampleDescIndex, outESD);
}

fixed #1736


GF_TrunEntry *traf_get_sample_entry(GF_TrackFragmentBox *traf, u32 sample_index)
{
	u32 i, idx;
	GF_TrackFragmentRunBox *trun;
	idx=0;
	i=0;
	while ((trun = (GF_TrackFragmentRunBox *)gf_list_enum(traf->TrackRuns, &i))) {
		u32 j;
		for (j=0; j<trun->sample_count; j++) {
			GF_TrunEntry *ent = gf_list_get(trun->entries, j);
			if (idx==sample_index) return ent;
			if (ent->nb_pack>1) {
				if (idx < sample_index + ent->nb_pack)
					return ent;
				idx += ent->nb_pack;
			} else {
				idx++;
			}
		}
	}
	return NULL;
}

fixed #1736


GF_Err NewMedia(GF_MediaBox **mdia, u32 MediaType, u32 TimeScale)
{
	GF_MediaHeaderBox *mdhd;
	GF_Box *mediaInfo;
	GF_HandlerBox *hdlr;
	GF_MediaInformationBox *minf;
	GF_DataInformationBox *dinf;
	GF_SampleTableBox *stbl;
	GF_DataReferenceBox *dref;
	char *str="";

	GF_Err e;

	if (!mdia) return GF_BAD_PARAM;

	minf = *mdia ? (*mdia)->information : NULL;
	mdhd = *mdia ? (*mdia)->mediaHeader : NULL;
	hdlr = *mdia ? (*mdia)->handler : NULL;
	dinf =  minf ? minf->dataInformation : NULL;
	stbl = minf ? minf->sampleTable : NULL;
	dref = dinf ? dinf->dref : NULL;
	mediaInfo = minf ? minf->InfoHeader : NULL;

	//first create the media
	if (!*mdia) {
		*mdia = (GF_MediaBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MDIA);
		if (! *mdia) { e = GF_OUT_OF_MEM; goto err_exit; }
	}
	if (!mdhd) {
		mdhd = (GF_MediaHeaderBox *) gf_isom_box_new_parent( & ((*mdia)->child_boxes), GF_ISOM_BOX_TYPE_MDHD);
		if (! mdhd) { e = GF_OUT_OF_MEM; goto err_exit; }
		e = mdia_on_child_box((GF_Box*)*mdia, (GF_Box *) mdhd, GF_FALSE);
		if (e) goto err_exit;
	}
	if (!hdlr) {
		hdlr = (GF_HandlerBox *) gf_isom_box_new_parent(& ((*mdia)->child_boxes), GF_ISOM_BOX_TYPE_HDLR);
		if (! hdlr) { e = GF_OUT_OF_MEM; goto err_exit; }
		e = mdia_on_child_box((GF_Box*)*mdia, (GF_Box *) hdlr, GF_FALSE);
		if (e) goto err_exit;
	}
	if (!minf) {
		minf = (GF_MediaInformationBox *) gf_isom_box_new_parent(& ((*mdia)->child_boxes), GF_ISOM_BOX_TYPE_MINF);
		if (! minf) { e = GF_OUT_OF_MEM; goto err_exit; }
		e = mdia_on_child_box((GF_Box*)*mdia, (GF_Box *) minf, GF_FALSE);
		if (e) goto err_exit;
	}
	if (!dinf) {
		dinf = (GF_DataInformationBox *) gf_isom_box_new_parent(&minf->child_boxes, GF_ISOM_BOX_TYPE_DINF);
		if (! dinf) { e = GF_OUT_OF_MEM; goto err_exit; }
		e = minf_on_child_box((GF_Box*)minf, (GF_Box *) dinf, GF_FALSE);
		if (e) goto err_exit;
	}

	if (!mediaInfo) {
		//"handler name" is for debugging purposes. Let's stick our name here ;)
		switch (MediaType) {
		case GF_ISOM_MEDIA_VISUAL:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_VMHD);
			str = "GPAC ISO Video Handler";
			break;
		case GF_ISOM_MEDIA_AUXV:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_VMHD);
			str = "GPAC ISO Auxiliary Video Handler";
			break;
		case GF_ISOM_MEDIA_PICT:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_VMHD);
			str = "GPAC ISO Picture Sequence Handler";
			break;
		case GF_ISOM_MEDIA_AUDIO:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_SMHD);
			str = "GPAC ISO Audio Handler";
			break;
		case GF_ISOM_MEDIA_HINT:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_HMHD);
			str = "GPAC ISO Hint Handler";
			break;
		case GF_ISOM_MEDIA_META:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC Timed MetaData Handler";
			break;
		case GF_ISOM_MEDIA_OD:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC MPEG-4 OD Handler";
			break;
		case GF_ISOM_MEDIA_OCR:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC MPEG-4 OCR Handler";
			break;
		case GF_ISOM_MEDIA_SCENE:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC MPEG-4 Scene Description Handler";
			break;
		case GF_ISOM_MEDIA_MPEG7:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC MPEG-4 MPEG-7 Handler";
			break;
		case GF_ISOM_MEDIA_OCI:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC MPEG-4 OCI Handler";
			break;
		case GF_ISOM_MEDIA_IPMP:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC MPEG-4 IPMP Handler";
			break;
		case GF_ISOM_MEDIA_MPEGJ:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC MPEG-4 MPEG-J Handler";
			break;
		case GF_ISOM_MEDIA_TEXT:
		case GF_ISOM_MEDIA_SUBT:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC Streaming Text Handler";
			break;
		case GF_ISOM_MEDIA_MPEG_SUBT:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_STHD);
			str = "GPAC MPEG Subtitle Handler";
			break;
		case GF_ISOM_MEDIA_DIMS:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_VMHD);
			MediaType = GF_ISOM_MEDIA_SCENE;
			str = "GPAC DIMS Handler";
			break;
		case GF_ISOM_MEDIA_TIMECODE:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_GMHD);
			str = "GPAC TMCD Handler";
			break;
		default:
			mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
			str = "GPAC IsoMedia Handler";
			break;
		}
		if (! mediaInfo) { e = GF_OUT_OF_MEM; goto err_exit; }
		if (!minf->child_boxes) minf->child_boxes = gf_list_new();
		gf_list_add(minf->child_boxes, mediaInfo);

		e = minf_on_child_box((GF_Box*)minf, (GF_Box *) mediaInfo, GF_FALSE);
		if (e) goto err_exit;
	}

	mdhd->timeScale = TimeScale;
	hdlr->handlerType = MediaType;
	if (!hdlr->nameUTF8)
		hdlr->nameUTF8 = gf_strdup(str);

	if (!dref) {
		//Create a data reference WITHOUT DATA ENTRY (we don't know anything yet about the media Data)
		dref = (GF_DataReferenceBox *) gf_isom_box_new_parent(&dinf->child_boxes, GF_ISOM_BOX_TYPE_DREF);
		if (! dref) { e = GF_OUT_OF_MEM; goto err_exit; }
		e = dinf_on_child_box((GF_Box*)dinf, (GF_Box *)dref, GF_FALSE);
		if (e) goto err_exit;
	}

	if (!stbl) {
		//first set-up the sample table...
		stbl = (GF_SampleTableBox *) gf_isom_box_new_parent(&minf->child_boxes, GF_ISOM_BOX_TYPE_STBL);
		if (! stbl) { e = GF_OUT_OF_MEM; goto err_exit; }

		e = minf_on_child_box((GF_Box*)minf, (GF_Box *) stbl, GF_FALSE);
		if (e) goto err_exit;
	}
	if (!stbl->SampleDescription) {
		stbl->SampleDescription = (GF_SampleDescriptionBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSD);
		if (! stbl->SampleDescription) { e = GF_OUT_OF_MEM; goto err_exit; }
	}

	//by default create a regular table, 32 but offset and normal sample size
	if (!stbl->ChunkOffset) {
		stbl->ChunkOffset = gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STCO);
		if (! stbl->ChunkOffset) { e = GF_OUT_OF_MEM; goto err_exit; }
	}
	if (!stbl->SampleSize) {
		stbl->SampleSize = (GF_SampleSizeBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSZ);
		if (! stbl->SampleSize) { e = GF_OUT_OF_MEM; goto err_exit; }
	}
	if (!stbl->SampleToChunk) {
		stbl->SampleToChunk = (GF_SampleToChunkBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSC);
		if (! stbl->SampleToChunk) { e = GF_OUT_OF_MEM; goto err_exit; }
	}
	if (!stbl->TimeToSample) {
		stbl->TimeToSample = (GF_TimeToSampleBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STTS);
		if (! stbl->TimeToSample) { e = GF_OUT_OF_MEM; goto err_exit; }
	}
	if (!stbl->SampleDescription) {
		stbl->SampleDescription = (GF_SampleDescriptionBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSD);
		if (! stbl->SampleDescription) { e = GF_OUT_OF_MEM; goto err_exit; }
	}
	return GF_OK;

err_exit:
	if (mdhd) gf_isom_box_del_parent(& ((*mdia)->child_boxes), (GF_Box *)mdhd);
	if (minf) gf_isom_box_del_parent(& ((*mdia)->child_boxes), (GF_Box *)minf);
	if (hdlr) {
		gf_isom_box_del_parent(& ((*mdia)->child_boxes) , (GF_Box *)hdlr);
	}
	return e;

}

fixed #1736


GF_TrackBox *GetTrackbyID(GF_MovieBox *moov, GF_ISOTrackID TrackID)
{
	GF_TrackBox *trak;
	u32 i;
	if (!moov) return NULL;
	i=0;
	while ((trak = (GF_TrackBox *)gf_list_enum(moov->trackList, &i))) {
		if (trak->Header->trackID == TrackID) return trak;
	}
	return NULL;
}

fixed #1736


GF_Err GetESD(GF_MovieBox *moov, GF_ISOTrackID trackID, u32 StreamDescIndex, GF_ESD **outESD)
{
	GF_Err e;
	GF_ESD *esd;
	u32 track_num = 0;
	u32 k;
	GF_SampleTableBox *stbl;
	GF_TrackBox *trak, *OCRTrack;
	GF_TrackReferenceTypeBox *dpnd;
	GF_SLConfig *slc;
	GF_MPEGSampleEntryBox *entry;

	if (!moov) return GF_ISOM_INVALID_FILE;

	track_num = gf_isom_get_tracknum_from_id(moov, trackID);
	dpnd = NULL;
	*outESD = NULL;

	trak = gf_isom_get_track(moov, track_num);
	if (!trak) return GF_ISOM_INVALID_FILE;

	e = Media_GetESD(trak->Media, StreamDescIndex, &esd, 0);
	if (e) return e;
	if (!esd) return GF_NON_COMPLIANT_BITSTREAM;

	e = Media_GetSampleDesc(trak->Media, StreamDescIndex, (GF_SampleEntryBox **) &entry, NULL);
	if (e) return e;
	//set the ID
	esd->ESID = trackID;

	//find stream dependencies: dpnd, sbas and scal
	for (k=0; k<3; k++) {
		u32 ref = GF_ISOM_BOX_TYPE_DPND;
		if (k==1) ref = GF_ISOM_REF_BASE;
		else if (k==2) ref = GF_ISOM_REF_SCAL;

		e = Track_FindRef(trak, ref , &dpnd);
		if (e) return e;
		if (dpnd) {
			//ONLY ONE STREAM DEPENDENCY IS ALLOWED
			if (!k && (dpnd->trackIDCount != 1)) return GF_ISOM_INVALID_MEDIA;
			//fix the spec: where is the index located ??
			esd->dependsOnESID = dpnd->trackIDs[0];
			break;
		} else {
			esd->dependsOnESID = 0;
		}
	}

	if (trak->udta) {
		GF_UserDataMap *map;
		u32 i = 0;
		while ((map = (GF_UserDataMap*)gf_list_enum(trak->udta->recordList, &i))) {
			if (map->boxType == GF_ISOM_BOX_TYPE_AUXV) {
				GF_Descriptor *d = gf_odf_desc_new(GF_ODF_AUX_VIDEO_DATA);
				gf_list_add(esd->extensionDescriptors, d);
				break;
			}
		}
	}

	//OK, get the OCR (in a REAL MP4File, OCR is 0 in ESD and is specified through track reference
	dpnd = NULL;
	OCRTrack = NULL;
	//find OCR dependencies
	e = Track_FindRef(trak, GF_ISOM_BOX_TYPE_SYNC, &dpnd);
	if (e) return e;
	if (dpnd) {
		if (dpnd->trackIDCount != 1) return GF_ISOM_INVALID_MEDIA;
		esd->OCRESID = dpnd->trackIDs[0];
		OCRTrack = gf_isom_get_track_from_id(trak->moov, dpnd->trackIDs[0]);

		while (OCRTrack) {
			/*if we have a dependency on a track that doesn't have OCR dep, remove that dependency*/
			e = Track_FindRef(OCRTrack, GF_ISOM_BOX_TYPE_SYNC, &dpnd);
			if (e || !dpnd || !dpnd->trackIDCount) {
				OCRTrack = NULL;
				goto default_sync;
			}
			/*this is explicit desync*/
			if ((dpnd->trackIDs[0]==0) || (dpnd->trackIDs[0]==OCRTrack->Header->trackID))
				break;
			/*loop in OCRs, break it*/
			if (esd->ESID == (u16) OCRTrack->Header->trackID) {
				OCRTrack = NULL;
				goto default_sync;
			}
			/*check next*/
			OCRTrack = gf_isom_get_track_from_id(trak->moov, dpnd->trackIDs[0]);
		}
		if (!OCRTrack) goto default_sync;
	} else {
default_sync:
		/*all tracks are sync'ed by default*/
		if (trak->moov->mov->es_id_default_sync<0) {
			if (esd->OCRESID)
				trak->moov->mov->es_id_default_sync = esd->OCRESID;
			else
				trak->moov->mov->es_id_default_sync = esd->ESID;
		}
		if (trak->moov->mov->es_id_default_sync) esd->OCRESID = (u16) trak->moov->mov->es_id_default_sync;
		/*cf ESD writer*/
		if (esd->OCRESID == esd->ESID) esd->OCRESID = 0;
	}

	//update the IPI stuff if needed
	if (esd->ipiPtr != NULL) {
		dpnd = NULL;
		e = Track_FindRef(trak, GF_ISOM_BOX_TYPE_IPIR, &dpnd);
		if (e) return e;
		if (dpnd) {
			if (esd->ipiPtr->tag != GF_ODF_ISOM_IPI_PTR_TAG) return GF_ISOM_INVALID_FILE;
			//OK, retrieve the ID: the IPI_ES_Id is currently the ref track
			esd->ipiPtr->IPI_ES_Id = dpnd->trackIDs[esd->ipiPtr->IPI_ES_Id - 1];
			//and change the tag
			esd->ipiPtr->tag = GF_ODF_IPI_PTR_TAG;
		} else {
			return GF_ISOM_INVALID_FILE;
		}
	}

	if ((trak->Media->mediaHeader->packedLanguage[0] != 'u')
	        || (trak->Media->mediaHeader->packedLanguage[1] != 'n')
	        || (trak->Media->mediaHeader->packedLanguage[2] != 'd') ) {
		if (!esd->langDesc) esd->langDesc = (GF_Language *) gf_odf_desc_new(GF_ODF_LANG_TAG);

		esd->langDesc->langCode = trak->Media->mediaHeader->packedLanguage[0];
		esd->langDesc->langCode <<= 8;
		esd->langDesc->langCode |= trak->Media->mediaHeader->packedLanguage[1];
		esd->langDesc->langCode <<= 8;
		esd->langDesc->langCode |= trak->Media->mediaHeader->packedLanguage[2];
	}

	{
		u16 rvc_predefined;
		u8 *rvc_cfg_data;
		const char *mime_type;
		u32 rvc_cfg_size;
		e = gf_isom_get_rvc_config(moov->mov, track_num, 1, &rvc_predefined, &rvc_cfg_data, &rvc_cfg_size, &mime_type);
		if (e==GF_OK) {
			if (rvc_predefined) {
				esd->decoderConfig->predefined_rvc_config = rvc_predefined;
			} else {
				esd->decoderConfig->rvc_config = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
				if (mime_type && !strcmp(mime_type, "application/rvc-config+xml+gz") ) {
#if !defined(GPAC_DISABLE_CORE_TOOLS) && !defined(GPAC_DISABLE_ZLIB)
					gf_gz_decompress_payload(rvc_cfg_data, rvc_cfg_size, &esd->decoderConfig->rvc_config->data, &esd->decoderConfig->rvc_config->dataLength);
					gf_free(rvc_cfg_data);
#endif
				} else {
					esd->decoderConfig->rvc_config->data = rvc_cfg_data;
					esd->decoderConfig->rvc_config->dataLength = rvc_cfg_size;
				}
			}
		}
	}

	/*normally all files shall be stored with predefined=SLPredef_MP4, but of course some are broken (philips)
	so we just check the ESD_URL. If set, use the given cfg, otherwise always rewrite it*/
	if (esd->URLString != NULL) {
		*outESD = esd;
		return GF_OK;
	}

	//if we are in publishing mode and we have an SLConfig specified, use it as is
	switch (entry->type) {
	case GF_ISOM_BOX_TYPE_MP4V:
		slc = ((GF_MPEGVisualSampleEntryBox *)entry)->slc;
		break;
	case GF_ISOM_BOX_TYPE_MP4A:
		slc = ((GF_MPEGAudioSampleEntryBox *)entry)->slc;
		break;
	case GF_ISOM_BOX_TYPE_MP4S:
		slc = entry->slc;
		break;
	default:
		slc = NULL;
		break;
	}
	if (slc) {
		gf_odf_desc_del((GF_Descriptor *)esd->slConfig);
		gf_odf_desc_copy((GF_Descriptor *)slc, (GF_Descriptor **)&esd->slConfig);
		*outESD = esd;
		return GF_OK;
	}
	//otherwise use the regular mapping

	if (!esd->slConfig)
		esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);

	//this is a desc for a media in the file, let's rewrite some param
	esd->slConfig->timestampLength = 32;
	esd->slConfig->timestampResolution = trak->Media->mediaHeader->timeScale;
	//NO OCR from MP4File streams (eg, constant OC Res one)
	esd->slConfig->OCRLength = 0;
	esd->slConfig->OCRResolution = 0;
//	if (OCRTrack) esd->slConfig->OCRResolution = OCRTrack->Media->mediaHeader->timeScale;

	stbl = trak->Media->information->sampleTable;
	// a little optimization here: if all our samples are sync,
	//set the RAPOnly to true... for external users...
	if (! stbl->SyncSample) {
		if (
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
		    moov->mvex &&
#endif
		    esd->decoderConfig && esd->decoderConfig->streamType &&
		    (esd->decoderConfig->streamType==GF_STREAM_VISUAL)
		) {
			esd->slConfig->hasRandomAccessUnitsOnlyFlag = 0;
			esd->slConfig->useRandomAccessPointFlag = 1;
			if (trak->moov->mov->openMode!=GF_ISOM_OPEN_READ) {
				stbl->SyncSample = (GF_SyncSampleBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSS);
				if (!stbl->SyncSample) return GF_OUT_OF_MEM;
			}
		} else {
			esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
			esd->slConfig->useRandomAccessPointFlag = 0;
		}
	} else {
		esd->slConfig->hasRandomAccessUnitsOnlyFlag = 0;
		//signal we are NOT using sync points if no info is present in the table
		esd->slConfig->useRandomAccessPointFlag = stbl->SyncSample->nb_entries ? 1 : 0;
	}
	//change to support reflecting OD streams
	esd->slConfig->useAccessUnitEndFlag = 1;
	esd->slConfig->useAccessUnitStartFlag = 1;

	//signal we do have padding flag (since we only use logical SL packet
	//the user can decide whether to use the info or not
	esd->slConfig->usePaddingFlag = stbl->PaddingBits ? 1 : 0;

	//same with degradation priority
	esd->slConfig->degradationPriorityLength = stbl->DegradationPriority ? 32 : 0;

	//this new SL will be OUT OF THE FILE. Let's set its predefined to 0
	esd->slConfig->predefined = 0;

	*outESD = esd;
	return GF_OK;
}

fixed #1736


GF_TrackBox *gf_isom_get_track(GF_MovieBox *moov, u32 trackNumber)
{
	GF_TrackBox *trak;
	if (!moov || !trackNumber || (trackNumber > gf_list_count(moov->trackList))) return NULL;
	trak = (GF_TrackBox*)gf_list_get(moov->trackList, trackNumber - 1);
	return trak;

}

fixed #1736


GF_Err SetTrackDuration(GF_TrackBox *trak)
{
	u64 trackDuration;
	u32 i;
	GF_Err e;

	//the total duration is the media duration: adjust it in case...
	e = Media_SetDuration(trak);
	if (e) return e;

	//assert the timeScales are non-NULL
	if (!trak->moov->mvhd || !trak->moov->mvhd->timeScale || !trak->Media->mediaHeader->timeScale) return GF_ISOM_INVALID_FILE;
	trackDuration = (trak->Media->mediaHeader->duration * trak->moov->mvhd->timeScale) / trak->Media->mediaHeader->timeScale;

	//if we have an edit list, the duration is the sum of all the editList
	//entries' duration (always expressed in MovieTimeScale)
	if (trak->editBox && trak->editBox->editList) {
		GF_EdtsEntry *ent;
		GF_EditListBox *elst = trak->editBox->editList;
		trackDuration = 0;
		i=0;
		while ((ent = (GF_EdtsEntry*)gf_list_enum(elst->entryList, &i))) {
			trackDuration += ent->segmentDuration;
		}
	}
	if (!trackDuration) {
		trackDuration = (trak->Media->mediaHeader->duration * trak->moov->mvhd->timeScale) / trak->Media->mediaHeader->timeScale;
	}
	if (!trak->Header) {
		return GF_OK;
	}
	trak->Header->duration = trackDuration;
	if (!trak->moov->mov->keep_utc && !gf_sys_is_test_mode() )
		trak->Header->modificationTime = gf_isom_get_mp4time();
	return GF_OK;
}

fixed #1736


GF_Err Track_RemoveRef(GF_TrackBox *trak, u32 ReferenceType)
{
	GF_TrackReferenceBox *ref;
	GF_Box *a;
	u32 i;
	if (! trak) return GF_BAD_PARAM;
	if (! trak->References) return GF_OK;
	ref = trak->References;
	i=0;
	while ((a = (GF_Box *)gf_list_enum(ref->child_boxes, &i))) {
		if (a->type == ReferenceType) {
			gf_isom_box_del_parent(&ref->child_boxes, a);
			return GF_OK;
		}
	}
	return GF_OK;
}

fixed #1736


GF_Err Track_SetStreamDescriptor(GF_TrackBox *trak, u32 StreamDescriptionIndex, u32 DataReferenceIndex, GF_ESD *esd, u32 *outStreamIndex)
{
	GF_Err e;
	GF_MPEGSampleEntryBox *entry;
	GF_MPEGVisualSampleEntryBox *entry_v;
	GF_MPEGAudioSampleEntryBox *entry_a;
	GF_TrackReferenceBox *tref;
	GF_TrackReferenceTypeBox *dpnd;
	u16 tmpRef;

	entry = NULL;
	tref = NULL;

	if (!trak || !esd || (!outStreamIndex && !DataReferenceIndex) ) return GF_BAD_PARAM;
	if (!Track_IsMPEG4Stream(trak->Media->handler->handlerType)) return GF_ISOM_INVALID_MEDIA;

	esd->ESID = 0;
	//set SL to predefined if no url
	if (esd->URLString == NULL) {
		if (!esd->slConfig) esd->slConfig = (GF_SLConfig*) gf_odf_desc_new(GF_ODF_SLC_TAG);
		esd->slConfig->predefined = SLPredef_MP4;
		esd->slConfig->durationFlag = 0;
		esd->slConfig->useTimestampsFlag = 1;
	}

	//get the REF box if needed
	if (esd->dependsOnESID || (esd->OCRESID  && (esd->OCRESID != trak->moov->mov->es_id_default_sync)) ) {
		if (!trak->References) {
			tref = (GF_TrackReferenceBox *) gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_TREF);
			if (!tref) return GF_OUT_OF_MEM;
			e = trak_on_child_box((GF_Box*)trak, (GF_Box *)tref, GF_FALSE);
			if (e) return e;
		}
		tref = trak->References;
	}

	//Update Stream dependencies
	e = Track_FindRef(trak, GF_ISOM_REF_DECODE, &dpnd);
	if (e) return e;

	if (!dpnd && esd->dependsOnESID) {
		e = Track_FindRef(trak, GF_ISOM_REF_BASE, &dpnd);
		if (e) return e;
	}

	if (!dpnd && esd->dependsOnESID) {
		dpnd = (GF_TrackReferenceTypeBox *) gf_isom_box_new_parent(&tref->child_boxes, GF_ISOM_BOX_TYPE_REFT);
		dpnd->reference_type = GF_ISOM_BOX_TYPE_DPND;
		e = reftype_AddRefTrack(dpnd, esd->dependsOnESID, NULL);
		if (e) return e;
	} else if (dpnd && !esd->dependsOnESID) {
		Track_RemoveRef(trak, GF_ISOM_BOX_TYPE_DPND);
	}
	esd->dependsOnESID = 0;

	//Update GF_Clock dependencies
	e = Track_FindRef(trak, GF_ISOM_REF_OCR, &dpnd);
	if (e) return e;
	if (!dpnd && esd->OCRESID && (esd->OCRESID != trak->moov->mov->es_id_default_sync)) {
		dpnd = (GF_TrackReferenceTypeBox *) gf_isom_box_new_parent(&tref->child_boxes, GF_ISOM_BOX_TYPE_REFT);
		if (!dpnd) return GF_OUT_OF_MEM;
		dpnd->reference_type = GF_ISOM_BOX_TYPE_SYNC;
		e = reftype_AddRefTrack(dpnd, esd->OCRESID, NULL);
		if (e) return e;
	} else if (dpnd && !esd->OCRESID) {
		Track_RemoveRef(trak, GF_ISOM_BOX_TYPE_SYNC);
	} else if (dpnd && esd->OCRESID) {
		if (dpnd->trackIDCount != 1) return GF_ISOM_INVALID_MEDIA;
		dpnd->trackIDs[0] = esd->OCRESID;
	}
	esd->OCRESID = 0;

	//brand new case: we have to change the IPI desc
	if (esd->ipiPtr) {
		e = Track_FindRef(trak, GF_ISOM_REF_IPI, &dpnd);
		if (e) return e;
		if (!dpnd) {
			tmpRef = 0;
			dpnd = (GF_TrackReferenceTypeBox *) gf_isom_box_new_parent(&tref->child_boxes, GF_ISOM_BOX_TYPE_REFT);
			if (!dpnd) return GF_OUT_OF_MEM;
			dpnd->reference_type = GF_ISOM_BOX_TYPE_IPIR;
			e = reftype_AddRefTrack(dpnd, esd->ipiPtr->IPI_ES_Id, &tmpRef);
			if (e) return e;
			//and replace the tag and value...
			esd->ipiPtr->IPI_ES_Id = tmpRef;
			esd->ipiPtr->tag = GF_ODF_ISOM_IPI_PTR_TAG;
		} else {
			//Watch out! ONLY ONE IPI dependency is allowed per stream
			if (dpnd->trackIDCount != 1) return GF_ISOM_INVALID_MEDIA;
			//if an existing one is there, what shall we do ???
			//donno, erase it
			dpnd->trackIDs[0] = esd->ipiPtr->IPI_ES_Id;
			//and replace the tag and value...
			esd->ipiPtr->IPI_ES_Id = 1;
			esd->ipiPtr->tag = GF_ODF_ISOM_IPI_PTR_TAG;
		}
	}

	/*don't store the lang desc in ESD, use the media header language info*/
	if (esd->langDesc) {
		trak->Media->mediaHeader->packedLanguage[0] = (esd->langDesc->langCode>>16)&0xFF;
		trak->Media->mediaHeader->packedLanguage[1] = (esd->langDesc->langCode>>8)&0xFF;
		trak->Media->mediaHeader->packedLanguage[2] = (esd->langDesc->langCode)&0xFF;
		gf_odf_desc_del((GF_Descriptor *)esd->langDesc);
		esd->langDesc = NULL;
	}

	//we have a streamDescriptionIndex, use it
	if (StreamDescriptionIndex) {
		u32 entry_type;
		entry = (GF_MPEGSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex - 1);
		if (!entry) return GF_ISOM_INVALID_FILE;

		entry_type = entry->type;
		GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF);
		if (sinf && sinf->original_format) entry_type = sinf->original_format->data_format;

		switch (entry_type) {
		case GF_ISOM_BOX_TYPE_MP4S:
			//OK, delete the previous ESD
			gf_odf_desc_del((GF_Descriptor *) entry->esd->desc);
			entry->esd->desc = esd;
			break;
		case GF_ISOM_BOX_TYPE_MP4V:
			entry_v = (GF_MPEGVisualSampleEntryBox*) entry;
			//OK, delete the previous ESD
			gf_odf_desc_del((GF_Descriptor *) entry_v->esd->desc);
			entry_v->esd->desc = esd;
			break;
		case GF_ISOM_BOX_TYPE_MP4A:
			entry_a = (GF_MPEGAudioSampleEntryBox*) entry;
            if (entry_a->esd) { // some non-conformant files may not have an ESD ...
                //OK, delete the previous ESD
                gf_odf_desc_del((GF_Descriptor *) entry_a->esd->desc);
                entry_a->esd->desc = esd;
            } else {
				// can't return OK here otherwise we can't know if esd hasn't been used
				// and need to be freed
				return GF_ISOM_INVALID_MEDIA;
			}
			break;
		case GF_ISOM_BOX_TYPE_AVC1:
		case GF_ISOM_BOX_TYPE_AVC2:
		case GF_ISOM_BOX_TYPE_AVC3:
		case GF_ISOM_BOX_TYPE_AVC4:
		case GF_ISOM_BOX_TYPE_SVC1:
		case GF_ISOM_BOX_TYPE_MVC1:
		case GF_ISOM_BOX_TYPE_HVC1:
		case GF_ISOM_BOX_TYPE_HEV1:
		case GF_ISOM_BOX_TYPE_HVC2:
		case GF_ISOM_BOX_TYPE_HEV2:
		case GF_ISOM_BOX_TYPE_LHE1:
		case GF_ISOM_BOX_TYPE_LHV1:
		case GF_ISOM_BOX_TYPE_HVT1:
		case GF_ISOM_BOX_TYPE_VVC1:
		case GF_ISOM_BOX_TYPE_VVI1:
			e = AVC_HEVC_UpdateESD((GF_MPEGVisualSampleEntryBox*)entry, esd);
			if (e) return e;
			break;
		case GF_ISOM_BOX_TYPE_LSR1:
			e = LSR_UpdateESD((GF_LASeRSampleEntryBox*)entry, esd);
			if (e) return e;
			break;
		case GF_ISOM_BOX_TYPE_AV01:
		case GF_ISOM_BOX_TYPE_AV1C:
		case GF_ISOM_BOX_TYPE_OPUS:
		case GF_ISOM_BOX_TYPE_DOPS:
		case GF_ISOM_BOX_TYPE_STXT:
		case GF_ISOM_BOX_TYPE_WVTT:
		case GF_ISOM_BOX_TYPE_STPP:
			if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
			break;

		default:
			//silently fail, not an MPEG-4 esd
			gf_odf_desc_del((GF_Descriptor *) esd);
			return GF_OK;
		}
	} else {
		//need to check we're not in URL mode where only ONE description is allowed...
		StreamDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes);
		if (StreamDescriptionIndex) {
			GF_ESD *old_esd=NULL;
			entry = (GF_MPEGSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex - 1);
			if (!entry) return GF_ISOM_INVALID_FILE;
			//get ESD (only if present, do not emulate)
			Media_GetESD(trak->Media, StreamDescriptionIndex, &old_esd, GF_TRUE);
			if (old_esd && old_esd->URLString) return GF_BAD_PARAM;
		}

		//OK, check the handler and create the entry
		switch (trak->Media->handler->handlerType) {
        case GF_ISOM_MEDIA_AUXV:
        case GF_ISOM_MEDIA_PICT:
		case GF_ISOM_MEDIA_VISUAL:
			if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_AVC) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_SVC) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_MVC)) {
				entry_v = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AVC1);
				if (!entry_v) return GF_OUT_OF_MEM;
				e = AVC_HEVC_UpdateESD((GF_MPEGVisualSampleEntryBox*)entry_v, esd);
				if (e) return  e;
			} else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_HEVC) {
				entry_v = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_HVC1);
				if (!entry_v) return GF_OUT_OF_MEM;
				e = AVC_HEVC_UpdateESD((GF_MPEGVisualSampleEntryBox*)entry_v, esd);
				if (e) return  e;
			} else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_VVC) {
				entry_v = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_VVC1);
				if (!entry_v) return GF_OUT_OF_MEM;
				e = AVC_HEVC_UpdateESD((GF_MPEGVisualSampleEntryBox*)entry_v, esd);
				if (e) return  e;
			} else {
				entry_v = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MP4V);
				if (!entry_v) return GF_OUT_OF_MEM;
				entry_v->esd = (GF_ESDBox *) gf_isom_box_new_parent(&entry_v->child_boxes, GF_ISOM_BOX_TYPE_ESDS);
				if (!entry_v->esd) return GF_OUT_OF_MEM;
				entry_v->esd->desc = esd;
			}

			//type cast possible now
			entry = (GF_MPEGSampleEntryBox*) entry_v;
			break;
		case GF_ISOM_MEDIA_AUDIO:
			if (esd->decoderConfig->objectTypeIndication == GF_CODECID_OPUS) {
				GF_MPEGAudioSampleEntryBox *opus = (GF_MPEGAudioSampleEntryBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_OPUS);
				if (!opus) return GF_OUT_OF_MEM;
				opus->cfg_opus = (GF_OpusSpecificBox *)gf_isom_box_new_parent(&opus->child_boxes, GF_ISOM_BOX_TYPE_DOPS);
				if (!opus->cfg_opus) return GF_OUT_OF_MEM;
				entry = (GF_MPEGSampleEntryBox*)opus;
				gf_odf_desc_del((GF_Descriptor *) esd);
			} else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_AC3) {
				GF_MPEGAudioSampleEntryBox *ac3 = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AC3);
				if (!ac3) return GF_OUT_OF_MEM;
				ac3->cfg_ac3 = (GF_AC3ConfigBox *) gf_isom_box_new_parent(&ac3->child_boxes, GF_ISOM_BOX_TYPE_DAC3);
				if (!ac3->cfg_ac3) return GF_OUT_OF_MEM;
				entry = (GF_MPEGSampleEntryBox*) ac3;
				gf_odf_desc_del((GF_Descriptor *) esd);
			} else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_EAC3) {
				GF_MPEGAudioSampleEntryBox *eac3 = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_EC3);
				if (!eac3) return GF_OUT_OF_MEM;
				eac3->cfg_ac3 = (GF_AC3ConfigBox *) gf_isom_box_new_parent(&eac3->child_boxes, GF_ISOM_BOX_TYPE_DEC3);
				if (!eac3->cfg_ac3) return GF_OUT_OF_MEM;
				entry = (GF_MPEGSampleEntryBox*) eac3;
				gf_odf_desc_del((GF_Descriptor *) esd);
			} else {
				entry_a = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MP4A);
				if (!entry_a) return GF_OUT_OF_MEM;
				entry_a->samplerate_hi = trak->Media->mediaHeader->timeScale;
				entry_a->esd = (GF_ESDBox *) gf_isom_box_new_parent(&entry_a->child_boxes, GF_ISOM_BOX_TYPE_ESDS);
				if (!entry_a->esd) return GF_OUT_OF_MEM;
				entry_a->esd->desc = esd;
				//type cast possible now
				entry = (GF_MPEGSampleEntryBox*) entry_a;
			}
			break;
		default:
			if ((esd->decoderConfig->streamType==0x03) && (esd->decoderConfig->objectTypeIndication==0x09)) {
				entry = (GF_MPEGSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_LSR1);
				if (!entry) return GF_OUT_OF_MEM;
				e = LSR_UpdateESD((GF_LASeRSampleEntryBox*)entry, esd);
				if (e) return  e;
			} else {
				entry = (GF_MPEGSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MP4S);
				entry->esd = (GF_ESDBox *) gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_ESDS);
				if (!entry->esd) return GF_OUT_OF_MEM;
				entry->esd->desc = esd;
			}
			break;
		}
		entry->dataReferenceIndex = DataReferenceIndex;

		if (!trak->Media->information->sampleTable->SampleDescription->child_boxes)
			trak->Media->information->sampleTable->SampleDescription->child_boxes = gf_list_new();
		gf_list_add(trak->Media->information->sampleTable->SampleDescription->child_boxes, entry);

		e = stsd_on_child_box((GF_Box*)trak->Media->information->sampleTable->SampleDescription, (GF_Box *) entry, GF_FALSE);
		if (e) return e;
		if(outStreamIndex) *outStreamIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes);
	}
	return GF_OK;
}

fixed #1736


GF_Err MergeTrack(GF_TrackBox *trak, GF_TrackFragmentBox *traf, GF_MovieFragmentBox *moof_box, u64 moof_offset, s32 compressed_diff, u64 *cumulated_offset, Bool is_first_merge)
{
	u32 i, j, chunk_size, track_num;
	u64 base_offset, data_offset, traf_duration;
	u32 def_duration, DescIndex, def_size, def_flags;
	u32 duration, size, flags, prev_trun_data_offset, sample_index;
	u8 pad, sync;
	u16 degr;
	Bool first_samp_in_traf=GF_TRUE;
	Bool store_traf_map=GF_FALSE;
	u8 *moof_template=NULL;
	u32 moof_template_size=0;
	Bool is_seg_start = GF_FALSE;
	u64 seg_start=0, sidx_start=0, sidx_end=0, frag_start=0, last_dts=0;
	GF_TrackFragmentRunBox *trun;
	GF_TrunEntry *ent;
#ifdef GF_ENABLE_CTRN
	GF_TrackFragmentBox *traf_ref = NULL;
#endif

	GF_Err stbl_AppendTime(GF_SampleTableBox *stbl, u32 duration, u32 nb_pack);
	GF_Err stbl_AppendSize(GF_SampleTableBox *stbl, u32 size, u32 nb_pack);
	GF_Err stbl_AppendChunk(GF_SampleTableBox *stbl, u64 offset);
	GF_Err stbl_AppendSampleToChunk(GF_SampleTableBox *stbl, u32 DescIndex, u32 samplesInChunk);
	GF_Err stbl_AppendCTSOffset(GF_SampleTableBox *stbl, s32 CTSOffset);
	GF_Err stbl_AppendRAP(GF_SampleTableBox *stbl, u8 isRap);
	GF_Err stbl_AppendPadding(GF_SampleTableBox *stbl, u8 padding);
	GF_Err stbl_AppendDegradation(GF_SampleTableBox *stbl, u16 DegradationPriority);

	if (trak->Header->trackID != traf->tfhd->trackID) return GF_OK;
	if (!trak->Media->information->sampleTable
		|| !trak->Media->information->sampleTable->SampleSize
		|| !trak->Media->information->sampleTable->TimeToSample
		|| !trak->Media->information->sampleTable->SampleToChunk
		|| !trak->Media->information->sampleTable->ChunkOffset
	) {
		return GF_ISOM_INVALID_FILE;
	}

	if (!traf->trex->track)
		traf->trex->track = trak;

	//setup all our defaults
	DescIndex = (traf->tfhd->flags & GF_ISOM_TRAF_SAMPLE_DESC) ? traf->tfhd->sample_desc_index : traf->trex->def_sample_desc_index;
	if (!DescIndex) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] default sample description set to 0, likely broken ! Fixing to 1\n" ));
		DescIndex = 1;
	} else if (DescIndex > gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes)) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] default sample description set to %d but only %d sample description(s), likely broken ! Fixing to 1\n", DescIndex, gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes)));
		DescIndex = 1;
	}
#ifdef GF_ENABLE_CTRN
	if (traf->trex->inherit_from_traf_id) {
		u32 traf_count = gf_list_count(moof_box->TrackList);
		for (i=0; i<traf_count; i++) {
			GF_TrackFragmentBox *atraf = gf_list_get(moof_box->TrackList, i);
			if (atraf->tfhd && atraf->tfhd->trackID==traf->trex->inherit_from_traf_id) {
				traf_ref = atraf;
				break;
			}
		}
	}
#endif

	def_duration = (traf->tfhd->flags & GF_ISOM_TRAF_SAMPLE_DUR) ? traf->tfhd->def_sample_duration : traf->trex->def_sample_duration;
	def_size = (traf->tfhd->flags & GF_ISOM_TRAF_SAMPLE_SIZE) ? traf->tfhd->def_sample_size : traf->trex->def_sample_size;
	def_flags = (traf->tfhd->flags & GF_ISOM_TRAF_SAMPLE_FLAGS) ? traf->tfhd->def_sample_flags : traf->trex->def_sample_flags;

	//locate base offset, by default use moof (dash-like)
	base_offset = moof_offset;
	//explicit base offset, use it
	if (traf->tfhd->flags & GF_ISOM_TRAF_BASE_OFFSET)
		base_offset = traf->tfhd->base_data_offset;
	//no moof offset and no explicit offset, the offset is the end of the last written chunk of
	//the previous traf. For the first traf, *cumulated_offset is actually moof offset
	else if (!(traf->tfhd->flags & GF_ISOM_MOOF_BASE_OFFSET))
		base_offset = *cumulated_offset;

	chunk_size = 0;
	prev_trun_data_offset = 0;
	data_offset = 0;
	traf_duration = 0;

	/*in playback mode*/
	if (traf->tfdt && is_first_merge) {
#ifndef GPAC_DISABLE_LOG
		if (trak->moov->mov->NextMoofNumber && trak->present_in_scalable_segment && trak->sample_count_at_seg_start && (trak->dts_at_seg_start != traf->tfdt->baseMediaDecodeTime)) {
			s32 drift = (s32) ((s64) traf->tfdt->baseMediaDecodeTime - (s64)trak->dts_at_seg_start);
			if (drift<0)  {
				GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Warning: TFDT timing "LLD" less than cumulated timing "LLD" - using tfdt\n", traf->tfdt->baseMediaDecodeTime, trak->dts_at_seg_start ));
			} else {
				GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[iso file] TFDT timing "LLD" higher than cumulated timing "LLD" (last sample got extended in duration)\n", traf->tfdt->baseMediaDecodeTime, trak->dts_at_seg_start ));
			}
		}
#endif
		trak->dts_at_seg_start = traf->tfdt->baseMediaDecodeTime;
	}
	else if (traf->tfxd) {
		trak->dts_at_seg_start = traf->tfxd->absolute_time_in_track_timescale;
	}

	if (traf->tfxd) {
		trak->last_tfxd_value = traf->tfxd->absolute_time_in_track_timescale;
		trak->last_tfxd_value += traf->tfxd->fragment_duration_in_track_timescale;
	}
	if (traf->tfrf) {
		if (trak->tfrf) gf_isom_box_del_parent(&trak->child_boxes, (GF_Box *)trak->tfrf);
		trak->tfrf = traf->tfrf;
		gf_list_del_item(traf->child_boxes, traf->tfrf);
		gf_list_add(trak->child_boxes, trak->tfrf);
	}

	if (trak->moov->mov->signal_frag_bounds) {
		store_traf_map = GF_TRUE;
		if (is_first_merge) {
			GF_MovieFragmentBox *moof_clone = NULL;
			gf_isom_box_freeze_order((GF_Box *)moof_box);
			gf_isom_clone_box((GF_Box *)moof_box, (GF_Box **)&moof_clone);

			if (moof_clone) {
				GF_BitStream *bs;
				for (i=0; i<gf_list_count(moof_clone->TrackList); i++) {
					GF_TrackFragmentBox *traf_clone = gf_list_get(moof_clone->TrackList, i);
					gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->TrackRuns);
					gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sampleGroups);
					gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sampleGroupsDescription);
					gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sub_samples);
					gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sai_offsets);
					gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sai_sizes);
					if (traf_clone->sample_encryption) {
						gf_isom_box_del_parent(&traf_clone->child_boxes, (GF_Box *) traf_clone->sample_encryption);
						traf_clone->sample_encryption = NULL;
					}
					if (traf_clone->sdtp) {
						gf_isom_box_del_parent(&traf_clone->child_boxes, (GF_Box *) traf_clone->sdtp);
						traf_clone->sdtp = NULL;
					}
				}
				gf_isom_box_size((GF_Box *)moof_clone);
				bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);

				if (trak->moov->mov->seg_styp) {
					gf_isom_box_size(trak->moov->mov->seg_styp);
					gf_isom_box_write(trak->moov->mov->seg_styp, bs);
				}
				if (trak->moov->mov->root_sidx) {
					gf_isom_box_size((GF_Box *)trak->moov->mov->root_sidx);
					gf_isom_box_write((GF_Box *)trak->moov->mov->root_sidx, bs);
				}
				if (trak->moov->mov->seg_ssix) {
					gf_isom_box_size(trak->moov->mov->seg_ssix);
					gf_isom_box_write(trak->moov->mov->seg_ssix, bs);
				}
				gf_isom_box_write((GF_Box *)moof_clone, bs);
				gf_isom_box_del((GF_Box*)moof_clone);

				gf_bs_get_content(bs, &moof_template, &moof_template_size);
				gf_bs_del(bs);
			}
		}
		if (trak->moov->mov->seg_styp) {
			is_seg_start = GF_TRUE;
			seg_start = trak->moov->mov->styp_start_offset;
		}
		if (trak->moov->mov->root_sidx) {
			is_seg_start = GF_TRUE;
			sidx_start = trak->moov->mov->sidx_start_offset;
			sidx_end = trak->moov->mov->sidx_end_offset;
			if (! seg_start || (sidx_start<seg_start))
				seg_start = sidx_start;
		}
		frag_start = trak->moov->mov->current_top_box_start;
	}
	else if (trak->moov->mov->store_traf_map) {
		store_traf_map = GF_TRUE;
	}

	sample_index = 0;
	i=0;
	while ((trun = (GF_TrackFragmentRunBox *)gf_list_enum(traf->TrackRuns, &i))) {
		//merge the run
		for (j=0; j<trun->sample_count; j++) {
			GF_Err e;
			s32 cts_offset=0;
			if (j<trun->nb_samples) {
				ent = &trun->samples[j];
			} else {
				GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Track %d doesn't have enough trun entries (%d) compared to sample count (%d) in run\n", traf->trex->trackID, trun->nb_samples, trun->sample_count ));
				break;
			}
			size = def_size;
			duration = def_duration;
			flags = def_flags;

			//CTS - if flag not set (trun or ctrn) defaults to 0 which is the base value after alloc
			//we just need to overrite its value if inherited
			cts_offset = ent->CTS_Offset;

#ifdef GF_ENABLE_CTRN
			if (trun->use_ctrn) {
				if (!j && (trun->ctrn_flags & GF_ISOM_CTRN_FIRST_SAMPLE) ) {
					if (trun->ctrn_first_dur) duration = ent->Duration;
					if (trun->ctrn_first_size) size = ent->size;
					if (trun->ctrn_first_ctts) flags = ent->flags;
				} else {
					if (trun->ctrn_dur) duration = ent->Duration;
					if (trun->ctrn_size) size = ent->size;
					if (trun->ctrn_sample_flags) flags = ent->flags;
				}
				/*re-override*/
				if (trun->ctrn_flags & 0xF0) {
					GF_TrunEntry *ref_entry;
					if (!traf_ref) {
						GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Track %d use traf inheritance to track ID %d but reference traf not found\n", traf->trex->trackID, traf->trex->inherit_from_traf_id ));
						break;
					}
					ref_entry = traf_get_sample_entry(traf_ref, sample_index);
					if (!ref_entry) {
						GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Track %d use traf inheritance but sample %d not found in reference traf\n", traf->trex->trackID, sample_index+1 ));
						break;
					}
					if (trun->ctrn_flags & GF_ISOM_CTRN_INHERIT_DUR)
						duration = ref_entry->Duration;
					if (trun->ctrn_flags & GF_ISOM_CTRN_INHERIT_SIZE)
						size = ref_entry->size;
					if (trun->ctrn_flags & GF_ISOM_CTRN_INHERIT_FLAGS)
						flags = ref_entry->flags;
					if (trun->ctrn_flags & GF_ISOM_CTRN_INHERIT_CTSO)
						cts_offset = ref_entry->CTS_Offset;
				}

			} else
#endif
			{
				if (trun->flags & GF_ISOM_TRUN_DURATION) duration = ent->Duration;
				if (trun->flags & GF_ISOM_TRUN_SIZE) size = ent->size;
				if (trun->flags & GF_ISOM_TRUN_FLAGS) {
					flags = ent->flags;
				} else if (!j && (trun->flags & GF_ISOM_TRUN_FIRST_FLAG)) {
					flags = trun->first_sample_flags;
				}
			}
			sample_index++;
			/*store the resolved value in case we have inheritance*/
			ent->size = size;
			ent->Duration = duration;
			ent->flags = flags;
			ent->CTS_Offset = cts_offset;

			last_dts += duration;

			//add size first
			if (!trak->Media->information->sampleTable->SampleSize) {
				trak->Media->information->sampleTable->SampleSize = (GF_SampleSizeBox *) gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_STSZ);
				if (!trak->Media->information->sampleTable->SampleSize)
					return GF_OUT_OF_MEM;
			}
			e = stbl_AppendSize(trak->Media->information->sampleTable, size, ent->nb_pack);
			if (e) return e;

			//then TS
			if (!trak->Media->information->sampleTable->TimeToSample) {
				trak->Media->information->sampleTable->TimeToSample = (GF_TimeToSampleBox *) gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_STTS);
				if (!trak->Media->information->sampleTable->TimeToSample)
					return GF_OUT_OF_MEM;
			}
			e = stbl_AppendTime(trak->Media->information->sampleTable, duration, ent->nb_pack);
			if (e) return e;

			//add chunk on first sample
			if (!j) {
				u64 final_offset;
				data_offset = base_offset;
				//we have an explicit data offset for this trun
				if (trun->flags & GF_ISOM_TRUN_DATA_OFFSET) {
					data_offset += trun->data_offset;
					/*reset chunk size since data is now relative to this trun*/
					chunk_size = 0;
					/*remember this data offset for following trun*/
					prev_trun_data_offset = trun->data_offset;
					/*if mdat is located after the moof, and the moof was compressed, adjust offset
					otherwise the offset does not need adjustment*/
					if (trun->data_offset>=0) {
						data_offset -= compressed_diff;
						prev_trun_data_offset -= compressed_diff;
					}
				}
				//we had an explicit data offset for the previous trun, use it + chunk size
				else if (prev_trun_data_offset) {
					/*data offset is previous chunk size plus previous offset of the trun*/
					data_offset += prev_trun_data_offset + chunk_size;
				}
				//no explicit data offset, continuous data after last data in previous chunk
				else {
					data_offset += chunk_size;
					//data offset of first trun in first traf, adjust if compressed moof
					if ((i==1) && (trun->data_offset>=0)) {
						data_offset -= compressed_diff;
					}
				}

				final_offset = data_offset;
				//adjust offset if moov was also compressed and we are still in the same file
				//so that later call to gf_isom_get_sample properly adjust back the offset
				if (trak->moov->compressed_diff) {
					final_offset += trak->moov->compressed_diff;
				}

				if (!trak->Media->information->sampleTable->ChunkOffset) {
					trak->Media->information->sampleTable->ChunkOffset = gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_STCO);
					if (!trak->Media->information->sampleTable->ChunkOffset)
						return GF_OUT_OF_MEM;
				}
				e = stbl_AppendChunk(trak->Media->information->sampleTable, final_offset);
				if (e) return e;
				//then sampleToChunk
				if (!trak->Media->information->sampleTable->SampleToChunk) {
					trak->Media->information->sampleTable->SampleToChunk = (GF_SampleToChunkBox *) gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_STSC);
					if (!trak->Media->information->sampleTable->SampleToChunk)
						return GF_OUT_OF_MEM;
				}
				e = stbl_AppendSampleToChunk(trak->Media->information->sampleTable,
				                         DescIndex, trun->sample_count);
				if (e) return e;
			}
			chunk_size += size;

			if (store_traf_map && first_samp_in_traf) {
				first_samp_in_traf = GF_FALSE;
				e = stbl_AppendTrafMap(trak->Media->information->sampleTable, is_seg_start, seg_start, frag_start, moof_template, moof_template_size, sidx_start, sidx_end);
				if (e) return e;
				//do not deallocate, the memory is now owned by traf map
				moof_template = NULL;
				moof_template_size = 0;
			}
			if (ent->nb_pack>1) {
				j+= ent->nb_pack-1;
				traf_duration += ent->nb_pack*duration;
				continue;
			}

			traf_duration += duration;

			e = stbl_AppendCTSOffset(trak->Media->information->sampleTable, cts_offset);
			if (e) return e;
			//flags
			sync = GF_ISOM_GET_FRAG_SYNC(flags);
			if (trak->Media->information->sampleTable->no_sync_found && sync) {
				trak->Media->information->sampleTable->no_sync_found = 0;
			}
			e = stbl_AppendRAP(trak->Media->information->sampleTable, sync);
			if (e) return e;
			pad = GF_ISOM_GET_FRAG_PAD(flags);
			if (pad) {
				e = stbl_AppendPadding(trak->Media->information->sampleTable, pad);
				if (e) return e;
			}
			degr = GF_ISOM_GET_FRAG_DEG(flags);
			if (degr) {
				e = stbl_AppendDegradation(trak->Media->information->sampleTable, degr);
				if (e) return e;
			}
			e = stbl_AppendDependencyType(trak->Media->information->sampleTable, GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags));
			if (e) return e;
		}
	}

	if (trak->moov->mov->is_smooth && !traf->tfdt && !traf->tfxd) {
		if (is_first_merge)
			trak->dts_at_seg_start = trak->dts_at_next_seg_start;
		trak->dts_at_next_seg_start += last_dts;
	}
	if (traf_duration && trak->editBox && trak->editBox->editList) {
		for (i=0; i<gf_list_count(trak->editBox->editList->entryList); i++) {
			GF_EdtsEntry *edts_e = gf_list_get(trak->editBox->editList->entryList, i);
			if (edts_e->was_empty_dur) {
				u64 extend_dur = traf_duration;
				extend_dur *= trak->moov->mvhd->timeScale;
				extend_dur /= trak->Media->mediaHeader->timeScale;
				edts_e->segmentDuration += extend_dur;
			}
			else if (!edts_e->segmentDuration) {
				edts_e->was_empty_dur = GF_TRUE;
				if ((s64) traf_duration > edts_e->mediaTime)
					traf_duration -= edts_e->mediaTime;
				else
					traf_duration = 0;

				edts_e->segmentDuration = traf_duration;
				edts_e->segmentDuration *= trak->moov->mvhd->timeScale;
				edts_e->segmentDuration /= trak->Media->mediaHeader->timeScale;
			}

		}
	}

	//in any case, update the cumulated offset
	//this will handle hypothetical files mixing MOOF offset and implicit non-moof offset
	*cumulated_offset = data_offset + chunk_size;

	/*merge sample groups*/
	if (traf->sampleGroups) {
		GF_List *groups;
		GF_List *groupDescs;
		Bool is_identical_sgpd = GF_TRUE;
		u32 *new_idx = NULL, new_idx_count=0;

		if (!trak->Media->information->sampleTable->sampleGroups)
			trak->Media->information->sampleTable->sampleGroups = gf_list_new();

		if (!trak->Media->information->sampleTable->sampleGroupsDescription)
			trak->Media->information->sampleTable->sampleGroupsDescription = gf_list_new();

		groupDescs = trak->Media->information->sampleTable->sampleGroupsDescription;
		for (i=0; i<gf_list_count(traf->sampleGroupsDescription); i++) {
			GF_SampleGroupDescriptionBox *new_sgdesc = NULL;
			GF_SampleGroupDescriptionBox *sgdesc = gf_list_get(traf->sampleGroupsDescription, i);
			for (j=0; j<gf_list_count(groupDescs); j++) {
				new_sgdesc = gf_list_get(groupDescs, j);
				if (new_sgdesc->grouping_type==sgdesc->grouping_type) break;
				new_sgdesc = NULL;
			}
			/*new description, move it to our sample table*/
			if (!new_sgdesc) {
				gf_list_add(groupDescs, sgdesc);
				gf_list_add(trak->Media->information->sampleTable->child_boxes, sgdesc);
				gf_list_rem(traf->sampleGroupsDescription, i);
				gf_list_del_item(traf->child_boxes, sgdesc);
				i--;
			}
			/*merge descriptions*/
			else {
				u32 count;

				is_identical_sgpd = gf_isom_is_identical_sgpd(new_sgdesc, sgdesc, 0);
				if (is_identical_sgpd)
					continue;

				new_idx_count = gf_list_count(sgdesc->group_descriptions);
				new_idx = (u32 *)gf_malloc(new_idx_count * sizeof(u32));
				if (!new_idx) return GF_OUT_OF_MEM;

				count = 0;
				while (gf_list_count(sgdesc->group_descriptions)) {
					void *sgpd_entry = gf_list_get(sgdesc->group_descriptions, 0);
					Bool new_entry = GF_TRUE;

					for (j = 0; j < gf_list_count(new_sgdesc->group_descriptions); j++) {
						void *ptr = gf_list_get(new_sgdesc->group_descriptions, j);
						if (gf_isom_is_identical_sgpd(sgpd_entry, ptr, new_sgdesc->grouping_type)) {
							new_idx[count] = j + 1;
							count ++;
							new_entry = GF_FALSE;
							gf_free(sgpd_entry);
							break;
						}
					}

					if (new_entry) {
						gf_list_add(new_sgdesc->group_descriptions, sgpd_entry);
						new_idx[count] = gf_list_count(new_sgdesc->group_descriptions);
						count ++;
					}

					gf_list_rem(sgdesc->group_descriptions, 0);
				}
			}
		}

		groups = trak->Media->information->sampleTable->sampleGroups;
		for (i=0; i<gf_list_count(traf->sampleGroups); i++) {
			GF_SampleGroupBox *stbl_group = NULL;
			GF_SampleGroupBox *frag_group = gf_list_get(traf->sampleGroups, i);

			for (j=0; j<gf_list_count(groups); j++) {
				stbl_group = gf_list_get(groups, j);
				if ((frag_group->grouping_type==stbl_group->grouping_type) && (frag_group->grouping_type_parameter==stbl_group->grouping_type_parameter))
					break;
				stbl_group = NULL;
			}
			if (!stbl_group) {
				stbl_group = (GF_SampleGroupBox *) gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_SBGP);
				if (!stbl_group) return GF_OUT_OF_MEM;
				stbl_group->grouping_type = frag_group->grouping_type;
				stbl_group->grouping_type_parameter = frag_group->grouping_type_parameter;
				stbl_group->version = frag_group->version;
				gf_list_add(groups, stbl_group);
			}

			if (is_identical_sgpd) {
				//adjust sgpd index: in traf index start at 0x1001
				for (j = 0; j < frag_group->entry_count; j++)
					frag_group->sample_entries[j].group_description_index &= 0x0FFFF;
				if (frag_group->entry_count && stbl_group->entry_count &&
				        (frag_group->sample_entries[0].group_description_index==stbl_group->sample_entries[stbl_group->entry_count-1].group_description_index)
				   ) {
					stbl_group->sample_entries[stbl_group->entry_count - 1].sample_count += frag_group->sample_entries[0].sample_count;
					if (frag_group->entry_count>1) {
						stbl_group->sample_entries = gf_realloc(stbl_group->sample_entries, sizeof(GF_SampleGroupEntry) * (stbl_group->entry_count + frag_group->entry_count - 1));
						memcpy(&stbl_group->sample_entries[stbl_group->entry_count], &frag_group->sample_entries[1], sizeof(GF_SampleGroupEntry) * (frag_group->entry_count - 1));
						stbl_group->entry_count += frag_group->entry_count - 1;
					}
				} else {
					stbl_group->sample_entries = gf_realloc(stbl_group->sample_entries, sizeof(GF_SampleGroupEntry) * (stbl_group->entry_count + frag_group->entry_count));
					memcpy(&stbl_group->sample_entries[stbl_group->entry_count], &frag_group->sample_entries[0], sizeof(GF_SampleGroupEntry) * frag_group->entry_count);
					stbl_group->entry_count += frag_group->entry_count;
				}
			} else {
				stbl_group->sample_entries = gf_realloc(stbl_group->sample_entries, sizeof(GF_SampleGroupEntry) * (stbl_group->entry_count + frag_group->entry_count));
				//adjust sgpd index
				for (j = 0; j < frag_group->entry_count; j++) {
					u32 sgidx = frag_group->sample_entries[j].group_description_index;
					if (sgidx > 0x10000) {
						sgidx -= 0x10001;
						if (sgidx>=new_idx_count) {
							GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[isobmf] corrupted sample group index in fragment %d but only %d group descriptions in fragment\n", sgidx, new_idx_count));
						} else {
							frag_group->sample_entries[j].group_description_index = new_idx[sgidx];
						}
					}
				}
				memcpy(&stbl_group->sample_entries[stbl_group->entry_count], &frag_group->sample_entries[0], sizeof(GF_SampleGroupEntry) * frag_group->entry_count);
				stbl_group->entry_count += frag_group->entry_count;
			}
		}

		if (new_idx) gf_free(new_idx);
	}

	/*content is encrypted*/
	track_num = gf_isom_get_tracknum_from_id(trak->moov, trak->Header->trackID);
	if (gf_isom_is_cenc_media(trak->moov->mov, track_num, DescIndex)
		|| traf->sample_encryption) {
		/*Merge sample auxiliary encryption information*/
		GF_SampleEncryptionBox *senc = NULL;
		u32 scheme_type;
		gf_isom_get_cenc_info(trak->moov->mov, track_num, DescIndex, NULL, &scheme_type, NULL);

		if (traf->sample_encryption) {
			for (i = 0; i < gf_list_count(trak->Media->information->sampleTable->child_boxes); i++) {
				GF_Box *a = (GF_Box *)gf_list_get(trak->Media->information->sampleTable->child_boxes, i);
				if (a->type != traf->sample_encryption->type) continue;

				if ((a->type ==GF_ISOM_BOX_TYPE_UUID) && (((GF_UUIDBox *)a)->internal_4cc == GF_ISOM_BOX_UUID_PSEC)) {
					senc = (GF_SampleEncryptionBox *)a;
					break;
				}
				else if (a->type ==GF_ISOM_BOX_TYPE_SENC) {
					senc = (GF_SampleEncryptionBox *)a;
					break;
				}
			}
			if (!senc && trak->sample_encryption)
				senc = trak->sample_encryption;

			if (!senc) {
				if (traf->sample_encryption->piff_type==1) {
					senc = (GF_SampleEncryptionBox *)gf_isom_create_piff_psec_box(1, 0x2, 0, 0, NULL);
				} else {
					senc = gf_isom_create_samp_enc_box(1, 0x2);
				}

				if (!trak->Media->information->sampleTable->child_boxes) trak->Media->information->sampleTable->child_boxes = gf_list_new();

				trak->sample_encryption = senc;
				if (!trak->child_boxes) trak->child_boxes = gf_list_new();
				gf_list_add(trak->child_boxes, senc);
			}
		}

		/*get sample auxiliary information by saiz/saio rather than by parsing senc box*/
		if (gf_isom_cenc_has_saiz_saio_traf(traf, scheme_type)) {
			u32 nb_saio;
			u32 aux_info_type;
			u64 offset;
			GF_Err e;
			Bool is_encrypted;
			GF_SampleAuxiliaryInfoOffsetBox *saio = NULL;
			GF_SampleAuxiliaryInfoSizeBox *saiz = NULL;

			offset = nb_saio = 0;

			for (i = 0; i < gf_list_count(traf->sai_offsets); i++) {
				saio = (GF_SampleAuxiliaryInfoOffsetBox *)gf_list_get(traf->sai_offsets, i);
				aux_info_type = saio->aux_info_type;
				if (!aux_info_type) aux_info_type = scheme_type;

				/*if we have only 1 sai_offsets, assume that its type is cenc*/
				if ((aux_info_type == GF_ISOM_CENC_SCHEME) || (aux_info_type == GF_ISOM_CBC_SCHEME) ||
					(aux_info_type == GF_ISOM_CENS_SCHEME) || (aux_info_type == GF_ISOM_CBCS_SCHEME) ||
					(gf_list_count(traf->sai_offsets) == 1)) {
					if (saio->offsets && saio->entry_count) {
						offset = saio->offsets[0] + moof_offset;
						nb_saio = saio->entry_count;
						break;
					}
				}
				saio = NULL;
			}
			for (i = 0; i < gf_list_count(traf->sai_sizes); i++) {
				saiz = (GF_SampleAuxiliaryInfoSizeBox *)gf_list_get(traf->sai_sizes, i);
				aux_info_type = saiz->aux_info_type;
				if (!aux_info_type) aux_info_type = scheme_type;
				/*if we have only 1 sai_sizes, assume that its type is cenc*/
				if ((aux_info_type == GF_ISOM_CENC_SCHEME) || (aux_info_type == GF_ISOM_CBC_SCHEME) ||
					(aux_info_type == GF_ISOM_CENS_SCHEME) || (aux_info_type == GF_ISOM_CBCS_SCHEME) ||
					(gf_list_count(traf->sai_sizes) == 1)) {
					break;
				}
				saiz = NULL;
			}
			if (saiz && saio && senc) {
				for (i = 0; i < saiz->sample_count; i++) {
					GF_CENCSampleAuxInfo *sai;
					const u8 *key_info=NULL;
					u32 key_info_size;
					u64 cur_position;
					if (nb_saio != 1)
						offset = saio->offsets[i] + moof_offset;
					size = saiz->default_sample_info_size ? saiz->default_sample_info_size : saiz->sample_info_size[i];

					cur_position = gf_bs_get_position(trak->moov->mov->movieFileMap->bs);
					gf_bs_seek(trak->moov->mov->movieFileMap->bs, offset);

					GF_SAFEALLOC(sai, GF_CENCSampleAuxInfo);
					if (!sai) return GF_OUT_OF_MEM;

					e = gf_isom_get_sample_cenc_info_internal(trak, traf, senc, i+1, &is_encrypted, NULL, NULL, &key_info, &key_info_size);
					if (e) {
						GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] could not get cenc info for sample %d: %s\n", i+1, gf_error_to_string(e) ));
						return e;
					}

					if (is_encrypted) {
						sai->cenc_data_size = size;
						sai->cenc_data = gf_malloc(sizeof(u8)*size);
						if (!sai->cenc_data) return GF_OUT_OF_MEM;
						gf_bs_read_data(trak->moov->mov->movieFileMap->bs, sai->cenc_data, sai->cenc_data_size);
					} else {
						sai->isNotProtected=1;
					}

					if (key_info) {
						//not multikey
						if (!key_info[0]) {
							//size greater than IV
							if (size > key_info[3])
								senc->flags = 0x00000002;
						}
						//multikey, always use subsamples
						else {
							senc->flags = 0x00000002;
						}
					}

					gf_bs_seek(trak->moov->mov->movieFileMap->bs, cur_position);

					gf_list_add(senc->samp_aux_info, sai);

					e = gf_isom_cenc_merge_saiz_saio(senc, trak->Media->information->sampleTable, offset, size);
					if (e) return e;
					if (nb_saio == 1)
						offset += size;
				}
			}
		} else if (traf->sample_encryption) {
			senc_Parse(trak->moov->mov->movieFileMap->bs, trak, traf, traf->sample_encryption);
			trak->sample_encryption->AlgorithmID = traf->sample_encryption->AlgorithmID;
			if (!trak->sample_encryption->IV_size)
				trak->sample_encryption->IV_size = traf->sample_encryption->IV_size;
			if (!trak->sample_encryption->samp_aux_info) trak->sample_encryption->samp_aux_info = gf_list_new();
			gf_list_transfer(trak->sample_encryption->samp_aux_info, traf->sample_encryption->samp_aux_info);
			if (traf->sample_encryption->flags & 0x00000002)
				trak->sample_encryption->flags |= 0x00000002;
		}
	}
	return GF_OK;
}

fixed #1736


u32 gf_isom_get_tracknum_from_id(GF_MovieBox *moov, GF_ISOTrackID trackID)
{
	u32 i;
	GF_TrackBox *trak;
	i=0;
	while ((trak = (GF_TrackBox *)gf_list_enum(moov->trackList, &i))) {
		if (trak->Header->trackID == trackID) return i;
	}
	return 0;
}

fixed #1736