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_comp.c b/openbsc/src/gprs/gprs_sndcp_comp.c
new file mode 100644
index 0000000..1a9d030
--- /dev/null
+++ b/openbsc/src/gprs/gprs_sndcp_comp.c
@@ -0,0 +1,320 @@
+/* GPRS SNDCP header compression entity management tools */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * 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/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sndcp_xid.h>
+#include <openbsc/gprs_sndcp_comp.h>
+#include <openbsc/gprs_sndcp_pcomp.h>
+
+/* Create a new compression entity from a XID-Field */
+static struct gprs_sndcp_comp *gprs_sndcp_comp_create(const void *ctx,
+						      const struct
+						      gprs_sndcp_comp_field
+						      *comp_field)
+{
+	struct gprs_sndcp_comp *comp_entity;
+	comp_entity = talloc_zero(ctx, struct gprs_sndcp_comp);
+
+	/* Copy relevant information from the SNDCP-XID field */
+	comp_entity->entity = comp_field->entity;
+	comp_entity->comp_len = comp_field->comp_len;
+	memcpy(comp_entity->comp, comp_field->comp, sizeof(comp_entity->comp));
+
+	if (comp_field->rfc1144_params) {
+		comp_entity->nsapi_len = comp_field->rfc1144_params->nsapi_len;
+		memcpy(comp_entity->nsapi,
+		       comp_field->rfc1144_params->nsapi,
+		       sizeof(comp_entity->nsapi));
+	} else if (comp_field->rfc2507_params) {
+		comp_entity->nsapi_len = comp_field->rfc2507_params->nsapi_len;
+		memcpy(comp_entity->nsapi,
+		       comp_field->rfc2507_params->nsapi,
+		       sizeof(comp_entity->nsapi));
+	} else if (comp_field->rohc_params) {
+		comp_entity->nsapi_len = comp_field->rohc_params->nsapi_len;
+		memcpy(comp_entity->nsapi, comp_field->rohc_params->nsapi,
+		       sizeof(comp_entity->nsapi));
+	} else if (comp_field->v42bis_params) {
+		comp_entity->nsapi_len = comp_field->v42bis_params->nsapi_len;
+		memcpy(comp_entity->nsapi,
+		       comp_field->v42bis_params->nsapi,
+		       sizeof(comp_entity->nsapi));
+	} else if (comp_field->v44_params) {
+		comp_entity->nsapi_len = comp_field->v42bis_params->nsapi_len;
+		memcpy(comp_entity->nsapi,
+		       comp_field->v42bis_params->nsapi,
+		       sizeof(comp_entity->nsapi));
+	} else {
+		/* The caller is expected to check carefully if the all
+		 * data fields required for compression entity creation
+		 * are present. Otherwise we blow an assertion here */
+		OSMO_ASSERT(false);
+	}
+	comp_entity->algo = comp_field->algo;
+
+	/* Check if an NSAPI is selected, if not, it does not make sense
+	 * to create the compression entity, since the caller should
+	 * have checked the presence of the NSAPI, we blow an assertion
+	 * in case of missing NSAPIs */
+	OSMO_ASSERT(comp_entity->nsapi_len > 0);
+
+	/* Determine of which class our compression entity will be
+	 * (Protocol or Data compresson ?) */
+	comp_entity->compclass = gprs_sndcp_get_compression_class(comp_field);
+
+	OSMO_ASSERT(comp_entity->compclass != -1);
+
+	/* Create an algorithm specific compression context */
+	if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+		if (gprs_sndcp_pcomp_init(ctx, comp_entity, comp_field) != 0) {
+			talloc_free(comp_entity);
+			comp_entity = NULL;
+		}
+	} else {
+		LOGP(DSNDCP, LOGL_ERROR,
+		     "We don't support data compression yet!\n");
+		talloc_free(comp_entity);
+		return NULL;
+	}
+
+	/* Display info message */
+	if (comp_entity == NULL) {
+		LOGP(DSNDCP, LOGL_ERROR,
+		     "Header compression entity (%d) creation failed!\n",
+		     comp_entity->entity);
+		return NULL;
+	}
+	if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+		LOGP(DSNDCP, LOGL_INFO,
+		     "New header compression entity (%d) created.\n",
+		     comp_entity->entity);
+	} else {
+		LOGP(DSNDCP, LOGL_INFO,
+		     "New data compression entity (%d) created.\n",
+		     comp_entity->entity);
+	}
+
+	return comp_entity;
+}
+
+/* Allocate a compression enitiy list */
+struct llist_head *gprs_sndcp_comp_alloc(const void *ctx)
+{
+	struct llist_head *lh;
+
+	lh = talloc_zero(ctx, struct llist_head);
+	INIT_LLIST_HEAD(lh);
+
+	return lh;
+}
+
+/* Free a compression entitiy list */
+void gprs_sndcp_comp_free(struct llist_head *comp_entities)
+{
+	struct gprs_sndcp_comp *comp_entity;
+
+	/* We expect the caller to take care of allocating a
+	 * compression entity list properly. Attempting to
+	 * free a non existing list clearly points out
+	 * a malfunction. */
+	OSMO_ASSERT(comp_entities);
+
+	llist_for_each_entry(comp_entity, comp_entities, list) {
+		/* Free compression entity */
+		if (comp_entity->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+			LOGP(DSNDCP, LOGL_INFO,
+			     "Deleting header compression entity %d ...\n",
+			     comp_entity->entity);
+			gprs_sndcp_pcomp_term(comp_entity);
+		} else {
+			LOGP(DSNDCP, LOGL_INFO,
+			     "Deleting data compression entity %d ...\n",
+			     comp_entity->entity);
+		}
+	}
+
+	talloc_free(comp_entities);
+}
+
+/* Delete a compression entity */
+void gprs_sndcp_comp_delete(struct llist_head *comp_entities,
+			    unsigned int entity)
+{
+	struct gprs_sndcp_comp *comp_entity;
+	struct gprs_sndcp_comp *comp_entity_to_delete = NULL;
+
+	OSMO_ASSERT(comp_entities);
+
+	llist_for_each_entry(comp_entity, comp_entities, list) {
+		if (comp_entity->entity == entity) {
+			comp_entity_to_delete = comp_entity;
+			break;
+		}
+	}
+
+	if (!comp_entity_to_delete)
+		return;
+
+	if (comp_entity_to_delete->compclass == SNDCP_XID_PROTOCOL_COMPRESSION) {
+		LOGP(DSNDCP, LOGL_INFO,
+		     "Deleting header compression entity %d ...\n",
+		     comp_entity_to_delete->entity);
+		gprs_sndcp_pcomp_term(comp_entity_to_delete);
+	} else {
+		LOGP(DSNDCP, LOGL_INFO,
+		     "Deleting data compression entity %d ...\n",
+		     comp_entity_to_delete->entity);
+	}
+
+	/* Delete compression entity */
+	llist_del(&comp_entity_to_delete->list);
+	talloc_free(comp_entity_to_delete);
+}
+
+/* Create and Add a new compression entity
+ * (returns a pointer to the compression entity that has just been created) */
+struct gprs_sndcp_comp *gprs_sndcp_comp_add(const void *ctx,
+					    struct llist_head *comp_entities,
+					    const struct gprs_sndcp_comp_field
+					    *comp_field)
+{
+	struct gprs_sndcp_comp *comp_entity;
+
+	OSMO_ASSERT(comp_entities);
+	OSMO_ASSERT(comp_field);
+
+	/* Just to be sure, if the entity is already in
+	 * the list it will be deleted now */
+	gprs_sndcp_comp_delete(comp_entities, comp_field->entity);
+
+	/* Create and add a new entity to the list */
+	comp_entity = gprs_sndcp_comp_create(ctx, comp_field);
+
+	if (!comp_entity)
+		return NULL;
+
+	llist_add(&comp_entity->list, comp_entities);
+	return comp_entity;
+}
+
+/* Find which compression entity handles the specified pcomp/dcomp */
+struct gprs_sndcp_comp *gprs_sndcp_comp_by_comp(const struct llist_head
+						*comp_entities, uint8_t comp)
+{
+	struct gprs_sndcp_comp *comp_entity;
+	int i;
+
+	OSMO_ASSERT(comp_entities);
+
+	llist_for_each_entry(comp_entity, comp_entities, list) {
+		for (i = 0; i < comp_entity->comp_len; i++) {
+			if (comp_entity->comp[i] == comp)
+				return comp_entity;
+		}
+	}
+
+	LOGP(DSNDCP, LOGL_ERROR,
+	     "Could not find a matching compression entity for given pcomp/dcomp value %d.\n",
+	     comp);
+	return NULL;
+}
+
+/* Find which compression entity handles the specified nsapi */
+struct gprs_sndcp_comp *gprs_sndcp_comp_by_nsapi(const struct llist_head
+						 *comp_entities, uint8_t nsapi)
+{
+	struct gprs_sndcp_comp *comp_entity;
+	int i;
+
+	OSMO_ASSERT(comp_entities);
+
+	llist_for_each_entry(comp_entity, comp_entities, list) {
+		for (i = 0; i < comp_entity->nsapi_len; i++) {
+			if (comp_entity->nsapi[i] == nsapi)
+				return comp_entity;
+		}
+	}
+
+	return NULL;
+}
+
+/* Find a comp_index for a given pcomp/dcomp value */
+uint8_t gprs_sndcp_comp_get_idx(const struct gprs_sndcp_comp *comp_entity,
+				uint8_t comp)
+{
+	/* Note: This function returns a normalized version of the comp value,
+	 * which matches up with the position of the comp field. Since comp=0
+	 * is reserved for "no compression", the index value starts counting
+	 * at one. The return value is the PCOMPn/DCOMPn value one can find
+	 * in the Specification (see e.g. 3GPP TS 44.065, 6.5.3.2, Table 7) */
+
+	int i;
+	OSMO_ASSERT(comp_entity);
+
+	/* A pcomp/dcomp value of zero is reserved for "no comproession",
+	 * So we just bail and return zero in this case */
+	if (comp == 0)
+		return 0;
+
+	/* Look in the pcomp/dcomp list for the index */
+	for (i = 0; i < comp_entity->comp_len; i++) {
+		if (comp_entity->comp[i] == comp)
+			return i + 1;
+	}
+
+	LOGP(DSNDCP, LOGL_ERROR,
+	     "Could not find a matching comp_index for given pcomp/dcomp value %d\n",
+	     comp);
+	return 0;
+}
+
+/* Find a pcomp/dcomp value for a given comp_index */
+uint8_t gprs_sndcp_comp_get_comp(const struct gprs_sndcp_comp *comp_entity,
+			         uint8_t comp_index)
+{
+	OSMO_ASSERT(comp_entity);
+
+	/* A comp_index of zero translates to zero right away. */
+	if (comp_index == 0)
+		return 0;
+
+	if (comp_index > comp_entity->comp_len) {
+		LOGP(DSNDCP, LOGL_ERROR,
+		     "Could not find a matching pcomp/dcomp value for given comp_index value %d.\n",
+		     comp_index);
+		return 0;
+	}
+
+	/* Look in the pcomp/dcomp list for the comp_index, see
+	 * note in gprs_sndcp_comp_get_idx() */
+	return comp_entity->comp[comp_index - 1];
+}