Add a context mapper to map RUA ContextIDs <-> SUA Connection IDs
diff --git a/src/Makefile.am b/src/Makefile.am
index d609445..13cdac9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,7 +8,7 @@
 
 bin_PROGRAMS = hnbgw
 
-hnbgw_SOURCES = hnbap_encoder.c hnbap_decoder.c rua_encoder.c rua_decoder.c ranap_common.c rua_common.c hnbap_common.c iu_helpers.c asn1helpers.c hnbgw.c hnbgw_hnbap.c hnbgw_rua.c hnbgw_ranap.c ranap_decoder.c ranap_encoder.c ranap_msg_factory.c
+hnbgw_SOURCES = hnbap_encoder.c hnbap_decoder.c rua_encoder.c rua_decoder.c ranap_common.c rua_common.c hnbap_common.c iu_helpers.c asn1helpers.c hnbgw.c hnbgw_hnbap.c hnbgw_rua.c hnbgw_ranap.c ranap_decoder.c ranap_encoder.c ranap_msg_factory.c context_map.c
 hnbgw_LDADD = $(OSMOCORE_LIBS) $(OSMOVTY_LIBS) $(OSMOGSM_LIBS) $(ASN1C_LIBS) $(OSMOSIGTRAN_LIBS) $(COMMON_LDADD) hnbap/libosmo-asn1-hnbap.a rua/libosmo-asn1-rua.a ranap/libosmo-asn1-ranap.a
 
 BUILT_SOURCES = hnbap_decoder.c hnbap_encoder.c rua_decoder.c rua_encoder.c ranap_decoder.c ranap_encoder.c
diff --git a/src/context_map.c b/src/context_map.c
new file mode 100644
index 0000000..8fc48ba
--- /dev/null
+++ b/src/context_map.c
@@ -0,0 +1,164 @@
+/* Mapper between RUA ContextID (24 bit, per HNB) and the SUA/SCCP
+ * Connection ID (32bit, per signalling link) */
+
+/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ * 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/>.
+ *
+ */
+
+/* an expired mapping is destroyed  after 1..2 * EXPIRY_TIMER_SECS */
+#define EXPIRY_TIMER_SECS	23
+
+#include <osmocom/core/timer.h>
+
+#include "hnbgw.h"
+#include "context_map.h"
+
+/* is a given SCCP USER SAP Connection ID in use for a given CN link? */
+static int cn_id_in_use(struct hnbgw_cnlink *cn, uint32_t id)
+{
+	struct hnbgw_context_map *map;
+
+	llist_for_each_entry(map, &cn->map_list, cn_list) {
+		if (map->scu_conn_id == id)
+			return 1;
+	}
+	return 0;
+}
+
+/* try to allocate a new SCCP User SAP Connection ID */
+static int alloc_cn_conn_id(struct hnbgw_cnlink *cn, uint32_t *id_out)
+{
+	uint32_t i;
+	uint32_t id;
+
+	for (i = 0; i < 0xffffffff; i++) {
+		id = cn->next_conn_id++;
+		if (!cn_id_in_use(cn, id)) {
+			*id_out = id;
+			return 1;
+		}
+	}
+	return -1;
+}
+
+/* Map from a HNB + ContextID to the SCCP-side Connection ID */
+struct hnbgw_context_map *
+context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
+			 struct hnbgw_cnlink *cn_if_new)
+{
+	struct hnbgw_context_map *map;
+	uint32_t new_scu_conn_id;
+
+	llist_for_each_entry(map, &hnb->map_list, hnb_list) {
+		if (map->state != MAP_S_ACTIVE)
+			continue;
+		if (map->rua_ctx_id == rua_ctx_id) {
+			return map;
+		}
+	}
+
+	/* FIXME: allocated CN side ID! */
+	if (alloc_cn_conn_id(cn_if_new, &new_scu_conn_id) < 0)
+		return NULL;
+
+	/* alloate a new map entry */
+	map = talloc_zero(hnb, struct hnbgw_context_map);
+	map->state = MAP_S_NULL;
+	map->cn_link = cn_if_new;
+	map->hnb_ctx = hnb;
+	map->rua_ctx_id = rua_ctx_id;
+
+	/* put it into both lists */
+	llist_add_tail(&map->hnb_list, &hnb->map_list);
+	llist_add_tail(&map->cn_list, &cn_if_new->map_list);
+	map->state = MAP_S_ACTIVE;
+
+	return map;
+}
+
+/* Map from a CN + Connection ID to HNB + Context ID */
+struct hnbgw_context_map *
+context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
+{
+	struct hnbgw_context_map *map;
+
+	/* FIXME: allocated HNB side ID! */
+
+	llist_for_each_entry(map, &cn->map_list, cn_list) {
+		if (map->state != MAP_S_ACTIVE)
+			continue;
+		if (map->scu_conn_id == scu_conn_id) {
+			return map;
+		}
+	}
+	/* we don't allocate new mappings in the CN->HNB
+	 * direction, as the RUA=SCCP=SUA connections are always
+	 * established from HNB towards CN. */ 
+	return NULL;
+}
+
+void context_map_deactivate(struct hnbgw_context_map *map)
+{
+	/* set the state to reserved. We still show up in the list and
+	 * avoid re-allocation of the context-id until we are cleaned up
+	 * by the context_map garbage collector timer */
+
+	if (map->state != MAP_S_RESERVED2)
+		map->state = MAP_S_RESERVED1;
+}
+
+static struct osmo_timer_list context_map_tmr;
+
+static void context_map_tmr_cb(void *data)
+{
+	struct hnb_gw *gw = data;
+	struct hnbgw_cnlink *cn;
+
+	/* iterate over list of core network (links) */
+	llist_for_each_entry(cn, &gw->cn_list, list) {
+		struct hnbgw_context_map *map;
+
+		llist_for_each_entry(map, &cn->map_list, cn_list) {
+			switch (map->state) {
+			case MAP_S_RESERVED1:
+				/* first time we see this reserved
+				 * entry: mark it for stage 2 */
+				map->state = MAP_S_RESERVED2;
+				break;
+			case MAP_S_RESERVED2:
+				/* first time we see this reserved
+				 * entry: remove it */
+				map->state = MAP_S_NULL;
+				llist_del(&map->cn_list);
+				llist_del(&map->hnb_list);
+				talloc_free(map);
+				break;
+			default:
+				break;
+			}
+		}
+	}
+	/* re-schedule this timer */
+	osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
+}
+
+int context_map_init(struct hnb_gw *gw)
+{
+	context_map_tmr.cb = context_map_tmr_cb;
+	context_map_tmr.data = gw;
+	osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
+}
diff --git a/src/context_map.h b/src/context_map.h
new file mode 100644
index 0000000..c1a4495
--- /dev/null
+++ b/src/context_map.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+
+enum hnbgw_context_map_state {
+	MAP_S_NULL,
+	MAP_S_ACTIVE,		/* currently active map */
+	MAP_S_RESERVED1,	/* just disconnected, still resrved */
+	MAP_S_RESERVED2,	/* still reserved */
+};
+
+struct hnb_context;
+struct hnbgw_cnlink;
+
+struct hnbgw_context_map {
+	/* entry in the per-CN list of mappings */
+	struct llist_head cn_list;
+	/* entry in the per-HNB list of mappings */
+	struct llist_head hnb_list;
+	/* pointer to HNB */
+	struct hnb_context *hnb_ctx;
+	/* pointer to CN */
+	struct hnbgw_cnlink *cn_link;
+	/* RUA contxt ID */
+	uint32_t rua_ctx_id;
+	/* SCCP User SAP connection ID */
+	uint32_t scu_conn_id;
+
+	enum hnbgw_context_map_state state;
+};
+
+
+struct hnbgw_context_map *
+context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
+			 struct hnbgw_cnlink *cn_if_new);
+
+struct hnbgw_context_map *
+context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id);
+
+void context_map_deactivate(struct hnbgw_context_map *map);
+
+int context_map_init(struct hnb_gw *gw);
diff --git a/src/hnbgw.c b/src/hnbgw.c
index 0dbe37d..bc8be99 100644
--- a/src/hnbgw.c
+++ b/src/hnbgw.c
@@ -53,6 +53,7 @@
 #include "hnbgw.h"
 #include "hnbgw_hnbap.h"
 #include "hnbgw_rua.h"
+#include "context_map.h"
 
 static void *tall_hnb_ctx;
 static void *tall_ue_ctx;
@@ -210,6 +211,47 @@
 	return rc;
 }
 
+struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, int new_fd)
+{
+	struct hnb_context *ctx;
+
+	ctx = talloc_zero(tall_hnb_ctx, struct hnb_context);
+	if (!ctx)
+		return NULL;
+
+	ctx->gw = gw;
+	osmo_wqueue_init(&ctx->wqueue, 16);
+	ctx->wqueue.bfd.data = ctx;
+	ctx->wqueue.bfd.fd = new_fd;
+	ctx->wqueue.bfd.when = BSC_FD_READ;
+	ctx->wqueue.read_cb = hnb_read_cb;
+	ctx->wqueue.write_cb = hnb_write_cb;
+	osmo_fd_register(&ctx->wqueue.bfd);
+
+	llist_add_tail(&ctx->list, &gw->hnb_list);
+}
+
+void hnb_context_release(struct hnb_context *ctx)
+{
+	struct hnbgw_context_map *map, *map2;
+
+	/* remove from the list of HNB contexts */
+	llist_del(&ctx->list);
+
+	/* deactivate all context maps */
+	llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) {
+		/* remove it from list, as HNB context will soon be
+		 * gone.  Let's hope the seccond osmo_llist_del in the
+		 * map garbage collector wors fine? */
+		llist_del(&map->hnb_list);
+		context_map_deactivate(map);
+	}
+	/* FIXME: flush write queue items */
+	osmo_fd_unregister(&ctx->wqueue.bfd);
+
+	talloc_free(ctx);
+}
+
 /*! call-back when the listen FD has something to read */
 static int listen_fd_cb(struct osmo_fd *fd, unsigned int what)
 {
@@ -226,21 +268,10 @@
 
 	LOGP(DMAIN, LOGL_INFO, "SCTP Connection accept()ed\n");
 
-	ctx = talloc_zero(tall_hnb_ctx, struct hnb_context);
+	ctx = hnb_context_alloc(gw, new_fd);
 	if (!ctx)
 		return -ENOMEM;
 
-	ctx->gw = gw;
-	osmo_wqueue_init(&ctx->wqueue, 16);
-	ctx->wqueue.bfd.data = ctx;
-	ctx->wqueue.bfd.fd = new_fd;
-	ctx->wqueue.bfd.when = BSC_FD_READ;
-	ctx->wqueue.read_cb = hnb_read_cb;
-	ctx->wqueue.write_cb = hnb_write_cb;
-	osmo_fd_register(&ctx->wqueue.bfd);
-
-	llist_add_tail(&ctx->list, &gw->hnb_list);
-
 	return 0;
 }
 
@@ -380,6 +411,8 @@
 	INIT_LLIST_HEAD(&g_hnb_gw.hnb_list);
 	INIT_LLIST_HEAD(&g_hnb_gw.ue_list);
 
+	context_map_init(&g_hnb_gw);
+
 	rc = osmo_init_logging(&hnbgw_log_info);
 	if (rc < 0)
 		exit(1);
diff --git a/src/hnbgw.h b/src/hnbgw.h
index fede5ee..0509415 100644
--- a/src/hnbgw.h
+++ b/src/hnbgw.h
@@ -3,10 +3,13 @@
 #include <osmocom/core/select.h>
 #include <osmocom/core/linuxlist.h>
 #include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/sigtran/sccp_sap.h>
 
 #define DEBUG
 #include <osmocom/core/logging.h>
 
+
 enum {
 	DMAIN,
 	DHNBAP,
@@ -39,6 +42,37 @@
 
 struct hnb_gw;
 
+enum hnbgw_cnlink_state {
+	/* we have just been initialized or were disconnected */
+	CNLINK_S_NULL,
+	/* establishment of the SUA/SCCP link is pending */
+	CNLINK_S_EST_PEND,
+	/* establishment of the SUA/SCCP link was confirmed */
+	CNLINK_S_EST_CONF,
+	/* we have esnt the RANAP RESET and wait for the ACK */
+	CNLINK_S_EST_RST_TX_WAIT_ACK,
+	/* we have received the RANAP RESET ACK and are active */
+	CNLINK_S_EST_ACTIVE,
+};
+
+struct hnbgw_cnlink {
+	struct llist_head list;
+	enum hnbgw_cnlink_state state;
+	struct hnb_gw *gw;
+	/* are we a PS connection (1) or CS (0) */
+	int is_ps;
+	/* timer for re-transmitting the RANAP Reset */
+	struct osmo_timer_list T_RafC;
+	/* reference to the SCCP User SAP by which we communicate */
+	struct osmo_sua_link *sua_link;
+	struct osmo_sccp_addr local_addr;
+	struct osmo_sccp_addr remote_addr;
+	uint32_t next_conn_id;
+
+	/* linked list of hnbgw_context_map */
+	struct llist_head map_list;
+};
+
 struct hnb_context {
 	/*! Entry in HNB-global list of HNB */
 	struct llist_head list;
@@ -55,6 +89,9 @@
 	uint16_t hnbap_stream;
 	/*! SCTP stream ID for RUA */
 	uint16_t rua_stream;
+
+	/* linked list of hnbgw_context_map */
+	struct llist_head map_list;
 };
 
 struct ue_context {
@@ -80,6 +117,7 @@
 	struct osmo_fd listen_fd;
 	struct llist_head hnb_list;
 	struct llist_head ue_list;
+	struct llist_head cn_list;
 	uint32_t next_ue_ctx_id;
 };
 
@@ -89,3 +127,6 @@
 struct ue_context *ue_context_by_imsi(const char *imsi);
 struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi);
 void ue_context_free(struct ue_context *ue);
+
+struct hnb_context *hnb_context_alloc(struct hnb_gw *gw, int new_fd);
+void hnb_context_release(struct hnb_context *ctx);