SNDCP: add RFC1144 header compression functionality

- Add module to handle compression entities
- Add module to control header compression
- Introduce VTY commands for heade compression configuration
- Add changes in sndcp and llc to integrate header compression

Change-Id: Ia00260dc09978844c2865957b4d43000b78b5e43
diff --git a/openbsc/src/gprs/gprs_sndcp_pcomp.c b/openbsc/src/gprs/gprs_sndcp_pcomp.c
new file mode 100644
index 0000000..5f6fb2c
--- /dev/null
+++ b/openbsc/src/gprs/gprs_sndcp_pcomp.c
@@ -0,0 +1,280 @@
+/* GPRS SNDCP header compression handler */
+
+/* (C) 2016 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * 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 <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <math.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <openbsc/gprs_llc.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_sndcp_xid.h>
+#include <openbsc/slhc.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sndcp_comp.h>
+#include <openbsc/gprs_sndcp_pcomp.h>
+
+/* Initalize header compression */
+int gprs_sndcp_pcomp_init(const void *ctx, struct gprs_sndcp_comp *comp_entity,
+			  const struct gprs_sndcp_comp_field *comp_field)
+{
+	/* Note: This function is automatically called from
+	 * gprs_sndcp_comp.c when a new header compression
+	 * entity is created by gprs_sndcp.c */
+
+	OSMO_ASSERT(comp_entity);
+	OSMO_ASSERT(comp_field);
+
+	if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION
+	    && comp_entity->algo == RFC_1144) {
+		comp_entity->state =
+		    slhc_init(ctx, comp_field->rfc1144_params->s01 + 1,
+			      comp_field->rfc1144_params->s01 + 1);
+		LOGP(DSNDCP, LOGL_INFO,
+		     "RFC1144 header compression initalized.\n");
+		return 0;
+	}
+
+	/* Just in case someone tries to initalize an unknown or unsupported
+	 * header compresson. Since everything is checked during the SNDCP
+	 * negotiation process, this should never happen! */
+	OSMO_ASSERT(false);
+}
+
+/* Terminate header compression */
+void gprs_sndcp_pcomp_term(struct gprs_sndcp_comp *comp_entity)
+{
+	/* Note: This function is automatically called from
+	 * gprs_sndcp_comp.c when a header compression
+	 * entity is deleted by gprs_sndcp.c */
+
+	OSMO_ASSERT(comp_entity);
+
+	if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION
+	    && comp_entity->algo == RFC_1144) {
+		if (comp_entity->state) {
+			slhc_free((struct slcompress *)comp_entity->state);
+			comp_entity->state = NULL;
+		}
+		LOGP(DSNDCP, LOGL_INFO,
+		     "RFC1144 header compression terminated.\n");
+		return;
+	}
+
+	/* Just in case someone tries to terminate an unknown or unsupported
+	 * data compresson. Since everything is checked during the SNDCP
+	 * negotiation process, this should never happen! */
+	OSMO_ASSERT(false);
+}
+
+/* Compress a packet using Van Jacobson RFC1144 header compression */
+static int rfc1144_compress(uint8_t *pcomp_index, uint8_t *data,
+			    unsigned int len, struct slcompress *comp)
+{
+	uint8_t *comp_ptr;
+	int compr_len;
+	uint8_t *data_o;
+
+	/* Create a working copy of the incoming data */
+	data_o = talloc_zero_size(comp, len);
+	memcpy(data_o, data, len);
+
+	/* Run compressor */
+	compr_len = slhc_compress(comp, data, len, data_o, &comp_ptr, 0);
+
+	/* Generate pcomp_index */
+	if (data_o[0] & SL_TYPE_COMPRESSED_TCP) {
+		*pcomp_index = 2;
+		data_o[0] &= ~SL_TYPE_COMPRESSED_TCP;
+		memcpy(data, data_o, compr_len);
+	} else if ((data_o[0] & SL_TYPE_UNCOMPRESSED_TCP) ==
+		   SL_TYPE_UNCOMPRESSED_TCP) {
+		*pcomp_index = 1;
+		data_o[0] &= 0x4F;
+		memcpy(data, data_o, compr_len);
+	} else
+		*pcomp_index = 0;
+
+	talloc_free(data_o);
+	return compr_len;
+}
+
+/* Expand a packet using Van Jacobson RFC1144 header compression */
+static int rfc1144_expand(uint8_t *data, unsigned int len, uint8_t pcomp_index,
+			  struct slcompress *comp)
+{
+	int data_decompressed_len;
+	int type;
+
+	/* Note: this function should never be called with pcomp_index=0,
+	 * since this condition is already filtered
+	 * out by gprs_sndcp_pcomp_expand() */
+
+	/* Determine the data type by the PCOMP index */
+	switch (pcomp_index) {
+	case 0:
+		type = SL_TYPE_IP;
+	case 1:
+		type = SL_TYPE_UNCOMPRESSED_TCP;
+		break;
+	case 2:
+		type = SL_TYPE_COMPRESSED_TCP;
+		break;
+	default:
+		LOGP(DSNDCP, LOGL_ERROR,
+		     "rfc1144_expand() Invalid pcomp_index value (%d) detected, assuming no compression!\n",
+		     pcomp_index);
+		type = SL_TYPE_IP;
+		break;
+	}
+
+	/* Restore the original version nibble on
+	 * marked uncompressed packets */
+	if (type == SL_TYPE_UNCOMPRESSED_TCP) {
+		/* Just in case the phone tags uncompressed tcp-data
+		 * (normally this is handled by pcomp so there is
+		 * no need for tagging the data) */
+		data[0] &= 0x4F;
+		data_decompressed_len = slhc_remember(comp, data, len);
+		return data_decompressed_len;
+	}
+
+	/* Uncompress compressed packets */
+	else if (type == SL_TYPE_COMPRESSED_TCP) {
+		data_decompressed_len = slhc_uncompress(comp, data, len);
+		return data_decompressed_len;
+	}
+
+	/* Regular or unknown packets will not be touched */
+	return len;
+}
+
+/* Expand packet header */
+int gprs_sndcp_pcomp_expand(uint8_t *data, unsigned int len, uint8_t pcomp,
+			    const struct llist_head *comp_entities)
+{
+	int rc;
+	uint8_t pcomp_index = 0;
+	struct gprs_sndcp_comp *comp_entity;
+
+	OSMO_ASSERT(data);
+	OSMO_ASSERT(comp_entities);
+
+	LOGP(DSNDCP, LOGL_DEBUG,
+	     "Header compression entity list: comp_entities=%p\n",
+	     comp_entities);
+
+	LOGP(DSNDCP, LOGL_DEBUG, "Header compression mode: pcomp=%d\n", pcomp);
+
+	/* Skip on pcomp=0 */
+	if (pcomp == 0) {
+		return len;
+	}
+
+	/* Find out which compression entity handles the data */
+	comp_entity = gprs_sndcp_comp_by_comp(comp_entities, pcomp);
+
+	/* Skip compression if no suitable compression entity can be found */
+	if (!comp_entity) {
+		return len;
+	}
+
+	/* Note: Only protocol compression entities may appear in
+	 * protocol compression context */
+	OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION);
+
+	/* Note: Currently RFC1144 is the only compression method we
+	 * support, so the only allowed algorithm is RFC1144 */
+	OSMO_ASSERT(comp_entity->algo == RFC_1144);
+
+	/* Find pcomp_index */
+	pcomp_index = gprs_sndcp_comp_get_idx(comp_entity, pcomp);
+
+	/* Run decompression algo */
+	rc = rfc1144_expand(data, len, pcomp_index, comp_entity->state);
+	slhc_i_status(comp_entity->state);
+	slhc_o_status(comp_entity->state);
+
+	LOGP(DSNDCP, LOGL_DEBUG,
+	     "Header expansion done, old length=%d, new length=%d, entity=%p\n",
+	     len, rc, comp_entity);
+
+	return rc;
+}
+
+/* Compress packet header */
+int gprs_sndcp_pcomp_compress(uint8_t *data, unsigned int len, uint8_t *pcomp,
+			      const struct llist_head *comp_entities,
+			      uint8_t nsapi)
+{
+	int rc;
+	uint8_t pcomp_index = 0;
+	struct gprs_sndcp_comp *comp_entity;
+
+	OSMO_ASSERT(data);
+	OSMO_ASSERT(pcomp);
+	OSMO_ASSERT(comp_entities);
+
+	LOGP(DSNDCP, LOGL_DEBUG,
+	     "Header compression entity list: comp_entities=%p\n",
+	     comp_entities);
+
+	/* Find out which compression entity handles the data */
+	comp_entity = gprs_sndcp_comp_by_nsapi(comp_entities, nsapi);
+
+	/* Skip compression if no suitable compression entity can be found */
+	if (!comp_entity) {
+		*pcomp = 0;
+		return len;
+	}
+
+	/* Note: Only protocol compression entities may appear in
+	 * protocol compression context */
+	OSMO_ASSERT(comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION);
+
+	/* Note: Currently RFC1144 is the only compression method we
+	 * support, so the only allowed algorithm is RFC1144 */
+	OSMO_ASSERT(comp_entity->algo == RFC_1144);
+
+	/* Run compression algo */
+	rc = rfc1144_compress(&pcomp_index, data, len, comp_entity->state);
+	slhc_i_status(comp_entity->state);
+	slhc_o_status(comp_entity->state);
+
+	/* Find pcomp value */
+	*pcomp = gprs_sndcp_comp_get_comp(comp_entity, pcomp_index);
+
+	LOGP(DSNDCP, LOGL_DEBUG, "Header compression mode: pcomp=%d\n", *pcomp);
+
+	LOGP(DSNDCP, LOGL_DEBUG,
+	     "Header compression done, old length=%d, new length=%d, entity=%p\n",
+	     len, rc, comp_entity);
+	return rc;
+}