/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2018-2019
 *					All rights reserved
 *
 *  This file is part of GPAC / GPAC stream serializer filter
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <gpac/filters.h>
#include <gpac/constants.h>
#include <gpac/bitstream.h>
#include <gpac/network.h>
#include <gpac/internal/media_dev.h>
#include <gpac/crypt.h>


typedef struct
{
	u32 offset;
	u32 size;
} GSF_PacketFragment;

typedef struct
{
	GF_FilterPacket *pck;
	u8 *output;
	u32 full_block_size, nb_bytes, recv_bytes;
	Bool corrupted;
	u16 frame_sn;
	u8 pck_type;
	u8 crypted;

	u32 nb_frags, nb_alloc_frags, nb_recv_frags;
	Bool complete;
	GSF_PacketFragment *frags;

} GSF_Packet;


typedef struct
{
	GF_FilterPid *opid;
	GF_List *packets;

	u32 idx;
	u16 nb_pck;
	u8 config_version;

	u16 last_frame_sn;
} GSF_Stream;

typedef struct
{
	//opts
	const char *magic;
	GF_PropData key;
	u32 pad, mq;


	//only one output pid declared
	GF_FilterPid *ipid;

	GF_List *streams;

	bin128 crypt_IV;
	GF_Crypt *crypt;
	u16 crypt_blocks, skip_blocks;

	u32 nb_playing;
	Double start_range;
	Bool is_file;
	u64 file_pos, file_size;
	GF_Fraction64 duration;
	Bool initial_play_done;

	GF_BitStream *bs_r;
	GF_BitStream *bs_pck;

	//signaling stream
	GSF_Stream *signal_st;

	//where we store incoming packets
	char *buffer;
	u32 alloc_size, buf_size;

	u32 missing_bytes;
	Bool tuned;
	Bool tune_error;
	Bool use_seq_num;
	Bool wait_for_play;

	GF_List *pck_res;
	Bool buffer_too_small;
} GSF_DemuxCtx;


typedef enum
{
	GFS_PCKTYPE_HDR=0,
	GFS_PCKTYPE_PID_CONFIG,
	GFS_PCKTYPE_PID_INFO_UPDATE,
	GFS_PCKTYPE_PID_REMOVE,
	GFS_PCKTYPE_PID_EOS,
	GFS_PCKTYPE_PCK,

	GFS_PCKTYPE_UNDEF1,
	GFS_PCKTYPE_UNDEF2,
	GFS_PCKTYPE_UNDEF3,
	GFS_PCKTYPE_UNDEF4,
	GFS_PCKTYPE_UNDEF5,
	GFS_PCKTYPE_UNDEF6,
	GFS_PCKTYPE_UNDEF7,
	GFS_PCKTYPE_UNDEF8,
	GFS_PCKTYPE_UNDEF9,
	GFS_PCKTYPE_UNDEF10,

	/*NO MORE PACKET TYPE AVAILABLE*/

} GSF_PacketType;



GF_Err gsfdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
	GSF_DemuxCtx *ctx = gf_filter_get_udta(filter);

	if (is_remove) {
		ctx->ipid = NULL;

		while (gf_list_count(ctx->streams)) {
			GSF_Stream *st = gf_list_pop_back(ctx->streams);
			if (st->opid)
				gf_filter_pid_remove(st->opid);
			gf_free(st);
		}
		return GF_OK;
	}
	if (! gf_filter_pid_check_caps(pid))
		return GF_NOT_SUPPORTED;

	ctx->ipid = pid;
	return GF_OK;
}

static Bool gsfdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
{
	GF_FilterEvent fevt;
	GSF_DemuxCtx *ctx = gf_filter_get_udta(filter);

	switch (evt->base.type) {
	case GF_FEVT_PLAY:
		if (ctx->nb_playing && (ctx->start_range == evt->play.start_range)) {
			return GF_TRUE;
		}
		ctx->nb_playing++;
		ctx->wait_for_play = GF_FALSE;

		if (! ctx->is_file) {
			return GF_FALSE;
		}
//		safdmx_check_dur(ctx);

		ctx->start_range = evt->play.start_range;
		ctx->file_pos = 0;
		if (ctx->duration.num) {
			ctx->file_pos = (u64) (ctx->file_size * ctx->start_range);
			ctx->file_pos *= ctx->duration.den;
			ctx->file_pos /= ctx->duration.num;
			if (ctx->file_pos>ctx->file_size) return GF_TRUE;
		}

		if (!ctx->initial_play_done) {
			ctx->initial_play_done = GF_TRUE;
			//seek will not change the current source state, don't send a seek
			if (!ctx->file_pos)
				return GF_TRUE;
		}
		//post a seek to 0 - we would need to build an index of AUs to find the next place to seek
		GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid);
		fevt.seek.start_offset = 0;
		gf_filter_pid_send_event(ctx->ipid, &fevt);

		//cancel event
		return GF_TRUE;

	case GF_FEVT_STOP:
		ctx->nb_playing--;
		//don't cancel event
		return GF_FALSE;

	case GF_FEVT_SET_SPEED:
		//cancel event
		return GF_TRUE;
	default:
		break;
	}
	//by default don't cancel event - to rework once we have downloading in place
	return GF_FALSE;
}

static void gsfdmx_decrypt(GSF_DemuxCtx *ctx, char *data, u32 size)
{
#ifndef GPAC_DISABLE_CRYPTO
	u32 pos=0;
	u32 clear_tail = size%16;
	u32 bytes_crypted = size - clear_tail;

	if (!bytes_crypted) return;

	gf_crypt_set_IV(ctx->crypt, ctx->crypt_IV, 16);
	if (ctx->crypt_blocks && ctx->skip_blocks) {
		u32 pattern_length = 16 * (ctx->crypt_blocks + ctx->skip_blocks);
		u32 cryp_len = 16 * ctx->crypt_blocks;
		while (bytes_crypted) {
			gf_crypt_decrypt(ctx->crypt, data+pos, bytes_crypted >= cryp_len ? cryp_len : bytes_crypted);
			if (bytes_crypted >= pattern_length) {
				pos += pattern_length;
				bytes_crypted -= pattern_length;
			} else {
				bytes_crypted = 0;
			}
		}
	} else {
		gf_crypt_decrypt(ctx->crypt, data, bytes_crypted);
	}
#endif
}

static GFINLINE u32 gsfdmx_read_vlen(GF_BitStream *bs)
{
	if (!gf_bs_read_int(bs, 1))
		return gf_bs_read_int(bs, 7);
	if (!gf_bs_read_int(bs, 1))
		return gf_bs_read_int(bs, 14);
	if (!gf_bs_read_int(bs, 1))
		return gf_bs_read_int(bs, 21);
	if (!gf_bs_read_int(bs, 1))
		return gf_bs_read_int(bs, 28);

	return (u32) gf_bs_read_long_int(bs, 36);
}

static GF_Err gsfdmx_read_prop(GF_BitStream *bs, GF_PropertyValue *p)
{
	u32 len, len2, i;

	switch (p->type) {
	case GF_PROP_SINT:
	case GF_PROP_UINT:
	case GF_PROP_PIXFMT:
	case GF_PROP_PCMFMT:
		p->value.uint = gsfdmx_read_vlen(bs);
		break;
	case GF_PROP_LSINT:
	case GF_PROP_LUINT:
		p->value.longuint = gf_bs_read_u64(bs);
		break;
	case GF_PROP_BOOL:
		p->value.boolean = gf_bs_read_u8(bs) ? 1 : 0;
		break;
	case GF_PROP_FRACTION:
		p->value.frac.num = gsfdmx_read_vlen(bs);
		p->value.frac.den = gsfdmx_read_vlen(bs);
		break;
	case GF_PROP_FRACTION64:
		p->value.lfrac.num = gf_bs_read_u64(bs);
		p->value.lfrac.den = gf_bs_read_u64(bs);
		break;
	case GF_PROP_FLOAT:
		p->value.fnumber = FLT2FIX( gf_bs_read_float(bs) );
		break;
	case GF_PROP_DOUBLE:
		p->value.number = gf_bs_read_double(bs);
		break;
	case GF_PROP_VEC2I:
		p->value.vec2i.x = gsfdmx_read_vlen(bs);
		p->value.vec2i.y = gsfdmx_read_vlen(bs);
		break;
	case GF_PROP_VEC2:
		p->value.vec2.x = gf_bs_read_double(bs);
		p->value.vec2.y = gf_bs_read_double(bs);
		break;
	case GF_PROP_VEC3I:
		p->value.vec3i.x = gsfdmx_read_vlen(bs);
		p->value.vec3i.y = gsfdmx_read_vlen(bs);
		p->value.vec3i.z = gsfdmx_read_vlen(bs);
		break;
	case GF_PROP_VEC3:
		p->value.vec3.x = gf_bs_read_double(bs);
		p->value.vec3.y = gf_bs_read_double(bs);
		p->value.vec3.z = gf_bs_read_double(bs);
		break;
	case GF_PROP_VEC4I:
		p->value.vec4i.x = gsfdmx_read_vlen(bs);
		p->value.vec4i.y = gsfdmx_read_vlen(bs);
		p->value.vec4i.z = gsfdmx_read_vlen(bs);
		p->value.vec4i.w = gsfdmx_read_vlen(bs);
		break;
	case GF_PROP_VEC4:
		p->value.vec4.x = gf_bs_read_double(bs);
		p->value.vec4.y = gf_bs_read_double(bs);
		p->value.vec4.z = gf_bs_read_double(bs);
		p->value.vec4.w = gf_bs_read_double(bs);
		break;
	case GF_PROP_STRING:
	case GF_PROP_STRING_NO_COPY:
	case GF_PROP_NAME:
		p->type = GF_PROP_STRING_NO_COPY;
		len = gsfdmx_read_vlen(bs);
		p->value.string = gf_malloc(sizeof(char)*(len+1));
		gf_bs_read_data(bs, p->value.string, len);
		p->value.string[len]=0;
		break;

	case GF_PROP_DATA:
	case GF_PROP_DATA_NO_COPY:
	case GF_PROP_CONST_DATA:
		p->type = GF_PROP_DATA_NO_COPY;
		p->value.data.size = gsfdmx_read_vlen(bs);
		p->value.data.ptr = gf_malloc(sizeof(char) * p->value.data.size);
		gf_bs_read_data(bs, p->value.data.ptr, p->value.data.size);
		break;

	case GF_PROP_STRING_LIST:
		p->value.string_list = gf_list_new();
		len2 = gsfdmx_read_vlen(bs);
		for (i=0; i<len2; i++) {
			len = gsfdmx_read_vlen(bs);
			char *str = gf_malloc(sizeof(char)*(len+1));
			gf_bs_read_data(bs, str, len);
			str[len] = 0;
			gf_list_add(p->value.string_list, str);
		}
		break;

	case GF_PROP_UINT_LIST:
		p->value.uint_list.nb_items = len = gsfdmx_read_vlen(bs);
		p->value.uint_list.vals = gf_malloc(sizeof(u32)*len);
		for (i=0; i<len; i++) {
			p->value.uint_list.vals[i] = gsfdmx_read_vlen(bs);
		}
		break;
	case GF_PROP_POINTER:
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] pointer property found in serialized stream, illegal\n"));
		return GF_NON_COMPLIANT_BITSTREAM;
	default:
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Cannot deserialize property of unknown type\n"));
		return GF_NON_COMPLIANT_BITSTREAM;
	}
	return GF_OK;
}

static GSF_Stream *gsfdmx_get_stream(GF_Filter *filter, GSF_DemuxCtx *ctx, u32 idx, u32 pkt_type)
{
	GSF_Stream *gst;
	u32 i, count;

	count = gf_list_count(ctx->streams);
	for (i=0; i<count; i++) {
		gst = gf_list_get(ctx->streams, i);
		if (gst->idx == idx) return gst;
	}

	if ((pkt_type==GFS_PCKTYPE_PID_CONFIG) || (pkt_type==GFS_PCKTYPE_PID_INFO_UPDATE) ) {
		GF_SAFEALLOC(gst, GSF_Stream);
		if (!gst) return NULL;
		gst->packets = gf_list_new();
		gst->idx = idx;
		gf_list_add(ctx->streams, gst);
		gst->opid = gf_filter_pid_new(filter);
		return gst;
	}

	GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[GSFDemux] no stream found for idx %d\n", idx));
	return NULL;
}
static GF_Err gsfdmx_parse_pid_info(GF_Filter *filter, GSF_DemuxCtx *ctx, GSF_Stream *gst, GSF_Packet *pck, Bool is_info_update)
{
	GF_Err e;
	u32 nb_4cc_props=0;
	u32 nb_str_props=0;
	u32 i;
	u8 cfg_version;
	GF_BitStream *bs=NULL;

	if (pck->crypted) {
		gsfdmx_decrypt(ctx, pck->output, pck->full_block_size);
	}
	e = gf_bs_reassign_buffer(ctx->bs_pck, pck->output, pck->full_block_size);
	if (e) return e;
	bs = ctx->bs_pck;

	cfg_version = gf_bs_read_u8(bs);
	if ((gst->config_version == 1 + cfg_version) && !is_info_update)
		return GF_OK;
	gst->config_version = 1 + cfg_version;

	nb_4cc_props = gsfdmx_read_vlen(bs);
	nb_str_props = gsfdmx_read_vlen(bs);

	for (i=0; i<nb_4cc_props; i++) {
		GF_PropertyValue p;
		u32 p4cc = gf_bs_read_u32(bs);

		memset(&p, 0, sizeof(GF_PropertyValue));
		p.type = gf_props_4cc_get_type(p4cc);
		if (p.type==GF_PROP_FORBIDEN) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC property type for property 4CC %s\n", gf_4cc_to_str(p4cc) ));
			return GF_NON_COMPLIANT_BITSTREAM;
		}

		e = gsfdmx_read_prop(bs, &p);
		if (e) return e;

		if (is_info_update) gf_filter_pid_set_info(gst->opid, p4cc, &p);
		else gf_filter_pid_set_property(gst->opid, p4cc, &p);
	}
	for (i=0; i<nb_str_props; i++) {
		GF_PropertyValue p;

		u32 len = gsfdmx_read_vlen(bs);
		char *pname = gf_malloc(sizeof(char)*(len+1));
		gf_bs_read_data(bs, pname, len);
		pname[len]=0;

		memset(&p, 0, sizeof(GF_PropertyValue));
		p.type = gf_bs_read_u8(bs);

		e = gsfdmx_read_prop(bs, &p);
		if (e) {
			gf_free(pname);
			return e;
		}
		if (is_info_update) gf_filter_pid_set_info_dyn(gst->opid, pname, &p);
		else gf_filter_pid_set_property_dyn(gst->opid, pname, &p);
		gf_free(pname);
	}
	return GF_OK;
}

static GF_Err gsfdmx_tune(GF_Filter *filter, GSF_DemuxCtx *ctx, char *pck_data, u32 pck_size, Bool is_crypted)
{
	u32 len;
	GF_BitStream *bs;
	GF_Err e = gf_bs_reassign_buffer(ctx->bs_pck, pck_data, pck_size);
	if (e) return e;
	bs = ctx->bs_pck;

	u32 sig = gf_bs_read_u32(bs);
	if (sig != GF_4CC('G','S','5','F') ) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC serialized signature %s, expecting \"GS5F\"\n", gf_4cc_to_str(sig) ));
		ctx->tune_error = GF_TRUE;
		return GF_NOT_SUPPORTED;
	}
	sig = gf_bs_read_u8(bs);
	if (sig != 1) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC serialized version %d\n", sig ));
		ctx->tune_error = GF_TRUE;
		return GF_NOT_SUPPORTED;
	}
	if (is_crypted) {
#ifdef GPAC_DISABLE_CRYPTO
		return GF_NOT_SUPPORTED;
#else
		if (pck_size<25) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong serialized header size %d, should be at least 25 bytes for encrypted streams\n", pck_size ));
			ctx->tune_error = GF_TRUE;
			return GF_NOT_SUPPORTED;
		}
		gf_bs_read_data(bs, ctx->crypt_IV, 16);
		ctx->crypt_blocks = gf_bs_read_u16(bs);
		ctx->skip_blocks = gf_bs_read_u16(bs);

		if (!ctx->key.size) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] stream is encrypted but no key provided\n" ));
			ctx->tune_error = GF_TRUE;
			return GF_BAD_PARAM;
		}
		ctx->crypt = gf_crypt_open(GF_AES_128, GF_CBC);
		if (!ctx->crypt) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] failed to create decryptor\n" ));
			ctx->tune_error = GF_TRUE;
			return GF_IO_ERR;
		}
		gf_crypt_init(ctx->crypt, ctx->key.ptr, ctx->crypt_IV);

		gsfdmx_decrypt(ctx, pck_data+25, pck_size - 25);
#endif
	}
	ctx->use_seq_num = gf_bs_read_int(bs, 1);
	gf_bs_read_int(bs, 7);

	//header:magic
	len = gsfdmx_read_vlen(bs);
	if (len) {
		Bool wrongm=GF_FALSE;
		char *magic = gf_malloc(sizeof(char)*len);
		gf_bs_read_data(bs, magic, len);

		if (ctx->magic && !memcmp(ctx->magic, magic, len)) wrongm = GF_TRUE;
		if (!wrongm) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] wrong magic word in stream config\n" ));
			ctx->tune_error = GF_TRUE;
			gf_free(magic);
			return GF_NOT_SUPPORTED;
		}
		GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[GSFDemux] tuning in stream, magic %s\n", magic));
		gf_free(magic);
 	} else {
		GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[GSFDemux] tuning in stream\n"));
	}

	ctx->tuned = GF_TRUE;
	return GF_OK;
}

static GFINLINE GSF_Packet *gsfdmx_get_packet(GSF_DemuxCtx *ctx, GSF_Stream *gst, Bool pck_frag, s32 frame_sn, u8 pkt_type, u32 frame_size)
{
	u32 i=0, count;
	GSF_Packet *gpck = NULL;

	if ((frame_sn>=0) || pck_frag) {
		while (( gpck = gf_list_enum(gst->packets, &i))) {
			if (gpck->frame_sn == frame_sn) {
				assert(gpck->pck_type == pkt_type);
				assert(gpck->full_block_size == frame_size);

				break;
			}
			gpck = NULL;
		}
	}
	if (!gpck) {
		Bool inserted = GF_FALSE;
		gpck = gf_list_pop_back(ctx->pck_res);
		if (!gpck) {
 			GF_SAFEALLOC(gpck, GSF_Packet);
 			if (!gpck) return NULL;
 			gpck->nb_alloc_frags = 10;
 			gpck->frags = gf_malloc(sizeof(GSF_PacketFragment) * gpck->nb_alloc_frags);
		}
		gpck->frame_sn = frame_sn;
		gpck->pck_type = pkt_type;
		gpck->full_block_size = frame_size;
		gpck->pck = gf_filter_pck_new_alloc(gst->opid, frame_size, &gpck->output);
		memset(gpck->output, (u8) ctx->pad, sizeof(char) * gpck->full_block_size);

		count = gf_list_count(gst->packets);
		for (i=0; i<count; i++) {
			GSF_Packet *apck = gf_list_get(gst->packets, i);

			if ( ( (apck->frame_sn > frame_sn) && (apck->frame_sn - frame_sn <= 32768) )
				|| ( (apck->frame_sn < frame_sn ) && (frame_sn - apck->frame_sn > 32768) )
			) {
				inserted = GF_TRUE;
				gf_list_insert(gst->packets, gpck, i);
			}
		}
		if (!inserted) gf_list_add(gst->packets, gpck);
	}
	return gpck;
}

static void gsfdmx_packet_append_frag(GSF_Packet *pck, u32 size, u32 offset)
{
	u32 i;
	Bool inserted = GF_FALSE;
	pck->recv_bytes += size;
	pck->nb_recv_frags++;

	assert(offset + size <= pck->full_block_size);

	for (i=0; i<pck->nb_frags; i++) {
		if ((pck->frags[i].offset <= offset) && (pck->frags[i].offset + pck->frags[i].size >= offset + size) ) {
			return;
		}

		//insert fragment
		if (pck->frags[i].offset > offset) {
			if (pck->nb_frags==pck->nb_alloc_frags) {
				pck->nb_alloc_frags *= 2;
				pck->frags = gf_realloc(pck->frags, sizeof(GSF_PacketFragment)*pck->nb_alloc_frags);
			}
			memmove(&pck->frags[i+1], &pck->frags[i], sizeof(GSF_PacketFragment) * (pck->nb_frags - i)  );
			pck->frags[i].offset = offset;
			pck->frags[i].size = size;
			pck->nb_bytes += size;
			pck->nb_frags++;
			inserted = GF_TRUE;
			break;
		}
		//expand fragment
		if (pck->frags[i].offset + pck->frags[i].size == offset) {
			pck->frags[i].size += size;
			pck->nb_bytes += size;
			inserted = GF_TRUE;
			break;
		}
	}

	if (!inserted) {
		if (pck->nb_frags==pck->nb_alloc_frags) {
			pck->nb_alloc_frags *= 2;
			pck->frags = gf_realloc(pck->frags, sizeof(GSF_PacketFragment)*pck->nb_alloc_frags);
		}
		pck->frags[pck->nb_frags].offset = offset;
		pck->frags[pck->nb_frags].size = size;
		pck->nb_frags++;
		pck->nb_bytes += size;
	}
	if (pck->nb_bytes >= pck->full_block_size) {
		if (pck->nb_bytes>pck->full_block_size) pck->corrupted=GF_TRUE;
		pck->complete = GF_TRUE;
	}
}

GF_Err gsfdmx_read_data_pck(GSF_DemuxCtx *ctx, GSF_Stream *gst, GSF_Packet *gpck, u32 pck_len, Bool full_pck, GF_BitStream *bs)
{
	u64 dts=GF_FILTER_NO_TS, cts=GF_FILTER_NO_TS, bo=GF_FILTER_NO_BO;
	u32 copy_size, consummed, dur, dep_flags=0, tsmodebits, durmodebits, spos;
	s16 roll=0;
	u8 carv=0;

	//not yet setup
	if (!gst || !gpck) return GF_NOT_FOUND;


//	gsfdmx_flush_dst_pck(gst, GF_TRUE);

	spos = (u32) gf_bs_get_position(bs);

	//flags first byte: 1(has_dts) 1(has_cts) 1(has_dur) 1(cts_diff_neg) 2(ts_mode) 2(ts_diff_mode)
	//flags second byte: 3(sap) 2(encrypted) 1(has_sample_deps) 1(has builtin props) 1(has_ext)
	//if (ext) {
	// 	flags third byte: 1(has_byteoffset) 1(corrupted) 1(seek) 1(has_carousel) 2(ilaced) 2(cktype)
	// 	flags fourth byte: 1(au start) 1(au end) 1(has props) reserved(5)
	//}
	//if has_dts, dts on tsmodebits
	//if has_cts, has_dts ? cts_diff on ts_diff_mode : cts on tsmodebits
	//if durmode>0, dur on ts_diff_mode
	//if sap==4, roll on signed 16 bits
	//if (has sample deps) sample_deps_flags on 8 bits
	//if has_carousel, carrousel version on 8 bits
	//if has_byteoffset, byte offset on 64 bits
	//if (has builtin) vlen nb builtin_props then props[builtin_props]
	//if (has props) vlen nb_str_props then props[nb_str_props]

	//first flags byte
	u8 has_dts = gf_bs_read_int(bs, 1);
	u8 has_cts = gf_bs_read_int(bs, 1);
	u8 has_dur = gf_bs_read_int(bs, 1);
	u8 cts_diff_neg = gf_bs_read_int(bs, 1);
	u8 tsmode = gf_bs_read_int(bs, 2);
	u8 tsdiffmode = gf_bs_read_int(bs, 2);

	u8 sap = gf_bs_read_int(bs, 3);
	u8 crypt = gf_bs_read_int(bs, 2);
	u8 has_dep = gf_bs_read_int(bs, 1);
	u8 has_4cc_props = gf_bs_read_int(bs, 1);
	u8 has_ext = gf_bs_read_int(bs, 1);

	//default field values without ext
	u8 has_bo = 0, corr = 0, seek = 0, has_carv = 0, interl = 0, cktype = 0, has_str_props = 0;
	u8 is_start = 1, is_end = 1;
	if (has_ext) {
		has_bo = gf_bs_read_int(bs, 1);
		corr = gf_bs_read_int(bs, 1);
		seek = gf_bs_read_int(bs, 1);
		has_carv = gf_bs_read_int(bs, 1);
		interl = gf_bs_read_int(bs, 2);
		cktype = gf_bs_read_int(bs, 2);

		is_start = gf_bs_read_int(bs, 1);
		is_end = gf_bs_read_int(bs, 1);
		has_str_props = gf_bs_read_int(bs, 1);
		gf_bs_read_int(bs, 5); //reserved
	}

	if (tsmode==3) tsmodebits = 64;
	else if (tsmode==2) tsmodebits = 32;
	else if (tsmode==1) tsmodebits = 24;
	else tsmodebits = 16;

	if (tsdiffmode==3) durmodebits = 32;
	else if (tsdiffmode==2) durmodebits = 24;
	else if (tsdiffmode==1) durmodebits = 16;
	else durmodebits = 8;

	if (has_dts) {
		if (tsmode==3) dts = gf_bs_read_long_int(bs, tsmodebits);
		else dts = gf_bs_read_int(bs, tsmodebits);
	}
	if (has_cts) {
		if (has_dts) {
			s32 cts_diff = gf_bs_read_int(bs, durmodebits);
			if (cts_diff_neg) {
				cts = dts - cts_diff;
			} else {
				cts = dts + cts_diff;
			}
		} else {
			if (tsmode==3) cts = gf_bs_read_long_int(bs, tsmodebits);
			else cts = gf_bs_read_int(bs, tsmodebits);
		}
	}

	if (has_dur) {
		dur = gf_bs_read_int(bs, durmodebits);
	}

	if ((sap==GF_FILTER_SAP_4) || (sap==GF_FILTER_SAP_4_PROL)) {
		roll = gf_bs_read_u16(bs);
	}
	if (has_dep)
		dep_flags = gf_bs_read_u8(bs);

	if (has_carv) {
		carv = gf_bs_read_u8(bs);
	}
	if (has_bo) {
		bo = gf_bs_read_u64(bs);
	}

	if (has_4cc_props) {
		u32 nb_4cc = gsfdmx_read_vlen(bs);
		while (nb_4cc) {
			GF_Err e;
			GF_PropertyValue p;
			memset(&p, 0, sizeof(GF_PropertyValue));
			u32 p4cc = gf_bs_read_u32(bs);
			p.type = gf_props_4cc_get_type(p4cc);
			if (p.type==GF_PROP_FORBIDEN) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC property type for property 4CC %s\n", gf_4cc_to_str(p4cc) ));
				gf_filter_pck_discard(gpck->pck);
				gpck->pck = NULL;
				return GF_NON_COMPLIANT_BITSTREAM;
			}
			nb_4cc--;

			e = gsfdmx_read_prop(bs, &p);
			if (e) {
				gf_filter_pck_discard(gpck->pck);
				gpck->pck = NULL;
				return e;
			}
			gf_filter_pck_set_property(gpck->pck, p4cc, &p);
		}
	}
	if (has_str_props) {
		u32 nb_props = gsfdmx_read_vlen(bs);
		while (nb_props) {
			GF_Err e;
			GF_PropertyValue p;
			char *pname;
			memset(&p, 0, sizeof(GF_PropertyValue));
			u32 len = gsfdmx_read_vlen(bs);
			pname = gf_malloc(sizeof(char)*(len+1) );
			gf_bs_read_data(bs, pname, len);
			pname[len] = 0;
			p.type = gf_bs_read_u8(bs);
			if (p.type==GF_PROP_FORBIDEN) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC property type for property %s\n", pname ));
				gf_free(pname);
				gf_filter_pck_discard(gpck->pck);
				gpck->pck = NULL;
				return GF_NON_COMPLIANT_BITSTREAM;
			}
			nb_props--;

			e = gsfdmx_read_prop(bs, &p);
			if (e) {
				gf_filter_pck_discard(gpck->pck);
				gf_free(pname);
				gpck->pck = NULL;
				return e;
			}
			gf_filter_pck_set_property_dyn(gpck->pck, pname, &p);
			gf_free(pname);
			if ((p.type==GF_PROP_UINT_LIST) && p.value.uint_list.vals)
				gf_free(p.value.uint_list.vals);
		}
	}

	consummed = (u32) gf_bs_get_position(bs) - spos;
	pck_len -= consummed;
	if (full_pck) {
		assert(gpck->full_block_size > consummed);
		gpck->full_block_size -= consummed;
		assert(gpck->full_block_size == pck_len);
		gf_filter_pck_truncate(gpck->pck, gpck->full_block_size);
	}
	copy_size = gpck->full_block_size;
	if (copy_size > pck_len)
		copy_size = pck_len;
	gf_bs_read_data(bs, gpck->output, copy_size);
	gsfdmx_packet_append_frag(gpck, copy_size, 0);

	gf_filter_pck_set_framing(gpck->pck, is_start, is_end);
	if (has_dts) gf_filter_pck_set_dts(gpck->pck, dts);
	if (has_cts) gf_filter_pck_set_cts(gpck->pck, cts);
	if (has_dur) gf_filter_pck_set_duration(gpck->pck, dur);
	if (has_bo) gf_filter_pck_set_byte_offset(gpck->pck, bo);
	if (corr) gf_filter_pck_set_corrupted(gpck->pck, corr);
	if (interl) gf_filter_pck_set_interlaced(gpck->pck, interl);
	if (has_carv) gf_filter_pck_set_carousel_version(gpck->pck, carv);
	if (has_dep) gf_filter_pck_set_dependency_flags(gpck->pck, dep_flags);
	if (cktype) gf_filter_pck_set_clock_type(gpck->pck, cktype);
	if (seek) gf_filter_pck_set_seek_flag(gpck->pck, seek);
	if (crypt) gf_filter_pck_set_crypt_flags(gpck->pck, crypt);
	if (sap) gf_filter_pck_set_sap(gpck->pck, sap);
	if ((sap==GF_FILTER_SAP_4) || (sap==GF_FILTER_SAP_4_PROL))
		gf_filter_pck_set_roll_info(gpck->pck, roll);
	return GF_OK;
}

#ifndef GPAC_DISABLE_LOG
static const char *gsfdmx_pck_name(u32 pck_type)
{
	switch (pck_type) {
	case GFS_PCKTYPE_HDR: return "TUNE";
	case GFS_PCKTYPE_PID_CONFIG: return "CONFIG";
	case GFS_PCKTYPE_PID_INFO_UPDATE: return "INFO";
	case GFS_PCKTYPE_PID_REMOVE: return "PIDREM";
	case GFS_PCKTYPE_PID_EOS: return "PIDEOS";
	case GFS_PCKTYPE_PCK: return "PCK";
	default: return "FORBIDDEN";
	}
}
#endif

static GFINLINE void gsfdmx_pck_reset(GSF_Packet *pck)
{
	u32 alloc_frags = pck->nb_alloc_frags;
	GSF_PacketFragment *frags = pck->frags;
	memset(pck, 0, sizeof(GSF_Packet));
	pck->nb_alloc_frags = alloc_frags;
	pck->frags = frags;
}

static void gsfdmx_stream_del(GSF_DemuxCtx *ctx, GSF_Stream *gst, Bool is_flush)
{
	while (gf_list_count(gst->packets)) {
		GSF_Packet *gpck = gf_list_pop_front(gst->packets);

		if (gpck->pck) {
			if (is_flush && (gpck->pck_type == GFS_PCKTYPE_PCK)) {
				gf_filter_pck_set_corrupted(gpck->pck, GF_TRUE);
				gf_filter_pck_send(gpck->pck);
			} else {
				gf_filter_pck_discard(gpck->pck);
			}
		}
		gsfdmx_pck_reset(gpck);
		gf_list_add(ctx->pck_res, gpck);
	}
	if (is_flush)
		gf_filter_pid_remove(gst->opid);

	gf_list_del(gst->packets);
	gf_list_del_item(ctx->streams, gst);
	gf_free(gst);
}

static GF_Err gsfdmx_process_packets(GF_Filter *filter, GSF_DemuxCtx *ctx, GSF_Stream *gst)
{
	GSF_Packet *gpck;
	GF_Err e;

	if (ctx->tune_error) {
		return GF_SERVICE_ERROR;
	}
	while (1) {
		gpck = gf_list_get(gst->packets, 0);

		if (!gpck)
			return GF_OK;

		if (!gpck->complete) {
			u32 pck_count = gf_list_count(gst->packets);
			if (ctx->mq && (pck_count > ctx->mq + 1)) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[GSFDemux] packets queue too large (%d vs %d max), processing incomplete packet size %d receive %d\n", pck_count, ctx->mq, gpck->full_block_size, gpck->nb_bytes));
				gpck->corrupted = 1;
			} else {
				return GF_OK;
			}
		}
		assert(gpck->pck);
		if (ctx->use_seq_num) {
			u32 frame_sn;
			if (!gst->last_frame_sn) frame_sn = gpck->frame_sn;
			else {
				frame_sn = gst->last_frame_sn;
				if (frame_sn>0xFFFF) frame_sn=0;
			}
			if (gpck->frame_sn != frame_sn) {
				u32 pck_count = gf_list_count(gst->packets);
				if (ctx->mq && (pck_count > 2*ctx->mq + 1)) {
					GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[GSFDemux] packets queue too large (%d vs %d max), missed one full packet\n", pck_count, 2*ctx->mq));
				} else {
					return GF_OK;
				}
			}
			gst->last_frame_sn = gpck->frame_sn+1;
		}

		switch (gpck->pck_type) {
		case GFS_PCKTYPE_PID_CONFIG:
		case GFS_PCKTYPE_PID_INFO_UPDATE:
			if (!gpck->corrupted)
				e = gsfdmx_parse_pid_info(filter, ctx, gst, gpck, (gpck->pck_type==GFS_PCKTYPE_PID_INFO_UPDATE) ? GF_TRUE : GF_FALSE);
			else
				e = GF_CORRUPTED_DATA;
			if (gpck->pck) gf_filter_pck_discard(gpck->pck);
			break;
		case GFS_PCKTYPE_PID_REMOVE:
			if (gpck->pck) gf_filter_pck_discard(gpck->pck);
			gsfdmx_stream_del(ctx, gst, GF_TRUE);
			return GF_OK;

		case GFS_PCKTYPE_PID_EOS:
			if (gpck->pck) gf_filter_pck_discard(gpck->pck);
			gf_filter_pid_set_eos(gst->opid);
			e = GF_EOS;
			break;
		case GFS_PCKTYPE_PCK:
			if (gpck->corrupted) gf_filter_pck_set_corrupted(gpck->pck, GF_TRUE);
			e = gf_filter_pck_send(gpck->pck);
			break;
		default:
			e = GF_OK;
			break;
		}
		gf_list_rem(gst->packets, 0);
		gsfdmx_pck_reset(gpck);
		gf_list_add(ctx->pck_res, gpck);
		if (e>GF_OK) e = GF_OK;
		if (e) return e;
	}
	return GF_OK;
}

static GF_Err gsfdmx_demux(GF_Filter *filter, GSF_DemuxCtx *ctx, char *data, u32 data_size)
{
	u32 last_pck_end=0;

	//always reset input buffer if not tuned - since in reliable (pipe/file/...) this is the first packet and it is less than 40 bytes at max whe should be fine
	if (!ctx->tuned)
		ctx->buf_size = 0;

	if (ctx->alloc_size < ctx->buf_size + data_size) {
		ctx->buffer = (char*)gf_realloc(ctx->buffer, sizeof(char)*(ctx->buf_size + data_size) );
		ctx->alloc_size = ctx->buf_size + data_size;
	}

	memcpy(ctx->buffer + ctx->buf_size, data, sizeof(char)*data_size);
	ctx->buf_size += data_size;

	gf_bs_reassign_buffer(ctx->bs_r, ctx->buffer, ctx->buf_size);
	while (gf_bs_available(ctx->bs_r) > 4) { //1 byte header + 3 vlen field at least 1 bytes
		GF_Err e = GF_OK;
		u32 pck_len, block_size, block_offset;
#ifndef GPAC_DISABLE_LOG
		u32 hdr_pos = (u32) gf_bs_get_position(ctx->bs_r);
#endif
		/*Bool reserved =*/ gf_bs_read_int(ctx->bs_r, 1);
		u32 frag_flags = gf_bs_read_int(ctx->bs_r, 2);
		Bool is_crypted = gf_bs_read_int(ctx->bs_r, 1);
		u32 pck_type = gf_bs_read_int(ctx->bs_r, 4);
#ifndef GPAC_DISABLE_LOG
		u16 sn = 0;
#endif
		u32 st_idx;
		Bool needs_agg = GF_FALSE;
		u16 frame_sn = 0;
		Bool has_sn = (pck_type==GFS_PCKTYPE_HDR) ? GF_FALSE : ctx->use_seq_num;

		Bool full_pck = (frag_flags==0) ? GF_TRUE : GF_FALSE;
		Bool pck_frag = (frag_flags>=2) ? GF_TRUE : GF_FALSE;

		//reset buffer too small flag, and blindly parse the following vlen fields
		ctx->buffer_too_small = GF_FALSE;

		st_idx = gsfdmx_read_vlen(ctx->bs_r);
		if (has_sn) {
			frame_sn = gf_bs_read_u16(ctx->bs_r);
		}

		block_size = block_offset = 0;
		if (!full_pck) {
			block_size = gsfdmx_read_vlen(ctx->bs_r);
			if (pck_frag) block_offset = gsfdmx_read_vlen(ctx->bs_r);
		}

		pck_len = gsfdmx_read_vlen(ctx->bs_r);
		if (pck_len > gf_bs_available(ctx->bs_r)) {
			break;
		}
		//buffer was not big enough to contain all the vlen fields, we need more data
		if (ctx->buffer_too_small)
			break;
			
		if (full_pck) {
			block_size = pck_len;
			block_offset = 0;
		}

		if ( (pck_type==GFS_PCKTYPE_PCK) && !ctx->nb_playing && ctx->tuned && gf_list_count(ctx->streams) ) {
			ctx->wait_for_play = GF_TRUE;
			break;
		}

		GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[GSFDemux] found %s on stream %d type %s cryped %d sn %d size %d block_offset %d, hdr size %d at position %d\n",
					(pck_len==block_size) ? "full packet" : (pck_frag ? "packet fragment" : "packet start"),
					st_idx,
					gsfdmx_pck_name(pck_type),
					is_crypted, sn, pck_len, block_offset, gf_bs_get_position(ctx->bs_r)-hdr_pos, hdr_pos));

		e = GF_OK;

		if ((pck_type != GFS_PCKTYPE_PCK) && (pck_frag || (pck_len < block_size)))
			needs_agg = GF_TRUE;

		//tunein, we don't care about the seq num (for now, might chenge if we want key roll or other order-dependent features)
		if (!st_idx) {
			if (ctx->tuned) {
			} else if (needs_agg) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] tune-in packet shall not be fragmented\n"));
				e = GF_NON_COMPLIANT_BITSTREAM;
			} else {
				u32 pos = (u32) gf_bs_get_position(ctx->bs_r);
				e = gsfdmx_tune(filter, ctx, ctx->buffer + pos, pck_len, is_crypted);
			}
		}
		//stream signaling or packet
		else {
			u32 cur_pos = (u32) gf_bs_get_position(ctx->bs_r);

			GSF_Stream *gst = gsfdmx_get_stream(filter, ctx, st_idx, pck_type);
			if (!gst) {
				e = GF_OK;
				GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[GSFDemux] cannot find stream idx %d\n", st_idx));
			} else {
				GSF_Packet *gpck = gsfdmx_get_packet(ctx, gst, pck_frag, frame_sn, pck_type, block_size);

				//aggregate data
				if (!gpck) {
					e = GF_OUT_OF_MEM;
					GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] cannot allocate packet\n"));
				} else if (!gpck->pck) {
					e = GF_CORRUPTED_DATA;
					GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] lost first packet in frame, cannot reaggregate fragment\n"));
				} else {
					Bool append = GF_TRUE;

					//packet: decrypt on per-packet base, and decode if not first fragment
					if (pck_type==GFS_PCKTYPE_PCK) {
						if (is_crypted) {
							gsfdmx_decrypt(ctx, ctx->buffer + cur_pos, pck_len);
						}
						if (!pck_frag) {
							gf_bs_reassign_buffer(ctx->bs_pck, ctx->buffer + cur_pos, pck_len);
							e = gsfdmx_read_data_pck(ctx, gst, gpck, pck_len, full_pck, ctx->bs_pck);
	 						append = GF_FALSE;
						}
					} else {
						//otherwise decryption will happen upon decoding the complete packet
						if (is_crypted) gpck->crypted = 1;
					}

					if (append) {
						if (block_offset + pck_len > gpck->full_block_size) {
							GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] packet fragment out of bounds of current frame (offset %d size %d max size %d)\n", block_offset,  pck_len, gpck->full_block_size));
							e = GF_NON_COMPLIANT_BITSTREAM;
						} else {
							//append fragment
							memcpy(gpck->output + block_offset, ctx->buffer + cur_pos, pck_len);

							gsfdmx_packet_append_frag(gpck, pck_len, block_offset);
						}
					}
					if (!e)
						e = gsfdmx_process_packets(filter, ctx, gst);
				}
			}
		}

		if (e) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] error decoding packet: %s\n", gf_error_to_string(e) ));
			if (ctx->tune_error) return e;
		}
		gf_bs_skip_bytes(ctx->bs_r, pck_len);
		last_pck_end = (u32) gf_bs_get_position(ctx->bs_r);
	}

	if (last_pck_end) {
		assert(ctx->buf_size>=last_pck_end);
		memmove(ctx->buffer, ctx->buffer+last_pck_end, sizeof(char) * (ctx->buf_size-last_pck_end));
		ctx->buf_size -= last_pck_end;
	}
	return GF_OK;
}

GF_Err gsfdmx_process(GF_Filter *filter)
{
	GF_Err e;
	GSF_DemuxCtx *ctx = gf_filter_get_udta(filter);
	GF_FilterPacket *pck;
	GSF_Stream *st;
	u32 i=0, pkt_size;
	const char *data;
	u32 would_block = 0;
	Bool is_eos = GF_FALSE;

	if (ctx->wait_for_play) return GF_OK;
	if (ctx->tune_error) return GF_SERVICE_ERROR;

	pck = gf_filter_pid_get_packet(ctx->ipid);
	if (!pck) {
		if (gf_filter_pid_is_eos(ctx->ipid)) is_eos = GF_TRUE;
		else return GF_OK;
	}

	//check if all the streams are in block state, if so return.
	//we need to check for all output since one pid could still be buffering
	while ((st = gf_list_enum(ctx->streams, &i))) {
		if (st->opid) {
			if (is_eos) {
				gf_filter_pid_set_eos(st->opid);
			} else if (gf_filter_pid_would_block(st->opid)) {
				would_block++;
			}
		}
	}
	if (is_eos) return GF_EOS;

	if (would_block && (would_block+1==i))
		return GF_OK;

	data = gf_filter_pck_get_data(pck, &pkt_size);
	e = gsfdmx_demux(filter, ctx, (char *) data, pkt_size);
	gf_filter_pid_drop_packet(ctx->ipid);
	if (ctx->tune_error)
		gf_filter_pid_set_discard(ctx->ipid, GF_TRUE);

	return e;
}

static const char *gsfdmx_probe_data(const u8 *data, u32 data_size, GF_FilterProbeScore *score)
{
	u32 avail = data_size;
	if (data_size < 10) return NULL;
	char *buf = (char *) data;
	while (buf) {
		char *start_sig = memchr(buf, 'G', avail);
		if (!start_sig) return NULL;
		//signature found and version is 1
		if (!strncmp(start_sig, "GS5F", 4) && (start_sig[4] == 1)) {
			*score = GF_FPROBE_SUPPORTED;
			return "application/x-gpac-sf";
		}
		buf = start_sig+1;
		avail = data_size - (u32) ( buf - (char *) data);
	}
	return NULL;
}

static void gsfdmx_not_enough_bytes(void *par)
{
	GSF_DemuxCtx *ctx = (GSF_DemuxCtx *)par;
	if (ctx)
		ctx->buffer_too_small = GF_TRUE;
}

static GF_Err gsfdmx_initialize(GF_Filter *filter)
{
	GSF_DemuxCtx *ctx = gf_filter_get_udta(filter);
	ctx->streams = gf_list_new();
	if (!ctx->streams) return GF_OUT_OF_MEM;
	ctx->bs_r = gf_bs_new((char *) ctx, 1, GF_BITSTREAM_READ);
	gf_bs_set_eos_callback(ctx->bs_r, gsfdmx_not_enough_bytes, ctx);

#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode())
		gsfdmx_not_enough_bytes(NULL);
#endif

	ctx->bs_pck = gf_bs_new((char *) ctx, 1, GF_BITSTREAM_READ);
	ctx->pck_res = gf_list_new();
	return GF_OK;
}

static void gsfdmx_finalize(GF_Filter *filter)
{
	GSF_DemuxCtx *ctx = gf_filter_get_udta(filter);
	while (gf_list_count(ctx->streams)) {
		GSF_Stream *gst = gf_list_pop_back(ctx->streams);
		gsfdmx_stream_del(ctx, gst, GF_FALSE);
	}
	gf_list_del(ctx->streams);

	while (gf_list_count(ctx->pck_res)) {
		GSF_Packet *gsp = gf_list_pop_back(ctx->pck_res);
		if (gsp->frags) gf_free(gsp->frags);
		gf_free(gsp);
	}
	gf_list_del(ctx->pck_res);

#ifndef GPAC_DISABLE_CRYPTO
	if (ctx->crypt) gf_crypt_close(ctx->crypt);
#endif
	if (ctx->buffer) gf_free(ctx->buffer);
	if (ctx->bs_r) gf_bs_del(ctx->bs_r);
	if (ctx->bs_pck) gf_bs_del(ctx->bs_pck);
}

static const GF_FilterCapability GSFDemuxCaps[] =
{
	CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
	CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "gsf"),
	CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/x-gpac-sf"),
	//we deliver more than these two but this make the filter chain loading stop until we declare a pid
	CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO),
	CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
	CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE),
};



#define OFFS(_n)	#_n, offsetof(GSF_DemuxCtx, _n)
static const GF_FilterArgs GSFDemuxArgs[] =
{
#ifndef GPAC_DISABLE_CRYPTO
	{ OFFS(key), "key for decrypting packets", GF_PROP_DATA, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
#endif
	{ OFFS(magic), "magic string to check in setup packet", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
	{ OFFS(mq), "set max packet queue length for loss detection. 0 will flush incomplete packet when a new one starts", GF_PROP_UINT, "4", NULL, GF_FS_ARG_HINT_ADVANCED},
	{ OFFS(pad), "byte value used to pad lost packets", GF_PROP_UINT, "0", "0-255", GF_FS_ARG_HINT_ADVANCED},
	{0}
};


GF_FilterRegister GSFDemuxRegister = {
	.name = "gsfdmx",
	GF_FS_SET_DESCRIPTION("GSF Demuxer")
#ifndef GPAC_DISABLE_DOC
	.help = "This filter provides GSF (__GPAC Super/Simple/Serialized/Stream/State Format__) demultiplexing.\n"
			"It deserializes the stream states (config/reconfig/info update/remove/eos) and packets of input PIDs.\n"
			"This allows either reading a session saved to file, or receiving the state/data of streams from another instance of GPAC using either pipes or sockets\n"
			"\n"
#ifndef GPAC_DISABLE_CRYPTO
			"The stream format can be encrypted in AES 128 CBC mode, in which case the demux filters must be given a 128 bit key."
#endif
		,
#endif
	
	.private_size = sizeof(GSF_DemuxCtx),
	.max_extra_pids = (u32) -1,
	.args = GSFDemuxArgs,
	.flags = GF_FS_REG_DYNAMIC_PIDS,
	SETCAPS(GSFDemuxCaps),
	.initialize = gsfdmx_initialize,
	.finalize = gsfdmx_finalize,
	.configure_pid = gsfdmx_configure_pid,
	.process = gsfdmx_process,
	.process_event = gsfdmx_process_event,
	.probe_data = gsfdmx_probe_data,
};


const GF_FilterRegister *gsfdmx_register(GF_FilterSession *session)
{
	return &GSFDemuxRegister;
}
