/* GPRS SNDCP as per 3GPP TS 44.065 */
/*
 * (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 *
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdint.h>
#include <errno.h>
#include <arpa/inet.h>

#include <osmocom/core/talloc.h>

#include <osmocom/gprs/sndcp/sndcp.h>
#include <osmocom/gprs/sndcp/dcomp.h>
#include <osmocom/gprs/sndcp/pcomp.h>
#include <osmocom/gprs/sndcp/sndcp_prim.h>
#include <osmocom/gprs/sndcp/sndcp_private.h>
#include <osmocom/gprs/llc/llc_prim.h>

struct gprs_sndcp_ctx *g_sndcp_ctx;

struct gprs_sndcp_llc_params {
	uint16_t n201_u;
	uint16_t n201_i;
};

/* Section 8.9.9 LLC layer parameter default values */
static const struct gprs_sndcp_llc_params llc_default_params[GPRS_SNDCP_NUM_NSAPIS] = {
	[1] = { .n201_u		= 400, },
	[2] = { .n201_u		= 270, },
	[3] = { .n201_u		= 500, .n201_i	= 1503, },
	[5] = { .n201_u		= 500, .n201_i	= 1503, },
	[7] = { .n201_u		= 270, },
	[8] = { .n201_u		= 270, },
	[9] = { .n201_u		= 500, .n201_i	= 1503, },
	[11] = { .n201_u	= 500, .n201_i	= 1503, },
};

int osmo_gprs_sndcp_init(enum osmo_gprs_sndcp_location location)
{
	if (g_sndcp_ctx)
		talloc_free(g_sndcp_ctx);

	g_sndcp_ctx = talloc_zero(NULL, struct gprs_sndcp_ctx);
	g_sndcp_ctx->location = location;
	INIT_LLIST_HEAD(&g_sndcp_ctx->snme_list);
	return 0;
}

struct gprs_sndcp_mgmt_entity *gprs_sndcp_snme_alloc(uint32_t tlli)
{
	struct gprs_sndcp_mgmt_entity *snme;

	snme = talloc_zero(g_sndcp_ctx, struct gprs_sndcp_mgmt_entity);
	if (!snme)
		return NULL;

	snme->tlli = tlli;
	snme->comp.proto = gprs_sndcp_comp_alloc(snme);
	snme->comp.data = gprs_sndcp_comp_alloc(snme);

	llist_add(&snme->list, &g_sndcp_ctx->snme_list);

	return snme;
}

static void gprs_sndcp_snme_free(struct gprs_sndcp_mgmt_entity *snme)
{
	if (!snme)
		return;

	LOGSNME(snme, LOGL_DEBUG, "free()\n");
	llist_del(&snme->list);
	talloc_free(snme);
}

/* lookup SNDCP Management Entity based on TLLI */
struct gprs_sndcp_mgmt_entity *gprs_sndcp_snme_find_by_tlli(uint32_t tlli)
{
	struct gprs_sndcp_mgmt_entity *snme;

	llist_for_each_entry(snme, &g_sndcp_ctx->snme_list, list) {
		if (snme->tlli == tlli)
			return snme;
	}
	return NULL;
}

static void gprs_sndcp_snme_attach_sne(struct gprs_sndcp_mgmt_entity *snme, struct gprs_sndcp_entity *sne)
{
	if (snme->sne[sne->nsapi])
		osmo_panic("Trying to attach already existing SNDCP entity!\n");

	snme->sne[sne->nsapi] = sne;
}

/* snme may become freed upon return (returns bool freed) */
static bool gprs_sndcp_snme_detach_sne(struct gprs_sndcp_mgmt_entity *snme, struct gprs_sndcp_entity *sne)
{
	unsigned int i;
	if (!snme->sne[sne->nsapi])
		osmo_panic("Trying to detach already non-existing SNDCP entity!\n");

	OSMO_ASSERT(sne->snme->sne[sne->nsapi] == sne);
	snme->sne[sne->nsapi] = NULL;

	for (i = 0; i < ARRAY_SIZE(snme->sne); i++) {
		if (snme->sne[i])
			return false;
	}
	LOGSNME(snme, LOGL_DEBUG, "No SNDCP Entities left activate, freeing SNME\n");
	gprs_sndcp_snme_free(snme);
	return true;
}

struct gprs_sndcp_entity *gprs_sndcp_sne_alloc(struct gprs_sndcp_mgmt_entity *snme, uint8_t llc_sapi, uint8_t nsapi)
{
	struct gprs_sndcp_entity *sne;

	sne = talloc_zero(g_sndcp_ctx, struct gprs_sndcp_entity);
	if (!sne)
		return NULL;

	sne->llc_sapi = llc_sapi;
	sne->nsapi = nsapi;
	sne->defrag.timer.data = sne;
	//sne->fqueue.timer.cb = FIXME;
	sne->rx_state = GPRS_SNDCP_RX_S_FIRST;
	INIT_LLIST_HEAD(&sne->defrag.frag_list);

	sne->n201_u = llc_default_params[llc_sapi].n201_u;
	sne->n201_i = llc_default_params[llc_sapi].n201_i;

	sne->snme = snme;
	gprs_sndcp_snme_attach_sne(snme, sne);

	return sne;
}

void gprs_sndcp_sne_free(struct gprs_sndcp_entity *sne)
{
	if (!sne)
		return;

	LOGSNE(sne, LOGL_DEBUG, "free()\n");
	gprs_sndcp_snme_detach_sne(sne->snme, sne);
	sne->snme = NULL;
	talloc_free(sne);
}

struct gprs_sndcp_entity *gprs_sndcp_sne_by_dlci(uint32_t tlli, uint8_t llc_sapi)
{
	struct gprs_sndcp_mgmt_entity *snme = gprs_sndcp_snme_find_by_tlli(tlli);
	struct gprs_sndcp_entity *sne;
	unsigned int i;

	if (!snme)
		return NULL;

	for (i = 0; i < ARRAY_SIZE(snme->sne); i++) {
		sne = snme->sne[i];
		if (!sne)
			continue;
		if (sne->llc_sapi != llc_sapi)
			continue;
		return sne;
	}

	return NULL;
}

struct gprs_sndcp_entity *gprs_sndcp_sne_by_dlci_nsapi(uint32_t tlli, uint8_t llc_sapi, uint8_t nsapi)
{
	struct gprs_sndcp_mgmt_entity *snme = gprs_sndcp_snme_find_by_tlli(tlli);
	struct gprs_sndcp_entity *sne;

	if (!snme)
		return NULL;

	sne = gprs_sndcp_snme_get_sne(snme, nsapi);
	if (!sne || sne->llc_sapi != llc_sapi)
		return NULL;
	return sne;
}

int gprs_sndcp_sne_submit_llc_ll_establish_req(struct gprs_sndcp_entity *sne)
{
	struct osmo_gprs_llc_prim *llc_prim_tx;
	int rc;

	llc_prim_tx = osmo_gprs_llc_prim_alloc_ll_establish_req(sne->snme->tlli, sne->llc_sapi,
								sne->l3xid_req, sne->l3xid_req_len);
	OSMO_ASSERT(llc_prim_tx);
	rc = gprs_sndcp_prim_call_down_cb(llc_prim_tx);
	return rc;
}

int gprs_sndcp_sne_submit_llc_ll_xid_req(struct gprs_sndcp_entity *sne)
{
	struct osmo_gprs_llc_prim *llc_prim_tx;
	int rc;

	llc_prim_tx = osmo_gprs_llc_prim_alloc_ll_xid_req(sne->snme->tlli, sne->llc_sapi,
							  sne->l3xid_req, sne->l3xid_req_len);
	OSMO_ASSERT(llc_prim_tx);
	rc = gprs_sndcp_prim_call_down_cb(llc_prim_tx);
	return rc;
}

int gprs_sndcp_sne_submit_llc_ll_unitdata_req(struct gprs_sndcp_entity *sne, uint8_t *data, unsigned int len)
{
	struct osmo_gprs_llc_prim *llc_prim_tx;
	int rc;

	llc_prim_tx = osmo_gprs_llc_prim_alloc_ll_unitdata_req(sne->snme->tlli, sne->llc_sapi, data, len);
	OSMO_ASSERT(llc_prim_tx);
	llc_prim_tx->ll.unitdata_req.qos_params.peak_throughput = sne->peak_throughput;
	llc_prim_tx->ll.unitdata_req.qos_params.reliability_class = sne->reliability_class;
	llc_prim_tx->ll.unitdata_req.radio_prio = sne->radio_prio;
	rc = gprs_sndcp_prim_call_down_cb(llc_prim_tx);
	return rc;
}

int gprs_sndcp_sne_submit_sn_xid_cnf(struct gprs_sndcp_entity *sne)
{
	struct osmo_gprs_sndcp_prim *sndcp_prim_tx;
	int rc;

	sndcp_prim_tx = gprs_sndcp_prim_alloc_sn_xid_cnf(sne->snme->tlli, sne->llc_sapi, sne->nsapi);
	OSMO_ASSERT(sndcp_prim_tx);
	rc = gprs_sndcp_prim_call_up_cb(sndcp_prim_tx);
	return rc;
}

int gprs_sndcp_sne_submit_snsm_activate_rsp(struct gprs_sndcp_entity *sne)
{
	struct osmo_gprs_sndcp_prim *sndcp_prim_tx;
	int rc;

	sndcp_prim_tx = gprs_sndcp_prim_alloc_snsm_activate_rsp(sne->snme->tlli, sne->nsapi);
	OSMO_ASSERT(sndcp_prim_tx);
	rc = gprs_sndcp_prim_call_snsm_cb(sndcp_prim_tx);
	return rc;
}

/* Check if any compression parameters are set in the sgsn configuration */
static inline int any_pcomp_or_dcomp_active(const struct gprs_sndcp_ctx *sgsn)
{
#if 0
	/* TODO: */
	if (sgsn->cfg.pcomp_rfc1144.active || sgsn->cfg.pcomp_rfc1144.passive ||
	    sgsn->cfg.dcomp_v42bis.active || sgsn->cfg.dcomp_v42bis.passive)
		return true;
	//else
#endif
		return false;
}

/* Enqueue a fragment into the defragment queue */
static int defrag_enqueue(struct gprs_sndcp_entity *sne, uint8_t seg_nr,
			  uint8_t *data, uint32_t data_len)
{
	struct defrag_queue_entry *dqe;

	dqe = talloc_zero(g_sndcp_ctx, struct defrag_queue_entry);
	if (!dqe)
		return -ENOMEM;
	dqe->data = talloc_zero_size(dqe, data_len);
	if (!dqe->data) {
		talloc_free(dqe);
		return -ENOMEM;
	}
	dqe->seg_nr = seg_nr;
	dqe->data_len = data_len;

	llist_add(&dqe->list, &sne->defrag.frag_list);

	if (seg_nr > sne->defrag.highest_seg)
		sne->defrag.highest_seg = seg_nr;

	sne->defrag.seg_have |= (1 << seg_nr);
	sne->defrag.tot_len += data_len;

	memcpy(dqe->data, data, data_len);

	return 0;
}

/* return if we have all segments of this N-PDU */
static int defrag_have_all_segments(const struct gprs_sndcp_entity *sne)
{
	uint32_t seg_needed = 0;
	unsigned int i;

	/* create a bitmask of needed segments */
	for (i = 0; i <= sne->defrag.highest_seg; i++)
		seg_needed |= (1 << i);

	if (seg_needed == sne->defrag.seg_have)
		return 1;

	return 0;
}

static struct defrag_queue_entry *defrag_get_seg(const struct gprs_sndcp_entity *sne,
						 uint32_t seg_nr)
{
	struct defrag_queue_entry *dqe;

	llist_for_each_entry(dqe, &sne->defrag.frag_list, list) {
		if (dqe->seg_nr == seg_nr) {
			llist_del(&dqe->list);
			return dqe;
		}
	}
	return NULL;
}

/* Returns talloced buffer containing decompressed data, NULL on error. */
static uint8_t *decompress_segment(struct gprs_sndcp_entity *sne, void *ctx,
				   const uint8_t *compressed_data, unsigned int compressed_data_len,
				   unsigned int *decompressed_data_len)
{
	int rc;
	uint8_t *expnd = NULL;
	*decompressed_data_len = 0;

#if DEBUG_IP_PACKETS == 1
	LOGSNE(sne, "\n");
	LOGSNE(sne, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n");
	LOGSNE(sne, "===================================================\n");
#endif

	expnd = talloc_zero_size(ctx, compressed_data_len * MAX_DATADECOMPR_FAC +
					 MAX_HDRDECOMPR_INCR);
	memcpy(expnd, compressed_data, compressed_data_len);

	/* Apply data decompression */
	rc = gprs_sndcp_dcomp_expand(expnd, compressed_data_len, sne->defrag.dcomp,
				     sne->defrag.data);
	if (rc < 0) {
		LOGSNE(sne, LOGL_ERROR,
		       "Data decompression failed!\n");
		talloc_free(expnd);
		return NULL;
	}

	/* Apply header decompression */
	rc = gprs_sndcp_pcomp_expand(expnd, rc, sne->defrag.pcomp, sne->defrag.proto);
	if (rc < 0) {
		LOGSNE(sne, LOGL_ERROR,
		       "TCP/IP Header decompression failed!\n");
		talloc_free(expnd);
		return NULL;
	}

	*decompressed_data_len = rc;

#if DEBUG_IP_PACKETS == 1
	debug_ip_packet(expnd, *decompressed_data_len, 1, "defrag_segments()");
	LOGSNE(sne, "===================================================\n");
	LOGSNE(sne, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n");
	LOGSNE(sne, "\n");
#endif
	return expnd;
}

/* Perform actual defragmentation and create an output packet */
static int defrag_segments(struct gprs_sndcp_entity *sne)
{
	struct msgb *msg;
	unsigned int seg_nr;
	uint8_t *npdu;
	unsigned int npdu_len;
	int rc;
	uint8_t *expnd = NULL;
	struct osmo_gprs_sndcp_prim *sndcp_prim_tx;

	LOGSNE(sne, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Defragment output PDU %u "
		"num_seg=%u tot_len=%u\n", sne->snme->tlli, sne->nsapi,
		sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.tot_len);
	msg = msgb_alloc_headroom(sne->defrag.tot_len+256, 128, "SNDCP Defrag");
	if (!msg)
		return -ENOMEM;

	/* FIXME: message headers + identifiers */

	npdu = msg->data;

	for (seg_nr = 0; seg_nr <= sne->defrag.highest_seg; seg_nr++) {
		struct defrag_queue_entry *dqe;
		uint8_t *data;

		dqe = defrag_get_seg(sne, seg_nr);
		if (!dqe) {
			LOGSNE(sne, LOGL_ERROR, "Segment %u missing\n", seg_nr);
			msgb_free(msg);
			return -EIO;
		}
		/* actually append the segment to the N-PDU */
		data = msgb_put(msg, dqe->data_len);
		memcpy(data, dqe->data, dqe->data_len);

		/* release memory for the fragment queue entry */
		talloc_free(dqe);
	}

	npdu_len = sne->defrag.tot_len;

	/* FIXME: cancel timer */

	/* actually send the N-PDU to the SGSN core code, which then
	 * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */

	/* Decompress packet */
	if (any_pcomp_or_dcomp_active(g_sndcp_ctx)) {
		expnd = decompress_segment(sne, msg, npdu, npdu_len, &npdu_len);
		if (!expnd) {
			rc = -EIO;
			goto ret_free;
		}
	} else {
		expnd = npdu;
	}

	/* Trigger SN-UNITDATA.ind to upper layers: */
	sndcp_prim_tx = gprs_sndcp_prim_alloc_sn_unitdata_ind(sne->snme->tlli, sne->llc_sapi, sne->nsapi, expnd, npdu_len);
	rc = gprs_sndcp_prim_call_up_cb(sndcp_prim_tx);

ret_free:
	/* we must free the memory we allocated above; ownership is not transferred
	 * downwards in the call above */
	msgb_free(msg);
	return rc;
}

static int defrag_input(struct gprs_sndcp_entity *sne, uint8_t *hdr, unsigned int len)
{
	struct sndcp_common_hdr *sch;
	struct sndcp_udata_hdr *suh;
	uint16_t npdu_num;
	uint8_t *data;
	int rc;

	sch = (struct sndcp_common_hdr *) hdr;
	if (sch->first) {
		suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr));
	} else
		suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr));

	data = (uint8_t *)suh + sizeof(struct sndcp_udata_hdr);

	npdu_num = (suh->npdu_high << 8) | suh->npdu_low;

	LOGSNE(sne, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Input PDU %u Segment %u "
		"Length %u %s %s\n", sne->snme->tlli, sne->nsapi, npdu_num,
		suh->seg_nr, len, sch->first ? "F " : "", sch->more ? "M" : "");

	if (sch->first) {
		/* first segment of a new packet.  Discard all leftover fragments of
		 * previous packet */
		if (!llist_empty(&sne->defrag.frag_list)) {
			struct defrag_queue_entry *dqe, *dqe2;
			LOGSNE(sne, LOGL_INFO, "TLLI=0x%08x NSAPI=%u: Dropping "
			     "SN-PDU %u due to insufficient segments (%04x)\n",
			     sne->snme->tlli, sne->nsapi, sne->defrag.npdu,
			     sne->defrag.seg_have);
			llist_for_each_entry_safe(dqe, dqe2, &sne->defrag.frag_list, list) {
				llist_del(&dqe->list);
				talloc_free(dqe);
			}
		}
		/* store the currently de-fragmented PDU number */
		sne->defrag.npdu = npdu_num;

		/* Re-set fragmentation state */
		sne->defrag.no_more = sne->defrag.highest_seg = sne->defrag.seg_have = 0;
		sne->defrag.tot_len = 0;
		/* FIXME: (re)start timer */
	}

	if (sne->defrag.npdu != npdu_num) {
		LOGSNE(sne, LOGL_INFO, "Segment for different SN-PDU "
			"(%u != %u)\n", npdu_num, sne->defrag.npdu);
		/* FIXME */
	}

	/* FIXME: check if seg_nr already exists */
	/* make sure to subtract length of SNDCP header from 'len' */
	rc = defrag_enqueue(sne, suh->seg_nr, data, len - (data - hdr));
	if (rc < 0)
		return rc;

	if (!sch->more) {
		/* this is suppsed to be the last segment of the N-PDU, but it
		 * might well be not the last to arrive */
		sne->defrag.no_more = 1;
	}

	if (sne->defrag.no_more) {
		/* we have already received the last segment before, let's check
		 * if all the previous segments exist */
		if (defrag_have_all_segments(sne))
			return defrag_segments(sne);
	}

	return 0;
}

/* Fragmenter state */
struct sndcp_frag_state {
	uint8_t frag_nr;
	struct msgb *msg;	/* original message */
	uint8_t *next_byte;	/* first byte of next fragment */

	struct gprs_sndcp_entity *sne;
};

/* returns '1' if there are more fragments to send, '0' if none */
static int gprs_sndcp_send_ud_frag(struct sndcp_frag_state *fs,
			      uint8_t pcomp, uint8_t dcomp)
{
	struct gprs_sndcp_entity *sne = fs->sne;
	struct sndcp_common_hdr *sch;
	struct sndcp_comp_hdr *scomph;
	struct sndcp_udata_hdr *suh;
	struct msgb *fmsg;
	unsigned int max_payload_len;
	unsigned int len;
	uint8_t *data;
	int rc, more;

	fmsg = msgb_alloc_headroom(sne->n201_u+256, 128, "SNDCP Frag");
	if (!fmsg) {
		msgb_free(fs->msg);
		return -ENOMEM;
	}

	/* prepend common SNDCP header */
	sch = (struct sndcp_common_hdr *) msgb_put(fmsg, sizeof(*sch));
	sch->nsapi = sne->nsapi;
	/* Set FIRST bit if we are the first fragment in a series */
	if (fs->frag_nr == 0)
		sch->first = 1;
	sch->type = 1;

	/* append the compression header for first fragment */
	if (sch->first) {
		scomph = (struct sndcp_comp_hdr *)
				msgb_put(fmsg, sizeof(*scomph));
		scomph->pcomp = pcomp;
		scomph->dcomp = dcomp;
	}

	/* append the user-data header */
	suh = (struct sndcp_udata_hdr *) msgb_put(fmsg, sizeof(*suh));
	suh->npdu_low = sne->tx_npdu_nr & 0xff;
	suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf;
	suh->seg_nr = fs->frag_nr % 0xf;

	/* calculate remaining length to be sent */
	len = (fs->msg->data + fs->msg->len) - fs->next_byte;
	/* how much payload can we actually send via LLC? */
	max_payload_len = sne->n201_u - (sizeof(*sch) + sizeof(*suh));
	if (sch->first)
		max_payload_len -= sizeof(*scomph);
	/* check if we're exceeding the max */
	if (len > max_payload_len)
		len = max_payload_len;

	/* copy the actual fragment data into our fmsg */
	data = msgb_put(fmsg, len);
	memcpy(data, fs->next_byte, len);

	/* Increment fragment number and data pointer to next fragment */
	fs->frag_nr++;
	fs->next_byte += len;

	/* determine if we have more fragemnts to send */
	if ((fs->msg->data + fs->msg->len) <= fs->next_byte)
		more = 0;
	else
		more = 1;

	/* set the MORE bit of the SNDCP header accordingly */
	sch->more = more;

	/* Send down the stack SNDCP->LLC as LL-UNITDATA.req: */
	rc = gprs_sndcp_sne_submit_llc_ll_unitdata_req(sne, fmsg->data, fmsg->len);
	msgb_free(fmsg);
	/* abort in case of error, do not advance frag_nr / next_byte */
	if (rc < 0) {
		msgb_free(fs->msg);
		return rc;
	}

	if (!more) {
		/* we've sent all fragments */
		msgb_free(fs->msg);
		memset(fs, 0, sizeof(*fs));
		/* increment NPDU number for next frame */
		sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff;
		return 0;
	}

	/* default: more fragments to send */
	return 1;
}

/* 5.1.1.3 SN-UNITDATA.request */
int gprs_sndcp_sne_handle_sn_unitdata_req(struct gprs_sndcp_entity *sne, uint8_t *npdu, unsigned int npdu_len)
{
	struct sndcp_common_hdr *sch;
	struct sndcp_comp_hdr *scomph;
	struct sndcp_udata_hdr *suh;
	struct sndcp_frag_state fs;
	uint8_t pcomp = 0;
	uint8_t dcomp = 0;
	int rc;
	struct msgb *msg = msgb_alloc_headroom(npdu_len + 256, 128, "sndcp-tx");

	memcpy(msgb_put(msg, npdu_len), npdu, npdu_len);

	/* Compress packet */
#if DEBUG_IP_PACKETS == 1
	LOGSNE(sne, "\n");
	LOGSNE(sne, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n");
	LOGSNE(sne, "===================================================\n");
	debug_ip_packet(npdu, npdu_len, 0, __func__ "()");
#endif
	if (any_pcomp_or_dcomp_active(g_sndcp_ctx)) {

		/* Apply header compression */
		rc = gprs_sndcp_pcomp_compress(msg->data, msg->len, &pcomp,
					       sne->snme->comp.proto, sne->nsapi);
		if (rc < 0) {
			LOGSNE(sne, LOGL_ERROR, "TCP/IP Header compression failed!\n");
			rc = -EIO;
			goto free_ret;
		}

		/* Fixup pointer locations and sizes in message buffer to match
		 * the new, compressed buffer size */
		msgb_get(msg, msg->len);
		msgb_put(msg, rc);

		/* Apply data compression */
		rc = gprs_sndcp_dcomp_compress(msg->data, msg->len, &dcomp,
					       sne->snme->comp.data, sne->nsapi);
		if (rc < 0) {
			LOGSNE(sne, LOGL_ERROR, "Data compression failed!\n");
			rc = -EIO;
			goto free_ret;
		}

		/* Fixup pointer locations and sizes in message buffer to match
		 * the new, compressed buffer size */
		msgb_get(msg, msg->len);
		msgb_put(msg, rc);
	}
#if DEBUG_IP_PACKETS == 1
	LOGSNE(sne, "===================================================\n");
	DLOGSNE(sne, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n");
	LOGSNE(sne, "\n");
#endif

	/* Check if we need to fragment this N-PDU into multiple SN-PDUs */
	if (msg->len > sne->n201_u -
			(sizeof(*sch) + sizeof(*suh) + sizeof(*scomph))) {
		/* initialize the fragmenter state */
		fs.msg = msg;
		fs.frag_nr = 0;
		fs.next_byte = msg->data;
		fs.sne = sne;

		/* call function to generate and send fragments until all
		 * of the N-PDU has been sent */
		while (1) {
			int rc = gprs_sndcp_send_ud_frag(&fs, pcomp, dcomp);
			if (rc == 0)
				return 0;
			if (rc < 0)
				return rc;
		}
		/* not reached */
		return 0;
	}

	/* this is the non-fragmenting case where we only build 1 SN-PDU */

	/* prepend the user-data header */
	suh = (struct sndcp_udata_hdr *) msgb_push(msg, sizeof(*suh));
	suh->npdu_low = sne->tx_npdu_nr & 0xff;
	suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf;
	suh->seg_nr = 0;
	sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff;

	scomph = (struct sndcp_comp_hdr *) msgb_push(msg, sizeof(*scomph));
	scomph->pcomp = pcomp;
	scomph->dcomp = dcomp;

	/* prepend common SNDCP header */
	sch = (struct sndcp_common_hdr *) msgb_push(msg, sizeof(*sch));
	sch->first = 1;
	sch->type = 1;
	sch->nsapi = sne->nsapi;

	/* Send down the stack SNDCP->LLC as LL-UNITDATA.req: */
	rc = gprs_sndcp_sne_submit_llc_ll_unitdata_req(sne, msg->data, msg->len);
free_ret:
	msgb_free(msg);
	return rc;
}


/* Generate SNDCP-XID message (5.1.1.5 SN-XID.request) */
static int gprs_sndcp_sne_gen_sndcp_xid(struct gprs_sndcp_entity *sne, uint8_t *bytes, int bytes_len, const struct osmo_gprs_sndcp_prim *sndcp_prim)
{
	int entity = 0;
	LLIST_HEAD(comp_fields);
	struct gprs_sndcp_pcomp_rfc1144_params rfc1144_params;
	struct gprs_sndcp_comp_field rfc1144_comp_field;
	struct gprs_sndcp_dcomp_v42bis_params v42bis_params;
	struct gprs_sndcp_comp_field v42bis_comp_field;

	memset(&rfc1144_comp_field, 0, sizeof(struct gprs_sndcp_comp_field));
	memset(&v42bis_comp_field, 0, sizeof(struct gprs_sndcp_comp_field));

	/* Setup rfc1144 */
	if (sndcp_prim->sn.xid_req.pcomp_rfc1144.active) {
		rfc1144_params.nsapi[0] = sne->nsapi;
		rfc1144_params.nsapi_len = 1;
		rfc1144_params.s01 = sndcp_prim->sn.xid_req.pcomp_rfc1144.s01;
		rfc1144_comp_field.p = 1;
		rfc1144_comp_field.entity = entity;
		rfc1144_comp_field.algo.pcomp = RFC_1144;
		rfc1144_comp_field.comp[RFC1144_PCOMP1] = 1;
		rfc1144_comp_field.comp[RFC1144_PCOMP2] = 2;
		rfc1144_comp_field.comp_len = RFC1144_PCOMP_NUM;
		rfc1144_comp_field.rfc1144_params = &rfc1144_params;
		entity++;
		llist_add(&rfc1144_comp_field.list, &comp_fields);
	}

	/* Setup V.42bis */
	if (sndcp_prim->sn.xid_req.dcomp_v42bis.active) {
		v42bis_params.nsapi[0] = sne->nsapi;
		v42bis_params.nsapi_len = 1;
		v42bis_params.p0 = sndcp_prim->sn.xid_req.dcomp_v42bis.p0;
		v42bis_params.p1 = sndcp_prim->sn.xid_req.dcomp_v42bis.p1;
		v42bis_params.p2 = sndcp_prim->sn.xid_req.dcomp_v42bis.p2;
		v42bis_comp_field.p = 1;
		v42bis_comp_field.entity = entity;
		v42bis_comp_field.algo.dcomp = V42BIS;
		v42bis_comp_field.comp[V42BIS_DCOMP1] = 1;
		v42bis_comp_field.comp_len = V42BIS_DCOMP_NUM;
		v42bis_comp_field.v42bis_params = &v42bis_params;
		entity++;
		llist_add(&v42bis_comp_field.list, &comp_fields);
	}

	LOGSNE(sne, LOGL_DEBUG, "SN-XID.req comp_fields:\n");
	gprs_sndcp_dump_comp_fields(&comp_fields, LOGL_DEBUG);

	/* Do not attempt to compile anything if there is no data in the list */
	if (llist_empty(&comp_fields))
		return 0;

	/* Compile bytestream */
	return gprs_sndcp_compile_xid(bytes, bytes_len, &comp_fields,
				      DEFAULT_SNDCP_VERSION);
}

/* 5.1.1.5 SN-XID.request */
int gprs_sndcp_sne_handle_sn_xid_req(struct gprs_sndcp_entity *sne, const struct osmo_gprs_sndcp_prim *sndcp_prim)
{
	int rc;
	uint8_t l3params[1024];

	/* Wipe off all compression entities and their states to
	 * get rid of possible leftovers from a previous session */
	gprs_sndcp_comp_free(sne->snme->comp.proto);
	gprs_sndcp_comp_free(sne->snme->comp.data);
	sne->snme->comp.proto = gprs_sndcp_comp_alloc(sne->snme);
	sne->snme->comp.data = gprs_sndcp_comp_alloc(sne->snme);

	/* Generate compression parameter bytestream */
	sne->l3xid_req_len = gprs_sndcp_sne_gen_sndcp_xid(sne, l3params, sizeof(l3params), sndcp_prim);
	if (sne->l3xid_req_len > 0) {
		talloc_free(sne->l3xid_req);
		sne->l3xid_req = talloc_size(sne, sne->l3xid_req_len);
		memcpy(sne->l3xid_req, l3params, sne->l3xid_req_len);
	} else {
		talloc_free(sne->l3xid_req);
		sne->l3xid_req = NULL;
	}
	sne->xid_req_in_transit_orig_sn_xid_req = true;

	rc = gprs_sndcp_sne_submit_llc_ll_xid_req(sne);
	return rc;
}

/* 5.1.1.7 SN-XID.response */
/* FIXME: This is currently uses comp_fields stored from gprs_sndcp_snme_handle_llc_ll_xid_ind().
* It should use info provided by upper layers in the prim instead */
int gprs_sndcp_sne_handle_sn_xid_rsp(struct gprs_sndcp_entity *sne, const struct osmo_gprs_sndcp_prim *sndcp_prim)
{
	struct osmo_gprs_llc_prim *llc_prim_tx;
	struct msgb *msg;
	int rc;

	if (!sne->l3_xid_comp_fields_req_from_peer) {
		LOGSNE(sne, LOGL_DEBUG, "SN-XID.rsp origianted comp_fields not found!\n");
		return -EINVAL;
	}

	LOGSNE(sne, LOGL_DEBUG, "SN-XID.rsp comp_fields:\n");
	gprs_sndcp_dump_comp_fields(sne->l3_xid_comp_fields_req_from_peer, LOGL_DEBUG);

	llc_prim_tx = osmo_gprs_llc_prim_alloc_ll_xid_resp(sne->snme->tlli, sne->llc_sapi, NULL, 256);
	OSMO_ASSERT(llc_prim_tx);
	msg = llc_prim_tx->oph.msg;
	llc_prim_tx->ll.l3_pdu = msg->tail;
	/* Compile modified SNDCP-XID bytes */
	rc = gprs_sndcp_compile_xid(msg->tail,
				    msgb_tailroom(msg),
				    sne->l3_xid_comp_fields_req_from_peer, 0);
	if (rc < 0) {
		msgb_free(msg);
		return -EINVAL;
	}
	msgb_put(msg, rc);
	llc_prim_tx->ll.l3_pdu_len = rc;

	/* Transmit LL-XID.rsp to lower layers (LLC): */
	rc = gprs_sndcp_prim_call_down_cb(llc_prim_tx);
	return rc;
}

/* Section 5.1.2.17 LL-UNITDATA.ind, see osmo-sgsn sndcp_llunitdata_ind() */
int gprs_sndcp_sne_handle_llc_ll_unitdata_ind(struct gprs_sndcp_entity *sne, struct sndcp_common_hdr *sch, uint16_t len)
{
	struct sndcp_comp_hdr *scomph = NULL;
	struct sndcp_udata_hdr *suh;
	uint8_t *npdu;
	uint16_t npdu_num __attribute__((unused));
	int npdu_len;
	int rc = 0;
	uint8_t *expnd = NULL;
	uint8_t *hdr = (uint8_t *)sch;
	struct osmo_gprs_sndcp_prim *sndcp_prim_tx;

	if (sch->first) {
		scomph = (struct sndcp_comp_hdr *) (hdr + 1);
		suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr));
	} else
		suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr));

	if (sch->type == 0) {
		LOGSNE(sne, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n");
		return -EINVAL;
	}

	if (len < sizeof(*sch) + sizeof(*suh)) {
		LOGSNE(sne, LOGL_ERROR, "SN-UNITDATA PDU too short (%u)\n", len);
		return -EIO;
	}

	if (scomph) {
		sne->defrag.pcomp = scomph->pcomp;
		sne->defrag.dcomp = scomph->dcomp;
		sne->defrag.proto = sne->snme->comp.proto;
		sne->defrag.data = sne->snme->comp.data;
	}

	/* any non-first segment is by definition something to defragment
	 * as is any segment that tells us there are more segments */
	if (!sch->first || sch->more)
		return defrag_input(sne, hdr, len);

	npdu_num = (suh->npdu_high << 8) | suh->npdu_low;
	npdu = (uint8_t *)suh + sizeof(*suh);
	npdu_len = (hdr + len) - npdu;

	if (npdu_len <= 0) {
		LOGSNE(sne, LOGL_ERROR, "Short SNDCP N-PDU: %d\n", npdu_len);
		return -EIO;
	}
	/* actually send the N-PDU to the SGSN core code, which then
	 * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */

	/* Decompress packet */
	if (any_pcomp_or_dcomp_active(g_sndcp_ctx)) {
		expnd = decompress_segment(sne, g_sndcp_ctx, npdu, npdu_len, (unsigned int *)&npdu_len);
		if (!expnd) {
			rc = -EIO;
			goto ret_free;
		}
	} else {
		expnd = npdu;
	}

	/* Trigger SN-UNITDATA.ind to upper layers: */
	sndcp_prim_tx = gprs_sndcp_prim_alloc_sn_unitdata_ind(sne->snme->tlli, sne->llc_sapi, sne->nsapi, expnd, npdu_len);
	rc = gprs_sndcp_prim_call_up_cb(sndcp_prim_tx);

ret_free:
	if (any_pcomp_or_dcomp_active(g_sndcp_ctx))
		talloc_free(expnd);
	return rc;
}

/* Handle header compression entities */
static int gprs_sndcp_snme_handle_pcomp_entities(struct gprs_sndcp_mgmt_entity *snme,
						 uint32_t sapi,
						 struct gprs_sndcp_comp_field *comp_field)
{
	/* Note: This functions also transforms the comp_field into its
	 * echo form (strips comp values, resets propose bit etc...)
	 * the processed comp_fields can then be sent back as XID-
	 * Response without further modification. */

	/* Delete propose bit */
	comp_field->p = 0;

	/* Process proposed parameters */
	switch (comp_field->algo.pcomp) {
	case RFC_1144:
		if (g_sndcp_ctx->cfg.pcomp_rfc1144_passive_accept
		    && comp_field->rfc1144_params->nsapi_len > 0) {
			LOGSNME(snme, LOGL_DEBUG, "Accepting RFC1144 header compression...\n");
			gprs_sndcp_comp_add(snme, snme->comp.proto, comp_field);
		} else {
			LOGSNME(snme, LOGL_DEBUG, "Rejecting RFC1144 header compression...\n");
			gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
			comp_field->rfc1144_params->nsapi_len = 0;
		}
		break;
	case RFC_2507:
		/* RFC 2507 is not yet supported,
		 * so we set applicable nsapis to zero */
		LOGSNME(snme, LOGL_DEBUG, "Rejecting RFC2507 header compression...\n");
		comp_field->rfc2507_params->nsapi_len = 0;
		gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
		break;
	case ROHC:
		/* ROHC is not yet supported,
		 * so we set applicable nsapis to zero */
		LOGSNME(snme, LOGL_DEBUG, "Rejecting ROHC header compression...\n");
		comp_field->rohc_params->nsapi_len = 0;
		gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
		break;
	}

	return 0;
}

/* Hanle data compression entities */
static int gprs_sndcp_snme_handle_dcomp_entities(struct gprs_sndcp_mgmt_entity *snme,
						 uint32_t sapi,
						 struct gprs_sndcp_comp_field *comp_field)
{
	/* See note in handle_pcomp_entities() */

	/* Delete propose bit */
	comp_field->p = 0;

	/* Process proposed parameters */
	switch (comp_field->algo.dcomp) {
	case V42BIS:
		if (g_sndcp_ctx->cfg.dcomp_v42bis_passive_accept &&
		    comp_field->v42bis_params->nsapi_len > 0) {
			LOGSNME(snme, LOGL_DEBUG, "Accepting V.42bis data compression...\n");
			gprs_sndcp_comp_add(snme, snme->comp.data, comp_field);
		} else {
			LOGSNME(snme, LOGL_DEBUG, "Rejecting V.42bis data compression...\n");
			gprs_sndcp_comp_delete(snme->comp.data, comp_field->entity);
			comp_field->v42bis_params->nsapi_len = 0;
		}
		break;
	case V44:
		/* V44 is not yet supported,
		 * so we set applicable nsapis to zero */
		LOGSNME(snme, LOGL_DEBUG, "Rejecting V.44 data compression...\n");
		comp_field->v44_params->nsapi_len = 0;
		gprs_sndcp_comp_delete(snme->comp.data, comp_field->entity);
		break;
	}

	return 0;

}

/* 5.1.2.10 SN-XID.indication */
int gprs_sndcp_snme_handle_llc_ll_xid_ind(struct gprs_sndcp_mgmt_entity *snme, uint32_t sapi,
					  uint16_t n201_u, uint16_t n201_i,
					  uint8_t *l3params, unsigned int l3params_len)
{
	int rc;
	int compclass;
	int version;
	struct llist_head *comp_fields;
	struct gprs_sndcp_comp_field *comp_field;
	struct gprs_sndcp_entity *sne = NULL;
	struct osmo_gprs_sndcp_prim *sndcp_prim_tx;
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(snme->sne); i++) {
		struct gprs_sndcp_entity *sne_i = snme->sne[i];
		if (!sne_i)
			continue;
		if (sne_i->llc_sapi != sapi)
			continue;
		LOGSNE(sne_i, LOGL_DEBUG, "LL-XID.ind: Found SNE SAPI=%u\n", sapi);
		sne = sne_i;
		break;
	}
	if (!sne) {
		LOGSNME(snme, LOGL_ERROR, "LL-XID.ind: No SNDCP entity found having sent a LL-XID.ind (SAPI=%u)\n", sapi);
		return -EINVAL;
	}

	sne->n201_u = n201_u;
	sne->n201_i = n201_i;

	/* Some phones send zero byte length SNDCP frames
	 * and do require a confirmation response. */
	if (l3params_len == 0) {
		/* TS 44.065 6.8: "If the SNDCP entity receives an LL-XID.indication without
		 * an SNDCP XID block, it shall not respond with the LL-XID.response primitive."
		 */
		return 0;
	}


	/* Parse SNDCP-CID XID-Field */
	comp_fields = gprs_sndcp_parse_xid(&version, sne,
					   l3params,
					   l3params_len,
					   NULL);
	if (!comp_fields) {
		LOGSNME(snme, LOGL_NOTICE, "LL-XID.ind: parse failed\n");
		return -EINVAL;
	}

	/* Handle compression entities */
	LOGSNME(snme, LOGL_DEBUG, "LL-XID.cnf requested comp_fields:\n");
	gprs_sndcp_dump_comp_fields(comp_fields, LOGL_DEBUG);

	llist_for_each_entry(comp_field, comp_fields, list) {
		compclass = gprs_sndcp_get_compression_class(comp_field);
		if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION)
			rc = gprs_sndcp_snme_handle_pcomp_entities(snme, sapi, comp_field);
		else if (compclass == SNDCP_XID_DATA_COMPRESSION)
			rc = gprs_sndcp_snme_handle_dcomp_entities(snme, sapi, comp_field);
		else {
			gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
			gprs_sndcp_comp_delete(snme->comp.data, comp_field->entity);
			rc = 0;
		}

		if (rc < 0) {
			talloc_free(comp_fields);
			return -EINVAL;
		}
	}

	TALLOC_FREE(sne->l3_xid_comp_fields_req_from_peer);
	sne->l3_xid_comp_fields_req_from_peer = comp_fields;

	/* Trigger SN-XID.ind to upper layers: */
	sndcp_prim_tx = gprs_sndcp_prim_alloc_sn_xid_ind(sne->snme->tlli, sne->llc_sapi, sne->nsapi);
	rc = gprs_sndcp_prim_call_up_cb(sndcp_prim_tx);
	return rc;
}

/* 5.1.2.12 SN-XID.confirm
 * (See also: TS 144 065, Section 6.8 XID parameter negotiation)
 */
int gprs_sndcp_snme_handle_llc_ll_xid_cnf(struct gprs_sndcp_mgmt_entity *snme, uint32_t sapi,
					  uint16_t n201_u, uint16_t n201_i,
					  uint8_t *l3params, unsigned int l3params_len)
{
	/* Note: This function handles an incoming SNDCP-XID confirmation.
	 * Since the confirmation fields may lack important parameters we
	 * will reconstruct these missing fields using the original request
	 * we have sent. After that we will create (or delete) the
	 * compression entities */

	struct llist_head *comp_fields_req;
	struct llist_head *comp_fields_conf;
	struct gprs_sndcp_comp_field *comp_field;
	int rc = 0;
	int compclass;
	unsigned int i;
	struct gprs_sndcp_entity *sne = NULL;

	/* We need both, the confirmation that is sent back by the ms,
	 * and the original request we have sent. If one of this is missing
	 * we can not process the confirmation, the caller must check if
	 * request and confirmation fields are available. */
	for (i = 0; i < ARRAY_SIZE(snme->sne); i++) {
		struct gprs_sndcp_entity *sne_i = snme->sne[i];
		if (!sne_i)
			continue;
		if (sne_i->llc_sapi != sapi)
			continue;
		LOGSNE(sne_i, LOGL_DEBUG, "LL-XID.cnf: Found SNE SAPI=%u\n", sapi);
		sne = sne_i;
		break;
	}
	if (!sne) {
		LOGSNME(snme, LOGL_ERROR, "LL-XID.cnf: No SNDCP entity found having sent a LL-XID.req (SAPI=%u)\n", sapi);
		return -EINVAL;
	}

	sne->n201_u = n201_u;
	sne->n201_i = n201_i;

	if (sne->l3xid_req && sne->l3xid_req_len > 0) {
		/* Parse SNDCP-CID XID-Field */
		comp_fields_req = gprs_sndcp_parse_xid(NULL, sne, sne->l3xid_req, sne->l3xid_req_len, NULL);
		if (!comp_fields_req)
			return -EINVAL;

		/* Parse SNDCP-CID XID-Field */
		comp_fields_conf = gprs_sndcp_parse_xid(NULL, sne, l3params, l3params_len, comp_fields_req);
		if (!comp_fields_conf) {
			talloc_free(comp_fields_req);
			return -EINVAL;
		}

		LOGSNDCP(LOGL_DEBUG, "LL-XID.cnf response comp_fields:\n");
		gprs_sndcp_dump_comp_fields(comp_fields_conf, LOGL_DEBUG);

		/* Handle compression entities */
		llist_for_each_entry(comp_field, comp_fields_conf, list) {
			compclass = gprs_sndcp_get_compression_class(comp_field);
			if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION)
				rc = gprs_sndcp_snme_handle_pcomp_entities(snme, sapi, comp_field);
			else if (compclass == SNDCP_XID_DATA_COMPRESSION)
				rc = gprs_sndcp_snme_handle_dcomp_entities(snme, sapi, comp_field);
			else {
				gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
				gprs_sndcp_comp_delete(snme->comp.data, comp_field->entity);
				rc = 0;
			}
			if (rc < 0)
				break;
		}

		talloc_free(comp_fields_req);
		talloc_free(comp_fields_conf);
	}

	/* Originated by SNSM-ACTIVATE.ind: */
	if (sne->xid_req_in_transit_orig_snsm_activate_ind) {
		sne->xid_req_in_transit_orig_snsm_activate_ind = false;
		rc = gprs_sndcp_sne_submit_snsm_activate_rsp(sne);
	}
	/* Originated by SN-XID.req: */
	if (sne->xid_req_in_transit_orig_sn_xid_req) {
		sne->xid_req_in_transit_orig_sn_xid_req = false;
		rc = gprs_sndcp_sne_submit_sn_xid_cnf(sne);
	}

	/* TODO:
	* gprs_sndcp_prim_alloc_sn_xid_cnf() SN-XID-CNF if SN-XID-REQ pending
	* gprs_sndcp_prim_alloc_snsm_activate_rsp() SNSM-ACTIVATE-RSP if SNSM-ACTIVATE-IND pending
	*/
	return rc;
}
