#include "t38.h"

#ifdef WITH_TRANSCODING
#include <assert.h>
#include <spandsp/t30.h>
#include <spandsp/logging.h>
#include "spandsp_logging.h"
#include "codec.h"
#include "call.h"
#include "log.h"
#include "str.h"
#include "media_player.h"
#include "log_funcs.h"
#include "sdp.h"



struct udptl_packet {
	seq_packet_t p;
	str *s;
};



static void __add_udptl_len(GString *s, const void *buf, unsigned int len) {
	if (len < 0x80) {
		g_string_append_c(s, len);
		if (len)
			g_string_append_len(s, buf, len);
		return;
	}

	if (len < 0x4000) {
		uint16_t enc_len = htons(0x8000 | len);
		g_string_append_len(s, (void *) &enc_len, 2);
		g_string_append_len(s, buf, len);
		return;
	}

	// fragmented - we don't support more than 65535 bytes
	unsigned int mult = len >> 14;
	g_string_append_c(s, 0xc0 | mult);
	mult <<= 14;
	// one portion
	g_string_append_len(s, buf, mult);
	// remainder - may be zero length
	__add_udptl_len(s, buf + mult, len - mult);
}

static void __add_udptl_raw(GString *s, const char *buf, size_t len) {
	assert(len < 0x10000);

	if (len == 0) {
		// add a single zero byte, length 1
		__add_udptl_len(s, "\x00", 1);
		return;
	}

	__add_udptl_len(s, buf, len);
}

static void __add_udptl(GString *s, const str *buf) {
	__add_udptl_raw(s, buf->s, buf->len);
}


static void g_string_null_extend(GString *s, size_t len) {
	if (s->len >= len)
		return;

	size_t oldb = s->len;
	size_t newb = len - s->len;
	g_string_set_size(s, len);
	memset(s->str + oldb, 0, newb);
}

static void spandsp_logging_func(SPAN_LOG_ARGS) {
	if (level <= SPAN_LOG_PROTOCOL_ERROR)
		level = LOG_ERR;
	else if (level <= SPAN_LOG_PROTOCOL_WARNING)
		level = LOG_WARN;
	else
		level = LOG_DEBUG;
	ilogs(spandsp, level, "SpanDSP: %s", text);
}


// call is locked in R or W
static int t38_gateway_handler(t38_core_state_t *stat, void *user_data, const uint8_t *b, int len, int count) {
	struct t38_gateway *tg = user_data;

	// cap the max length of packet we can handle
	if (len < 0 || len >= 0x10000) {
		ilog(LOG_ERR, "Received %i bytes from T.38 encoder - discarding", len);
		return -1;
	}

	ilog(LOG_DEBUG, "Received %i bytes from T.38 encoder", len);

	// build udptl packet: use a conservative guess for required buffer
	GString *s = g_string_sized_new(512);

	// add seqnum
	uint16_t seq = htons(tg->seqnum);
	g_string_append_len(s, (void *) &seq, 2);

	// add primary IFP packet
	str buf = STR_LEN(b, len);
	__add_udptl(s, &buf);

	// add error correction packets
	if (tg->options.fec_span > 1) {
		// forward error correction
		g_string_append_c(s, 0x80);

		// figure out how many packets we have and which span to use
		unsigned int packets = tg->options.fec_span * tg->options.max_ec_entries;
		if (packets > tg->udptl_ec_out.length)
			packets = tg->udptl_ec_out.length;
		unsigned int span = packets / tg->options.max_ec_entries;
		if (!span)
			span = 1;
		packets = span * tg->options.max_ec_entries; // our own packets we use
		unsigned int entries = packets / span; // FEC entries in the output
		if (entries > tg->udptl_ec_out.length)
			entries = tg->udptl_ec_out.length;
		packets = entries * span;

		assert(span < 0x80);
		assert(entries < 0x80);

		g_string_append_c(s, 0x01);
		g_string_append_c(s, span);

		// create needed number of FEC packet entries
		GQueue fec = G_QUEUE_INIT;
		for (int i = 0; i < entries; i++)
			g_queue_push_tail(&fec, g_string_new(""));

		// take each input packet, going backwards in time, and XOR it into
		// the respective output FEC packet
		GList *inp = tg->udptl_ec_out.head;
		for (int i = 0; i < packets; i++) {
			assert(inp != NULL);
			str *ip = inp->data;
			// just keep shifting the list around
			GString *outp = g_queue_pop_head(&fec);

			// extend string as needed
			g_string_null_extend(outp, ip->len);

			for (size_t j = 0; j < ip->len; j++)
				outp->str[j] ^= ip->s[j];

			g_queue_push_tail(&fec, outp);
			inp = inp->next;
		}

		// output list is now complete, but in reverse. append it to output buffer
		GString *ec = g_string_sized_new(512);
		entries = 0;
		int going = 1;
		while (fec.length) {
			GString *outp = g_queue_pop_tail(&fec);
			if (going) {
				if (s->len + ec->len + outp->len > tg->options.max_datagram)
					going = 0;
				else {
					__add_udptl_raw(ec, outp->str, outp->len);
					entries++;
				}
			}
			g_string_free(outp, TRUE);
		}

		g_string_append_c(s, entries);
		g_string_append_len(s, ec->str, ec->len);
		g_string_free(ec, TRUE);
	}
	else {
		// redundancy error correction
		g_string_append_c(s, 0x00);

		GString *ec = g_string_sized_new(512);
		int entries = 0;

		for (GList *l = tg->udptl_ec_out.head; l; l = l->next) {
			str *ec_s = l->data;
			// stop when we exceed max datagram length
			if (s->len + ec->len + ec_s->len > tg->options.max_datagram)
				break;
			// add redundancy packet
			__add_udptl(ec, ec_s);
			entries++;
		}

		// number of entries - must be <0x80 as verified in settings
		g_string_append_c(s, entries);
		g_string_append_len(s, ec->str, ec->len);
		g_string_free(ec, TRUE);
	}

	// done building our packet - add primary to our error correction buffer
	tg->seqnum++;
	unsigned int q_entries = tg->options.max_ec_entries * tg->options.fec_span;
	if (q_entries) {
		while (tg->udptl_ec_out.length >= q_entries) {
			str *ec_s = g_queue_pop_tail(&tg->udptl_ec_out);
			free(ec_s);
		}
		g_queue_push_head(&tg->udptl_ec_out, str_dup(&buf));
	}

	// send our packet if we can
	struct packet_stream *ps = NULL;
	if (tg->t38_media && tg->t38_media->streams.head)
		ps = tg->t38_media->streams.head->data;

	stream_fd *sfd = NULL;
	if (ps) {
		mutex_lock(&ps->lock);
		sfd = ps->selected_sfd;
	}
	if (sfd && sfd->socket.fd != -1 && ps->endpoint.address.family != NULL) {
		for (int i = 0; i < count; i++) {
			ilog(LOG_DEBUG, "Sending %u UDPTL bytes", (unsigned int) s->len);
			socket_sendto(&sfd->socket, s->str, s->len, &ps->endpoint);
		}
	}
	else
		ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unable to send T.38 UDPTL packet due to lack of "
				"socket or stream");
	if (ps)
		mutex_unlock(&ps->lock);

	g_string_free(s, TRUE);

	return 0;
}

void __t38_gateway_free(struct t38_gateway *tg) {
	ilog(LOG_DEBUG, "Destroying T.38 gateway");
	if (tg->gw)
		t38_gateway_free(tg->gw);
	if (tg->pcm_player) {
		media_player_stop(tg->pcm_player);
		media_player_put(&tg->pcm_player);
	}
	if (tg->udptl_fec)
		g_hash_table_destroy(tg->udptl_fec);
	g_queue_clear_full(&tg->udptl_ec_out, free);
	packet_sequencer_destroy(&tg->sequencer);
}

// call is locked in R and mp is locked
static bool t38_pcm_player(struct media_player *mp) {
	if (!mp || !mp->media)
		return true;

	struct t38_gateway *tg = mp->media->t38_gateway;
	if (!tg)
		return true;

	if (tg->pcm_media && tg->pcm_media->streams.head
			&& ((struct packet_stream *) tg->pcm_media->streams.head->data)->selected_sfd)
		log_info_stream_fd(((struct packet_stream *) tg->pcm_media->streams.head->data)->selected_sfd);

	ilog(LOG_DEBUG, "Generating T.38 PCM samples");

	mutex_lock(&tg->lock);

	int16_t smp[80];
	int num = t38_gateway_tx(tg->gw, smp, 80);
	if (num <= 0) {
		ilog(LOG_DEBUG, "No T.38 PCM samples generated");
		// use a fixed interval of 10 ms
		mp->next_run += 10000;
		timerthread_obj_schedule_abs(&mp->tt_obj, mp->next_run);
		mutex_unlock(&tg->lock);
		return false;
	}

	ilog(LOG_DEBUG, "Generated %i T.38 PCM samples", num);

	// release gateway lock as the media player may trigger a lock on the SSRC objects
	// and this is the wrong lock order
	struct media_player *pcm_player = media_player_get(tg->pcm_player);
	unsigned long long pts = tg->pts;
	tg->pts += num;

	// handle fill-in
	if (rtpe_now - tg->last_rx_ts > 30000) {
		ilog(LOG_DEBUG, "Adding T.38 fill-in samples");
		t38_gateway_rx_fillin(tg->gw, 80);
	}

	mutex_unlock(&tg->lock);

	// this reschedules our player as well
	media_player_add_packet(pcm_player, (char *) smp, num * 2, num * 1000000 / 8000, pts);
	media_player_put(&pcm_player);

	return false;
}


static void __udptl_packet_free(struct udptl_packet *p) {
	if (p->s)
		free(p->s);
	g_free(p);
}


static void __t38_options_normalise(struct t38_options *opts) {
	if (opts->version < 0)
		opts->version = 0;
	if (opts->fec_span < 1)
		opts->fec_span = 1;
	if (opts->min_ec_entries < 0)
		opts->min_ec_entries = 0;
	if (opts->min_ec_entries >= 0x80)
		opts->min_ec_entries = 0x7f;
	if (opts->max_ec_entries < 0)
		opts->max_ec_entries = 0;
	if (opts->max_ec_entries >= 0x80)
		opts->max_ec_entries = 0x7f;
	if (opts->max_ifp <= 0 || opts->max_ifp >= 0x4000)
		opts->max_ifp = 0x3fff;
	if (opts->max_datagram <= 0 || opts->max_datagram >= 0x4000)
		opts->max_datagram = 0x3fff;
}

static int span_log_level_map(int level) {
	if (level <= LOG_ERR)
		level = SPAN_LOG_PROTOCOL_ERROR;
	else if (level < LOG_DEBUG)
		level = SPAN_LOG_PROTOCOL_WARNING;
	else
		level = SPAN_LOG_DEBUG_3;
	return level;
}

static void t38_insert_media_attributes(GString *gs, struct call_media *media, struct call_media *source_media,
		const sdp_ng_flags *flags)
{
	struct t38_gateway *tg = media->t38_gateway;
	if (!tg)
		return;

	sdp_append_attr(gs, flags, MT_IMAGE, "T38FaxVersion", "%i", tg->options.version);
	sdp_append_attr(gs, flags, MT_IMAGE, "T38MaxBitRate", "14400");
	sdp_append_attr(gs, flags, MT_IMAGE, "T38FaxRateManagement", "%s",
				tg->options.local_tcf ? "localTFC" : "transferredTCF");
	sdp_append_attr(gs, flags, MT_IMAGE, "T38FaxMaxBuffer", "1800");
	sdp_append_attr(gs, flags, MT_IMAGE, "T38FaxMaxDatagram", "512");

	if (tg->options.max_ec_entries == 0)
		sdp_append_attr(gs, flags, MT_IMAGE, "T38FaxUdpEC", "t38UDPNoEC");
	else if (tg->options.fec_span > 1)
		sdp_append_attr(gs, flags, MT_IMAGE, "T38FaxUdpEC", "t38UDPFEC");
	else
		sdp_append_attr(gs, flags, MT_IMAGE, "T38FaxUdpEC", "t38UDPRedundancy");
	// XXX more options possible here
}


// call is locked in W
int t38_gateway_pair(struct call_media *t38_media, struct call_media *pcm_media,
		const struct t38_options *options)
{
	const char *err = NULL;

	if (!t38_media || !pcm_media || !options)
		return -1;

	struct t38_options opts = *options;
	__t38_options_normalise(&opts);

	// do we have one yet?
	if (t38_media->t38_gateway
			&& t38_media->t38_gateway == pcm_media->t38_gateway)
	{
		// XXX check options here?
		return 0;
	}

	// release old structs, if any
	t38_gateway_put(&t38_media->t38_gateway);
	t38_gateway_put(&pcm_media->t38_gateway);

	ilog(LOG_DEBUG, "Creating new T.38 gateway");

	// create and init new
	__auto_type tg = obj_alloc0(struct t38_gateway, __t38_gateway_free);

	tg->t38_media = t38_media;
	tg->pcm_media = pcm_media;
	mutex_init(&tg->lock);
	tg->udptl_fec = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
			(GDestroyNotify) __udptl_packet_free);
	tg->options = opts;

	tg->pcm_pt.payload_type = -1;
	tg->pcm_pt.encoding = STR("X-L16");
	tg->pcm_pt.encoding_with_params = tg->pcm_pt.encoding;
	tg->pcm_pt.clock_rate = 8000;
	tg->pcm_pt.channels = 1;
	tg->pcm_pt.ptime = 20;

	err = "Failed to init PCM codec";
	ensure_codec_def(&tg->pcm_pt, pcm_media);
	if (!codec_def_supported(tg->pcm_pt.codec_def))
		goto err;

	err = "Failed to create spandsp T.38 gateway";
	if (!(tg->gw = t38_gateway_init(NULL, t38_gateway_handler, tg)))
		goto err;

	media_player_new(&tg->pcm_player, pcm_media->monologue,
			call_get_first_ssrc(&pcm_media->ssrc_hash_out),
			NULL);
	// even though we call media_player_set_media() here, we need to call it again in
	// t38_gateway_start because our sink might not have any streams added here yet,
	// leaving the media_player setup incomplete
	media_player_set_media(tg->pcm_player, pcm_media);
	tg->pcm_player->run_func = t38_pcm_player;

	// set options
	t38_core_state_t *t38 = t38_gateway_get_t38_core_state(tg->gw);
	t38_gateway_set_ecm_capability(tg->gw, opts.no_ecm ? FALSE : TRUE);
	t38_gateway_set_transmit_on_idle(tg->gw, TRUE);
	t38_gateway_set_supported_modems(tg->gw,
			(opts.no_v17 ? 0 : T30_SUPPORT_V17)
			| (opts.no_v27ter ? 0 : T30_SUPPORT_V27TER)
			| (opts.no_v29 ? 0 : T30_SUPPORT_V29)
			| (opts.no_v34 ? 0 : T30_SUPPORT_V34HDX)
			| (opts.no_iaf ? 0 : T30_SUPPORT_IAF));
	t38_set_t38_version(t38, opts.version);
	t38_set_data_rate_management_method(t38,
			opts.local_tcf ? 1 : 2);
	t38_set_fill_bit_removal(t38, opts.fill_bit_removal);
	t38_set_mmr_transcoding(t38, opts.transcoding_mmr);
	t38_set_jbig_transcoding(t38, opts.transcoding_jbig);
	t38_set_max_datagram_size(t38, opts.max_ifp);

	logging_state_t *ls = t38_gateway_get_logging_state(tg->gw);
	my_span_set_log(ls, spandsp_logging_func);
	span_log_set_level(ls, span_log_level_map(get_log_level(spandsp)));

	ls = t38_core_get_logging_state(t38);
	my_span_set_log(ls, spandsp_logging_func);
	span_log_set_level(ls, span_log_level_map(get_log_level(spandsp)));

	packet_sequencer_init(&tg->sequencer, (GDestroyNotify) __udptl_packet_free);
	tg->sequencer.seq = 0;

	// done - add references to media structs
	t38_media->t38_gateway = tg;
	pcm_media->t38_gateway = obj_get(tg);

	// add SDP options for T38
	t38_media->sdp_attr_print = t38_insert_media_attributes;

	return 0;

err:
	if (err)
		ilog(LOG_ERR, "Failed to create T.38 gateway: %s", err);
	t38_gateway_put(&tg);
	return -1;
}


// call is locked in W
void t38_gateway_start(struct t38_gateway *tg, str_case_value_ht codec_set) {
	if (!tg)
		return;

	// set up our player first
	media_player_set_media(tg->pcm_player, tg->pcm_media);
	if (media_player_setup(tg->pcm_player, &tg->pcm_pt, NULL, codec_set))
		return;

	// now start our player if we can or should
	// already running?
	if (tg->pcm_player->next_run)
		return;

	// only start our player only if we can send both ways
	if (!tg->pcm_media->codecs.codec_prefs.length)
		return;
	if (!tg->pcm_media->streams.length)
		return;
	if (!tg->t38_media->streams.length)
		return;

	struct packet_stream *ps;
	ps = tg->pcm_media->streams.head->data;
	if (!PS_ISSET(ps, FILLED))
		return;
	ps = tg->t38_media->streams.head->data;
	if (!PS_ISSET(ps, FILLED))
		return;

	ilog(LOG_DEBUG, "Starting T.38 PCM player");

	// start off PCM player
	tg->pcm_player->next_run = rtpe_now;
	timerthread_obj_schedule_abs(&tg->pcm_player->tt_obj, tg->pcm_player->next_run);
}


// call is locked in R
int t38_gateway_input_samples(struct t38_gateway *tg, int16_t amp[], int len) {
	if (!tg)
		return 0;
	if (len <= 0)
		return 0;

	ilog(LOG_DEBUG, "Adding %i samples to T.38 encoder", len);

	mutex_lock(&tg->lock);

	int left = t38_gateway_rx(tg->gw, amp, len);
	if (left)
		ilog(LOG_WARN | LOG_FLAG_LIMIT, "%i PCM samples were not processed by the T.38 gateway",
				left);

	tg->last_rx_ts = rtpe_now;

	mutex_unlock(&tg->lock);

	return 0;
}


static ssize_t __get_udptl_len(str *s) {
	ssize_t ret;

	if (s->len < 1)
		return -1;

	if (!(s->s[0] & 0x80)) {
		ret = s->s[0];
		str_shift(s, 1);
		return ret;
	}

	if (s->len < 2)
		return -1;

	if (!(s->s[0] & 0x40)) {
		ret = ntohs(*((uint16_t *) s->s)) & 0x3fff;
		str_shift(s, 2);
		return ret;
	}

	ilog(LOG_INFO | LOG_FLAG_LIMIT, "Decoding UDPTL fragments is not supported");
	return -1;
}

static int __get_udptl(str *piece, str *s) {
	ssize_t len = __get_udptl_len(s);
	if (len < 0)
		return -1;

	return str_shift_ret(s, len, piece);
}


static struct udptl_packet *__make_udptl_packet(const str *piece, uint16_t seq) {
	struct udptl_packet *up = g_new0(__typeof(*up), 1);
	up->p.seq = seq;
	up->s = str_dup(piece);
	return up;
}

static void __fec_save(struct t38_gateway *tg, const str *piece, uint16_t seq) {
	struct udptl_packet *up = __make_udptl_packet(piece, seq);
	g_hash_table_insert(tg->udptl_fec, GUINT_TO_POINTER(seq), up);
}

int t38_gateway_input_udptl(struct t38_gateway *tg, const str *buf) {
	const char *err = NULL;
	struct udptl_packet *up = NULL;

	if (!tg)
		return 0;
	if (!buf || !buf->len)
		return 0;

	if (buf->len < 4) {
		ilog(LOG_INFO | LOG_FLAG_LIMIT, "Ignoring short UDPTL packet (%zu bytes)", buf->len);
		return 0;
	}

	ilog(LOG_DEBUG, "Processing %zu UDPTL bytes", buf->len);

	str s = *buf;
	str piece;

	// get seq num
	uint16_t seq;
	if (str_shift_ret(&s, 2, &piece))
		goto err_nolock;
	seq = ntohs(*((uint16_t *) piece.s));

	err = "Invalid primary UDPTL packet";
	if (__get_udptl(&piece, &s))
		goto err;

	ilog(LOG_DEBUG, "Received primary IFP packet, len %zu, seq %i", piece.len, seq);
	str primary = piece;
	up = __make_udptl_packet(&primary, seq);

	err = "Error correction mode byte missing";
	if (str_shift_ret(&s, 1, &piece))
		goto err_nolock;
	char fec = piece.s[0];

	mutex_lock(&tg->lock);

	long diff = seq - up->p.seq;
	if (diff > 100 || diff < -100) {
		ilog(LOG_INFO | LOG_FLAG_LIMIT, "Ignoring UDPTL packet with wildly off seq (%u <> %u)",
				(unsigned int) seq, (unsigned int) up->p.seq);
		err = NULL;
		goto err;
	}

	// XXX possible short path here without going through the sequencer
	int ret = packet_sequencer_insert(&tg->sequencer, &up->p);
	if (ret < 0) {
		// main seq is dupe - everything else must be dupe too
		__udptl_packet_free(up);
		goto out;
	}

	up = NULL;

	if (!(fec & 0x80)) {
		// packet redundancy
		if (packet_sequencer_next_ok(&tg->sequencer))
			goto seq_ok;

		// process EC packets as well as something's wrong
		ssize_t num_packets = __get_udptl_len(&s);
		err = "Invalid number of EC packets";
		if (num_packets < 0 || num_packets > 100)
			goto err;
		for (int i = 0; i < num_packets; i++) {
			if (__get_udptl(&piece, &s)) {
				ilog(LOG_WARN | LOG_FLAG_LIMIT,
						"Invalid UDPTL error correction packet at index %i",
						i);
				break;
			}
			// ignore zero-length packets
			if (!piece.len)
				continue;
			ilog(LOG_DEBUG, "Received secondary IFP packet, len %zu, seq %i", piece.len,
					seq - 1 - i);
			up = __make_udptl_packet(&piece, seq - 1 - i);
			packet_sequencer_insert(&tg->sequencer, &up->p);
			up = NULL;

			// can we stop here?
			if (packet_sequencer_next_ok(&tg->sequencer))
				break;
		}
	}
	else {
		// FEC
		// start by saving the new packet
		__fec_save(tg, &primary, seq);

		if (packet_sequencer_next_ok(&tg->sequencer))
			goto seq_ok;

		// process all FEC packets
		err = "Invalid number of FEC packets";
		if (str_shift_ret(&s, 2, &piece))
			goto err;
		if (piece.s[0] != 0x01)
			goto err;
		unsigned int span = piece.s[1];
		if (span <= 0 || span >= 0x80)
			goto err;
		ssize_t entries = __get_udptl_len(&s);
		if (entries < 0 || entries > 100)
			goto err;

		// first seq we can possibly recover
		uint16_t seq_start = seq - span * entries;

		while (entries) {
			// get our entry
			if (__get_udptl(&piece, &s)) {
				ilog(LOG_WARN | LOG_FLAG_LIMIT,
						"Invalid UDPTL error correction packet at index %i",
						seq_start);
				break;
			}
			// check each of the entries covered by `span`
			for (int i = 0; i < span; i++) {
				uint16_t seq_fec = seq_start + i * span;
				// skip if we already know this packet
				if (g_hash_table_lookup(tg->udptl_fec, GUINT_TO_POINTER(seq_fec)))
					continue;

				// can we recover it? we need all other packets from the series
				GString *rec_s = g_string_new("");
				int complete = 1;

				for (int j = 0; j < span; j++) {
					uint16_t seq_rec = seq_start + i * span;
					if (seq_rec == seq_fec)
						continue;
					struct udptl_packet *recp =
						g_hash_table_lookup(tg->udptl_fec, GUINT_TO_POINTER(seq_rec));
					if (!recp) {
						ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unable to recover UDPTL FEC "
								"packet with seq %i due to missing seq %i",
								seq_fec, seq_rec);
						complete = 0;
						break;
					}

					// XOR in packet
					for (size_t k = 0; k < recp->s->len; k++)
						rec_s->str[k] ^= recp->s->s[k];
				}

				if (complete) {
					ilog(LOG_WARN | LOG_FLAG_LIMIT, "Recovered UDPTL "
							"packet with seq %i from FEC",
							seq_fec);

					str rec_str = STR_GS(rec_s);
					__fec_save(tg, &rec_str, seq_fec);
					up = __make_udptl_packet(&rec_str, seq_fec);
					packet_sequencer_insert(&tg->sequencer, &up->p);
					up = NULL;
				}

				g_string_free(rec_s, TRUE);

				// no point in continuing further: one packet was missing, which means
				// that no other packet in this span can be recovered
				break;
			}

			// proceed to next entry
			entries--;
			seq_start++;
		}
	}

seq_ok:;

	t38_core_state_t *t38 = t38_gateway_get_t38_core_state(tg->gw);

	// process any packets that we can
	while (1) {
		up = packet_sequencer_next_packet(&tg->sequencer);
		if (!up)
			break;

		ilog(LOG_DEBUG, "Processing %zu IFP bytes, seq %i", up->s->len, up->p.seq);

		t38_core_rx_ifp_packet(t38, (uint8_t *) up->s->s, up->s->len, up->p.seq);

		__udptl_packet_free(up);
	}

out:
	mutex_unlock(&tg->lock);
	return 0;

err:
	mutex_unlock(&tg->lock);
err_nolock:
	if (err)
		ilog(LOG_ERR | LOG_FLAG_LIMIT, "Failed to process UDPTL/T.38/IFP packet: %s", err);
	if (up)
		__udptl_packet_free(up);
	return -1;
}


void t38_gateway_stop(struct t38_gateway *tg) {
	if (!tg)
		return;
	if (tg->pcm_player)
		media_player_stop(tg->pcm_player);
	if (tg->t38_media)
		tg->t38_media->sdp_attr_print = sdp_insert_media_attributes;
}


void t38_init(void) {
	my_span_mh(NULL);
}



#endif
