Merge commit '7638af95fd08213aef4adb3c6399975fe3621855'
diff --git a/hlrsync/hlrsync.py b/hlrsync/hlrsync.py
new file mode 100755
index 0000000..e4a4955
--- /dev/null
+++ b/hlrsync/hlrsync.py
@@ -0,0 +1,125 @@
+#!/usr/bin/python2.5
+
+from __future__ import with_statement
+
+from pysqlite2 import dbapi2 as sqlite3
+import sys
+
+hlr = sqlite3.connect(sys.argv[1])
+web = sqlite3.connect(sys.argv[2])
+
+# switch to autocommit
+hlr.isolation_level = None
+web.isolation_level = None
+
+hlr.row_factory = sqlite3.Row
+web.row_factory = sqlite3.Row
+
+with hlr:
+	hlr_subscrs = hlr.execute("""
+		SELECT * FROM Subscriber
+	""").fetchall()
+	hlr_tokens = hlr.execute("""
+		SELECT * FROM AuthToken
+	""").fetchall()
+
+with web:
+	web_tokens = web.execute("""
+		SELECT * FROM reg_tokens
+	""").fetchall()
+	web_sms = web.execute("""
+		SELECT * FROM sms_queue
+	""").fetchall()
+
+# index by subscr id
+hlr_subscrs_by_id = {}
+hlr_subscrs_by_ext = {}
+hlr_tokens_by_subscr_id = {}
+for x in hlr_subscrs:
+	hlr_subscrs_by_id[x['id']] = x
+	hlr_subscrs_by_ext[x['extension']] = x
+del hlr_subscrs
+for x in hlr_tokens:
+	hlr_tokens_by_subscr_id[x['subscriber_id']] = x
+del hlr_tokens
+
+web_tokens_by_subscr_id = {}
+for x in web_tokens:
+	web_tokens_by_subscr_id[x['subscriber_id']] = x
+del web_tokens
+
+# remove leftover web_tokens and correct inconsistent fields
+with web:
+	for x in web_tokens_by_subscr_id.values():
+		subscr = hlr_subscrs_by_id.get(x['subscriber_id'], None)
+		if subscr is None:
+			web.execute("""
+				      DELETE FROM reg_tokens WHERE subscriber_id = ?
+				   """, (x['subscriber_id'],))
+			del web_tokens_by_subscr_id[x['subscriber_id']]
+			continue
+		if str(x['imsi']) != str(subscr['imsi']) or \
+		   x['extension'] != subscr['extension'] or \
+		   x['tmsi'] != subscr['tmsi'] or \
+		   x['lac'] != subscr['lac']:
+			web.execute("""
+				      UPDATE reg_tokens
+				      SET imsi = ?, extension = ?, tmsi = ?, lac = ?
+				      WHERE subscriber_id = ?
+				   """, (str(subscr['imsi']), subscr['extension'],
+				   subscr['tmsi'], subscr['lac'], x['subscriber_id']))
+
+# add missing web_tokens
+with web:
+	for x in hlr_tokens_by_subscr_id.values():
+		subscr = hlr_subscrs_by_id.get(x['subscriber_id'], None)
+		if subscr is None:
+			hlr.execute("""
+				      DELETE FROM AuthToken WHERE subscriber_id = ?
+				   """, (x['subscriber_id'],))
+			del hlr_tokens_by_subscr_id[x['subscriber_id']]
+			continue
+		webtoken = web_tokens_by_subscr_id.get(x['subscriber_id'], None)
+		if webtoken is None:
+			web.execute("""
+				      INSERT INTO reg_tokens
+				      (subscriber_id, extension, reg_completed, name, email, lac, imsi, token, tmsi)
+				      VALUES
+				      (?, ?, 0, ?, '', ?, ?, ?, ?)
+				   """, (x['subscriber_id'], subscr['extension'], subscr['name'],
+				   subscr['lac'], str(subscr['imsi']), x['token'], subscr['tmsi']))
+
+# authorize subscribers
+with hlr:
+	for x in web_tokens_by_subscr_id.values():
+		subscr = hlr_subscrs_by_id.get(x['subscriber_id'], None)
+		if x['reg_completed'] and not subscr['authorized']:
+			hlr.execute("""
+				      UPDATE Subscriber
+				      SET authorized = 1
+				      WHERE id = ?
+				   """, (x['subscriber_id'],))
+
+# Sync SMS from web to hlr
+with hlr:
+	for sms in web_sms:
+		subscr = hlr_subscrs_by_ext.get(sms['receiver_ext'])
+		if subscr is None:
+			print '%s not found' % sms['receiver_ext']
+			continue
+		hlr.execute("""
+				      INSERT INTO SMS
+				      (created, sender_id, receiver_id, reply_path_req, status_rep_req, protocol_id, data_coding_scheme, ud_hdr_ind, text)
+				      VALUES
+				      (?, 1, ?, 0, 0, 0, 0, 0, ?)
+				   """, (sms['created'], subscr['id'], sms['text']))
+with web:
+	for sms in web_sms:
+		web.execute("""
+				      DELETE FROM sms_queue WHERE id = ?
+				   """, (sms['id'],))
+
+
+hlr.close()
+web.close()
+
diff --git a/.gitignore b/libosmocore/.gitignore
similarity index 100%
rename from .gitignore
rename to libosmocore/.gitignore
diff --git a/COPYING b/libosmocore/COPYING
similarity index 100%
rename from COPYING
rename to libosmocore/COPYING
diff --git a/Makefile.am b/libosmocore/Makefile.am
similarity index 100%
rename from Makefile.am
rename to libosmocore/Makefile.am
diff --git a/configure.in b/libosmocore/configure.in
similarity index 100%
rename from configure.in
rename to libosmocore/configure.in
diff --git a/git-version-gen b/libosmocore/git-version-gen
similarity index 100%
rename from git-version-gen
rename to libosmocore/git-version-gen
diff --git a/include/Makefile.am b/libosmocore/include/Makefile.am
similarity index 100%
rename from include/Makefile.am
rename to libosmocore/include/Makefile.am
diff --git a/include/osmocore/Makefile.am b/libosmocore/include/osmocore/Makefile.am
similarity index 100%
rename from include/osmocore/Makefile.am
rename to libosmocore/include/osmocore/Makefile.am
diff --git a/include/osmocore/bitvec.h b/libosmocore/include/osmocore/bitvec.h
similarity index 100%
rename from include/osmocore/bitvec.h
rename to libosmocore/include/osmocore/bitvec.h
diff --git a/include/osmocore/comp128.h b/libosmocore/include/osmocore/comp128.h
similarity index 100%
rename from include/osmocore/comp128.h
rename to libosmocore/include/osmocore/comp128.h
diff --git a/include/osmocore/gsm0808.h b/libosmocore/include/osmocore/gsm0808.h
similarity index 100%
rename from include/osmocore/gsm0808.h
rename to libosmocore/include/osmocore/gsm0808.h
diff --git a/include/osmocore/gsm48.h b/libosmocore/include/osmocore/gsm48.h
similarity index 100%
rename from include/osmocore/gsm48.h
rename to libosmocore/include/osmocore/gsm48.h
diff --git a/include/osmocore/gsm48_ie.h b/libosmocore/include/osmocore/gsm48_ie.h
similarity index 100%
rename from include/osmocore/gsm48_ie.h
rename to libosmocore/include/osmocore/gsm48_ie.h
diff --git a/include/osmocore/gsm_utils.h b/libosmocore/include/osmocore/gsm_utils.h
similarity index 100%
rename from include/osmocore/gsm_utils.h
rename to libosmocore/include/osmocore/gsm_utils.h
diff --git a/include/osmocore/gsmtap.h b/libosmocore/include/osmocore/gsmtap.h
similarity index 100%
rename from include/osmocore/gsmtap.h
rename to libosmocore/include/osmocore/gsmtap.h
diff --git a/include/osmocore/linuxlist.h b/libosmocore/include/osmocore/linuxlist.h
similarity index 100%
rename from include/osmocore/linuxlist.h
rename to libosmocore/include/osmocore/linuxlist.h
diff --git a/include/osmocore/logging.h b/libosmocore/include/osmocore/logging.h
similarity index 100%
rename from include/osmocore/logging.h
rename to libosmocore/include/osmocore/logging.h
diff --git a/include/osmocore/mncc.h b/libosmocore/include/osmocore/mncc.h
similarity index 100%
rename from include/osmocore/mncc.h
rename to libosmocore/include/osmocore/mncc.h
diff --git a/include/osmocore/msgb.h b/libosmocore/include/osmocore/msgb.h
similarity index 100%
rename from include/osmocore/msgb.h
rename to libosmocore/include/osmocore/msgb.h
diff --git a/include/osmocore/protocol/Makefile.am b/libosmocore/include/osmocore/protocol/Makefile.am
similarity index 100%
rename from include/osmocore/protocol/Makefile.am
rename to libosmocore/include/osmocore/protocol/Makefile.am
diff --git a/include/osmocore/protocol/gsm_04_08.h b/libosmocore/include/osmocore/protocol/gsm_04_08.h
similarity index 100%
rename from include/osmocore/protocol/gsm_04_08.h
rename to libosmocore/include/osmocore/protocol/gsm_04_08.h
diff --git a/include/osmocore/protocol/gsm_04_11.h b/libosmocore/include/osmocore/protocol/gsm_04_11.h
similarity index 100%
rename from include/osmocore/protocol/gsm_04_11.h
rename to libosmocore/include/osmocore/protocol/gsm_04_11.h
diff --git a/include/osmocore/protocol/gsm_04_80.h b/libosmocore/include/osmocore/protocol/gsm_04_80.h
similarity index 100%
rename from include/osmocore/protocol/gsm_04_80.h
rename to libosmocore/include/osmocore/protocol/gsm_04_80.h
diff --git a/include/osmocore/protocol/gsm_08_08.h b/libosmocore/include/osmocore/protocol/gsm_08_08.h
similarity index 100%
rename from include/osmocore/protocol/gsm_08_08.h
rename to libosmocore/include/osmocore/protocol/gsm_08_08.h
diff --git a/include/osmocore/protocol/gsm_08_58.h b/libosmocore/include/osmocore/protocol/gsm_08_58.h
similarity index 100%
rename from include/osmocore/protocol/gsm_08_58.h
rename to libosmocore/include/osmocore/protocol/gsm_08_58.h
diff --git a/include/osmocore/protocol/gsm_12_21.h b/libosmocore/include/osmocore/protocol/gsm_12_21.h
similarity index 100%
rename from include/osmocore/protocol/gsm_12_21.h
rename to libosmocore/include/osmocore/protocol/gsm_12_21.h
diff --git a/include/osmocore/rsl.h b/libosmocore/include/osmocore/rsl.h
similarity index 100%
rename from include/osmocore/rsl.h
rename to libosmocore/include/osmocore/rsl.h
diff --git a/include/osmocore/rxlev_stat.h b/libosmocore/include/osmocore/rxlev_stat.h
similarity index 100%
rename from include/osmocore/rxlev_stat.h
rename to libosmocore/include/osmocore/rxlev_stat.h
diff --git a/include/osmocore/select.h b/libosmocore/include/osmocore/select.h
similarity index 100%
rename from include/osmocore/select.h
rename to libosmocore/include/osmocore/select.h
diff --git a/include/osmocore/signal.h b/libosmocore/include/osmocore/signal.h
similarity index 100%
rename from include/osmocore/signal.h
rename to libosmocore/include/osmocore/signal.h
diff --git a/include/osmocore/statistics.h b/libosmocore/include/osmocore/statistics.h
similarity index 100%
rename from include/osmocore/statistics.h
rename to libosmocore/include/osmocore/statistics.h
diff --git a/include/osmocore/talloc.h b/libosmocore/include/osmocore/talloc.h
similarity index 100%
rename from include/osmocore/talloc.h
rename to libosmocore/include/osmocore/talloc.h
diff --git a/include/osmocore/timer.h b/libosmocore/include/osmocore/timer.h
similarity index 100%
rename from include/osmocore/timer.h
rename to libosmocore/include/osmocore/timer.h
diff --git a/include/osmocore/tlv.h b/libosmocore/include/osmocore/tlv.h
similarity index 100%
rename from include/osmocore/tlv.h
rename to libosmocore/include/osmocore/tlv.h
diff --git a/include/osmocore/utils.h b/libosmocore/include/osmocore/utils.h
similarity index 100%
rename from include/osmocore/utils.h
rename to libosmocore/include/osmocore/utils.h
diff --git a/include/osmocore/write_queue.h b/libosmocore/include/osmocore/write_queue.h
similarity index 100%
rename from include/osmocore/write_queue.h
rename to libosmocore/include/osmocore/write_queue.h
diff --git a/libosmocore.pc.in b/libosmocore/libosmocore.pc.in
similarity index 100%
rename from libosmocore.pc.in
rename to libosmocore/libosmocore.pc.in
diff --git a/m4/DUMMY b/libosmocore/m4/DUMMY
similarity index 100%
rename from m4/DUMMY
rename to libosmocore/m4/DUMMY
diff --git a/src/Makefile.am b/libosmocore/src/Makefile.am
similarity index 100%
rename from src/Makefile.am
rename to libosmocore/src/Makefile.am
diff --git a/src/bitvec.c b/libosmocore/src/bitvec.c
similarity index 100%
rename from src/bitvec.c
rename to libosmocore/src/bitvec.c
diff --git a/src/comp128.c b/libosmocore/src/comp128.c
similarity index 100%
rename from src/comp128.c
rename to libosmocore/src/comp128.c
diff --git a/src/gsm0808.c b/libosmocore/src/gsm0808.c
similarity index 100%
rename from src/gsm0808.c
rename to libosmocore/src/gsm0808.c
diff --git a/src/gsm48.c b/libosmocore/src/gsm48.c
similarity index 100%
rename from src/gsm48.c
rename to libosmocore/src/gsm48.c
diff --git a/src/gsm48_ie.c b/libosmocore/src/gsm48_ie.c
similarity index 100%
rename from src/gsm48_ie.c
rename to libosmocore/src/gsm48_ie.c
diff --git a/src/gsm_utils.c b/libosmocore/src/gsm_utils.c
similarity index 100%
rename from src/gsm_utils.c
rename to libosmocore/src/gsm_utils.c
diff --git a/src/logging.c b/libosmocore/src/logging.c
similarity index 100%
rename from src/logging.c
rename to libosmocore/src/logging.c
diff --git a/src/msgb.c b/libosmocore/src/msgb.c
similarity index 100%
rename from src/msgb.c
rename to libosmocore/src/msgb.c
diff --git a/src/rsl.c b/libosmocore/src/rsl.c
similarity index 100%
rename from src/rsl.c
rename to libosmocore/src/rsl.c
diff --git a/src/rxlev_stat.c b/libosmocore/src/rxlev_stat.c
similarity index 100%
rename from src/rxlev_stat.c
rename to libosmocore/src/rxlev_stat.c
diff --git a/src/select.c b/libosmocore/src/select.c
similarity index 100%
rename from src/select.c
rename to libosmocore/src/select.c
diff --git a/src/signal.c b/libosmocore/src/signal.c
similarity index 100%
rename from src/signal.c
rename to libosmocore/src/signal.c
diff --git a/src/statistics.c b/libosmocore/src/statistics.c
similarity index 100%
rename from src/statistics.c
rename to libosmocore/src/statistics.c
diff --git a/src/talloc.c b/libosmocore/src/talloc.c
similarity index 100%
rename from src/talloc.c
rename to libosmocore/src/talloc.c
diff --git a/src/timer.c b/libosmocore/src/timer.c
similarity index 100%
rename from src/timer.c
rename to libosmocore/src/timer.c
diff --git a/src/tlv_parser.c b/libosmocore/src/tlv_parser.c
similarity index 100%
rename from src/tlv_parser.c
rename to libosmocore/src/tlv_parser.c
diff --git a/src/utils.c b/libosmocore/src/utils.c
similarity index 100%
rename from src/utils.c
rename to libosmocore/src/utils.c
diff --git a/src/write_queue.c b/libosmocore/src/write_queue.c
similarity index 100%
rename from src/write_queue.c
rename to libosmocore/src/write_queue.c
diff --git a/tests/Makefile.am b/libosmocore/tests/Makefile.am
similarity index 100%
rename from tests/Makefile.am
rename to libosmocore/tests/Makefile.am
diff --git a/tests/sms/Makefile.am b/libosmocore/tests/sms/Makefile.am
similarity index 100%
rename from tests/sms/Makefile.am
rename to libosmocore/tests/sms/Makefile.am
diff --git a/tests/sms/sms_test.c b/libosmocore/tests/sms/sms_test.c
similarity index 100%
rename from tests/sms/sms_test.c
rename to libosmocore/tests/sms/sms_test.c
diff --git a/tests/timer/Makefile.am b/libosmocore/tests/timer/Makefile.am
similarity index 100%
rename from tests/timer/Makefile.am
rename to libosmocore/tests/timer/Makefile.am
diff --git a/tests/timer/timer_test.c b/libosmocore/tests/timer/timer_test.c
similarity index 100%
rename from tests/timer/timer_test.c
rename to libosmocore/tests/timer/timer_test.c
diff --git a/linux-kernel/linux-2.6.27.4-misdn-abis.diff b/linux-kernel/linux-2.6.27.4-misdn-abis.diff
new file mode 100644
index 0000000..3691eda
--- /dev/null
+++ b/linux-kernel/linux-2.6.27.4-misdn-abis.diff
@@ -0,0 +1,144 @@
+diff -Nru --exclude-from /sunbeam/home/laforge/scripts/dontdiff linux-2.6.27.4-clean/drivers/isdn/mISDN/layer2.c linux-2.6.27.4/drivers/isdn/mISDN/layer2.c
+--- linux-2.6.27.4-clean/drivers/isdn/mISDN/layer2.c	2008-10-26 00:05:07.000000000 +0200
++++ linux-2.6.27.4/drivers/isdn/mISDN/layer2.c	2008-12-23 16:16:29.000000000 +0100
+@@ -94,8 +94,10 @@
+ 	struct layer2 *l2 = fi->userdata;
+ 	va_list va;
+ 
++#if 0
+ 	if (!(*debug & DEBUG_L2_FSM))
+ 		return;
++#endif
+ 	va_start(va, fmt);
+ 	printk(KERN_DEBUG "l2 (tei %d): ", l2->tei);
+ 	vprintk(fmt, va);
+@@ -882,6 +884,8 @@
+ 	l2->va = 0;
+ 	l2->vr = 0;
+ 	l2->sow = 0;
++	l2->sapi = skb->data[0] >> 2;
++	set_channel_address(&l2->ch, l2->sapi, l2->tei);
+ 	clear_exception(l2);
+ 	send_uframe(l2, NULL, UA | get_PollFlag(l2, skb), RSP);
+ 	mISDN_FsmChangeState(fi, ST_L2_7);
+@@ -898,6 +902,7 @@
+ 	struct layer2 *l2 = fi->userdata;
+ 	struct sk_buff *skb = arg;
+ 
++	printk(KERN_DEBUG "l2_send_UA()\n");
+ 	send_uframe(l2, skb, UA | get_PollFlag(l2, skb), RSP);
+ }
+ 
+@@ -931,6 +936,8 @@
+ 	l2->va = 0;
+ 	l2->vr = 0;
+ 	l2->sow = 0;
++	l2->sapi = skb->data[0] >> 2;
++	set_channel_address(&l2->ch, l2->sapi, l2->tei);
+ 	mISDN_FsmChangeState(fi, ST_L2_7);
+ 	stop_t200(l2, 3);
+ 	mISDN_FsmRestartTimer(&l2->t203, l2->T203, EV_L2_T203, NULL, 3);
+@@ -982,6 +989,8 @@
+ 	} else if (l2->vs != l2->va) {
+ 		skb_queue_purge(&l2->i_queue);
+ 		pr = DL_ESTABLISH_IND;
++		//l2->sapi = skb->data[0] >> 2;
++		//set_channel_address(&l2->ch, l2->sapi, l2->tei);
+ 	}
+ 	stop_t200(l2, 5);
+ 	l2->vr = 0;
+@@ -1841,11 +1850,14 @@
+ 	u_int	l;
+ 	int	c = 0;
+ 
++	printk(KERN_DEBUG "ph_data_indication 0x%x 0x%x 0x%x\n", datap[0], datap[1], datap[2]);
++
+ 	l = l2addrsize(l2);
+ 	if (skb->len <= l) {
+ 		mISDN_FsmEvent(&l2->l2m, EV_L2_FRAME_ERROR, (void *) 'N');
+ 		return ret;
+ 	}
++#if 0
+ 	if (test_bit(FLG_LAPD, &l2->flag)) { /* Maybe not needed */
+ 		psapi = *datap++;
+ 		ptei = *datap++;
+@@ -1875,6 +1887,7 @@
+ 			return 0;
+ 		}
+ 	} else
++#endif
+ 		datap += l;
+ 	if (!(*datap & 1)) {	/* I-Frame */
+ 		c = iframe_error(l2, skb);
+@@ -1890,6 +1903,7 @@
+ 			ret = mISDN_FsmEvent(&l2->l2m, EV_L2_UI, skb);
+ 	} else if (IsSABME(datap, l2)) {
+ 		c = unnum_error(l2, skb, CMD);
++		printk(KERN_DEBUG "IsSABME() returned true, unnum_error=%d\n", c);
+ 		if (!c)
+ 			ret = mISDN_FsmEvent(&l2->l2m, EV_L2_SABME, skb);
+ 	} else if (IsUA(datap)) {
+@@ -2087,7 +2101,7 @@
+ 		test_and_set_bit(FLG_LAPD, &l2->flag);
+ 		test_and_set_bit(FLG_LAPD_NET, &l2->flag);
+ 		test_and_set_bit(FLG_MOD128, &l2->flag);
+-		l2->sapi = 0;
++		l2->sapi = 62;
+ 		l2->maxlen = MAX_DFRAME_LEN;
+ 		if (test_bit(OPTION_L2_PMX, &options))
+ 			l2->window = 7;
+diff -Nru --exclude-from /sunbeam/home/laforge/scripts/dontdiff linux-2.6.27.4-clean/drivers/isdn/mISDN/tei.c linux-2.6.27.4/drivers/isdn/mISDN/tei.c
+--- linux-2.6.27.4-clean/drivers/isdn/mISDN/tei.c	2008-10-26 00:05:07.000000000 +0200
++++ linux-2.6.27.4/drivers/isdn/mISDN/tei.c	2008-12-23 16:32:59.000000000 +0100
+@@ -830,18 +830,29 @@
+ 	int		tei, ri;
+ 	struct layer2	*l2;
+ 
++	printk(KERN_DEBUG "new tei request: tei=%d\n", dp[3] >> 1);
++
+ 	ri = dp[0] << 8;
+ 	ri += dp[1];
+-	if (!mgr->up)
+-		goto denied;
+-	tei = get_free_tei(mgr);
+-	if (tei < 0) {
+-		printk(KERN_WARNING "%s:No free tei\n", __func__);
++	if (!mgr->up) {
++		printk(KERN_DEBUG "mgr->up == NULL\n");
+ 		goto denied;
+ 	}
++	if (dp[3] != 0xff) {
++		/* This is a TEI request according to 3GPP TS 08.56 6.1.11.2 */
++		tei = dp[3] >> 1;
++	} else {
++		tei = get_free_tei(mgr);
++		if (tei < 0) {
++			printk(KERN_WARNING "%s:No free tei\n", __func__);
++			goto denied;
++		}
++	}
+ 	l2 = create_new_tei(mgr, tei);
+-	if (!l2)
++	if (!l2) {
++		printk(KERN_DEBUG "create_new_tei == NULL\n");
+ 		goto denied;
++	}
+ 	else
+ 		mISDN_FsmEvent(&l2->tm->tei_m, EV_ASSIGN_REQ, dp);
+ 	return;
+@@ -1159,12 +1170,14 @@
+ 		return -ENOTCONN;
+ 	if (skb->len != 3)
+ 		return -ENOTCONN;
++#if 0
+ 	if (skb->data[0] != 0)
+ 		/* only SAPI 0 command */
+ 		return -ENOTCONN;
++#endif
+ 	if (!(skb->data[1] & 1)) /* invalid EA1 */
+ 		return -EINVAL;
+-	tei = skb->data[1] >> 0;
++	tei = skb->data[1] >> 1;
+ 	if (tei > 63) /* not a fixed tei */
+ 		return -ENOTCONN;
+ 	if ((skb->data[2] & ~0x10) != SABME)
diff --git a/linux-kernel/linux-2.6.30-hfcmulti-multibts.patch b/linux-kernel/linux-2.6.30-hfcmulti-multibts.patch
new file mode 100644
index 0000000..bb94d34
--- /dev/null
+++ b/linux-kernel/linux-2.6.30-hfcmulti-multibts.patch
@@ -0,0 +1,486 @@
+This experimental patch splits one E1 card into three virtual cards,
+
+TS 1,2,3,4,5 is card 0
+TS 6,7,8,9,10 is card 1
+TS 11,12,13,14 is card 2
+
+This allows you to run one L2 TEI handler on each of the virtual cards,
+which is required if you want to run multiple BTS on a single E1 link.
+
+Thanks to Andreas Eversberg for this patch.
+
+diff --git a/drivers/isdn/hardware/mISDN/hfc_multi.h b/drivers/isdn/hardware/mISDN/hfc_multi.h
+index 0c77386..02dd4a1 100644
+--- a/drivers/isdn/hardware/mISDN/hfc_multi.h
++++ b/drivers/isdn/hardware/mISDN/hfc_multi.h
+@@ -209,14 +209,17 @@ struct hfc_multi {
+ 	u_long		ledstate; /* save last state of leds */
+ 	int		opticalsupport; /* has the e1 board */
+ 					/* an optical Interface */
+-	int		dslot;	/* channel # of d-channel (E1) default 16 */
++
++	u_int		bmask[32]; /* bitmask of bchannels for port */
++	u_char		dnum[32]; /* array of used dchannel numbers for port */
++	u_char		created[32]; /* what port is created */
++	u_int		activity[32]; 	/* if there is any action on this */
++					/* port (will be cleared after */
++					/* showing led-states) */
+ 
+ 	u_long		wdcount; 	/* every 500 ms we need to */
+ 					/* send the watchdog a signal */
+ 	u_char		wdbyte; /* watchdog toggle byte */
+-	u_int		activity[8]; 	/* if there is any action on this */
+-					/* port (will be cleared after */
+-					/* showing led-states) */
+ 	int		e1_state; /* keep track of last state */
+ 	int		e1_getclock; /* if sync is retrieved from interface */
+ 	int		syncronized; /* keep track of existing sync interface */
+@@ -233,7 +236,6 @@ struct hfc_multi {
+ 	 * the bch->channel is equvalent to the hfc-channel
+ 	 */
+ 	struct hfc_chan	chan[32];
+-	u_char		created[8]; /* what port is created */
+ 	signed char	slot_owner[256]; /* owner channel of slot */
+ };
+ 
+diff --git a/drivers/isdn/hardware/mISDN/hfcmulti.c b/drivers/isdn/hardware/mISDN/hfcmulti.c
+index e1dab30..4fe2d27 100644
+--- a/drivers/isdn/hardware/mISDN/hfcmulti.c
++++ b/drivers/isdn/hardware/mISDN/hfcmulti.c
+@@ -1619,8 +1619,8 @@ hfcmulti_leds(struct hfc_multi *hc)
+ 		 * left red:       frame sync, but no L1
+ 		 * right green:    L2 active
+ 		 */
+-		if (hc->chan[hc->dslot].sync != 2) { /* no frame sync */
+-			if (hc->chan[hc->dslot].dch->dev.D.protocol
++		if (hc->chan[hc->dnum[0]].sync != 2) { /* no frame sync */
++			if (hc->chan[hc->dnum[0]].dch->dev.D.protocol
+ 				!= ISDN_P_NT_E1) {
+ 				led[0] = 1;
+ 				led[1] = 1;
+@@ -2428,55 +2428,56 @@ handle_timer_irq(struct hfc_multi *hc)
+ 			}
+ 		}
+ 	if (hc->ctype == HFC_TYPE_E1 && hc->created[0]) {
+-		dch = hc->chan[hc->dslot].dch;
+-		if (test_bit(HFC_CFG_REPORT_LOS, &hc->chan[hc->dslot].cfg)) {
++#warning todo: put interface parameters to hc
++		dch = hc->chan[hc->dnum[0]].dch;
++		if (test_bit(HFC_CFG_REPORT_LOS, &hc->chan[hc->dnum[0]].cfg)) {
+ 			/* LOS */
+ 			temp = HFC_inb_nodebug(hc, R_SYNC_STA) & V_SIG_LOS;
+-			if (!temp && hc->chan[hc->dslot].los)
++			if (!temp && hc->chan[hc->dnum[0]].los)
+ 				signal_state_up(dch, L1_SIGNAL_LOS_ON,
+ 				    "LOS detected");
+-			if (temp && !hc->chan[hc->dslot].los)
++			if (temp && !hc->chan[hc->dnum[0]].los)
+ 				signal_state_up(dch, L1_SIGNAL_LOS_OFF,
+ 				    "LOS gone");
+-			hc->chan[hc->dslot].los = temp;
++			hc->chan[hc->dnum[0]].los = temp;
+ 		}
+-		if (test_bit(HFC_CFG_REPORT_AIS, &hc->chan[hc->dslot].cfg)) {
++		if (test_bit(HFC_CFG_REPORT_AIS, &hc->chan[hc->dnum[0]].cfg)) {
+ 			/* AIS */
+ 			temp = HFC_inb_nodebug(hc, R_SYNC_STA) & V_AIS;
+-			if (!temp && hc->chan[hc->dslot].ais)
++			if (!temp && hc->chan[hc->dnum[0]].ais)
+ 				signal_state_up(dch, L1_SIGNAL_AIS_ON,
+ 				    "AIS detected");
+-			if (temp && !hc->chan[hc->dslot].ais)
++			if (temp && !hc->chan[hc->dnum[0]].ais)
+ 				signal_state_up(dch, L1_SIGNAL_AIS_OFF,
+ 				    "AIS gone");
+-			hc->chan[hc->dslot].ais = temp;
++			hc->chan[hc->dnum[0]].ais = temp;
+ 		}
+-		if (test_bit(HFC_CFG_REPORT_SLIP, &hc->chan[hc->dslot].cfg)) {
++		if (test_bit(HFC_CFG_REPORT_SLIP, &hc->chan[hc->dnum[0]].cfg)) {
+ 			/* SLIP */
+ 			temp = HFC_inb_nodebug(hc, R_SLIP) & V_FOSLIP_RX;
+-			if (!temp && hc->chan[hc->dslot].slip_rx)
++			if (!temp && hc->chan[hc->dnum[0]].slip_rx)
+ 				signal_state_up(dch, L1_SIGNAL_SLIP_RX,
+ 				    " bit SLIP detected RX");
+-			hc->chan[hc->dslot].slip_rx = temp;
++			hc->chan[hc->dnum[0]].slip_rx = temp;
+ 			temp = HFC_inb_nodebug(hc, R_SLIP) & V_FOSLIP_TX;
+-			if (!temp && hc->chan[hc->dslot].slip_tx)
++			if (!temp && hc->chan[hc->dnum[0]].slip_tx)
+ 				signal_state_up(dch, L1_SIGNAL_SLIP_TX,
+ 				    " bit SLIP detected TX");
+-			hc->chan[hc->dslot].slip_tx = temp;
++			hc->chan[hc->dnum[0]].slip_tx = temp;
+ 		}
+-		if (test_bit(HFC_CFG_REPORT_RDI, &hc->chan[hc->dslot].cfg)) {
++		if (test_bit(HFC_CFG_REPORT_RDI, &hc->chan[hc->dnum[0]].cfg)) {
+ 			/* RDI */
+ 			temp = HFC_inb_nodebug(hc, R_RX_SL0_0) & V_A;
+-			if (!temp && hc->chan[hc->dslot].rdi)
++			if (!temp && hc->chan[hc->dnum[0]].rdi)
+ 				signal_state_up(dch, L1_SIGNAL_RDI_ON,
+ 				    "RDI detected");
+-			if (temp && !hc->chan[hc->dslot].rdi)
++			if (temp && !hc->chan[hc->dnum[0]].rdi)
+ 				signal_state_up(dch, L1_SIGNAL_RDI_OFF,
+ 				    "RDI gone");
+-			hc->chan[hc->dslot].rdi = temp;
++			hc->chan[hc->dnum[0]].rdi = temp;
+ 		}
+ 		temp = HFC_inb_nodebug(hc, R_JATT_DIR);
+-		switch (hc->chan[hc->dslot].sync) {
++		switch (hc->chan[hc->dnum[0]].sync) {
+ 		case 0:
+ 			if ((temp & 0x60) == 0x60) {
+ 				if (debug & DEBUG_HFCMULTI_SYNC)
+@@ -2485,10 +2486,10 @@ handle_timer_irq(struct hfc_multi *hc)
+ 					    "in clock sync\n",
+ 					    __func__, hc->id);
+ 				HFC_outb(hc, R_RX_OFF,
+-				    hc->chan[hc->dslot].jitter | V_RX_INIT);
++				    hc->chan[hc->dnum[0]].jitter | V_RX_INIT);
+ 				HFC_outb(hc, R_TX_OFF,
+-				    hc->chan[hc->dslot].jitter | V_RX_INIT);
+-				hc->chan[hc->dslot].sync = 1;
++				    hc->chan[hc->dnum[0]].jitter | V_RX_INIT);
++				hc->chan[hc->dnum[0]].sync = 1;
+ 				goto check_framesync;
+ 			}
+ 			break;
+@@ -2499,7 +2500,7 @@ handle_timer_irq(struct hfc_multi *hc)
+ 					    "%s: (id=%d) E1 "
+ 					    "lost clock sync\n",
+ 					    __func__, hc->id);
+-				hc->chan[hc->dslot].sync = 0;
++				hc->chan[hc->dnum[0]].sync = 0;
+ 				break;
+ 			}
+ check_framesync:
+@@ -2510,7 +2511,7 @@ check_framesync:
+ 					    "%s: (id=%d) E1 "
+ 					    "now in frame sync\n",
+ 					    __func__, hc->id);
+-				hc->chan[hc->dslot].sync = 2;
++				hc->chan[hc->dnum[0]].sync = 2;
+ 			}
+ 			break;
+ 		case 2:
+@@ -2520,7 +2521,7 @@ check_framesync:
+ 					    "%s: (id=%d) E1 lost "
+ 					    "clock & frame sync\n",
+ 					    __func__, hc->id);
+-				hc->chan[hc->dslot].sync = 0;
++				hc->chan[hc->dnum[0]].sync = 0;
+ 				break;
+ 			}
+ 			temp = HFC_inb_nodebug(hc, R_SYNC_STA);
+@@ -2530,7 +2531,7 @@ check_framesync:
+ 					    "%s: (id=%d) E1 "
+ 					    "lost frame sync\n",
+ 					    __func__, hc->id);
+-				hc->chan[hc->dslot].sync = 1;
++				hc->chan[hc->dnum[0]].sync = 1;
+ 			}
+ 			break;
+ 		}
+@@ -2746,7 +2747,8 @@ hfcmulti_interrupt(int intno, void *dev_id)
+ 		if (r_irq_misc & V_STA_IRQ) {
+ 			if (hc->ctype == HFC_TYPE_E1) {
+ 				/* state machine */
+-				dch = hc->chan[hc->dslot].dch;
++#warning todo
++				dch = hc->chan[hc->dnum[0]].dch;
+ 				e1_syncsta = HFC_inb_nodebug(hc, R_SYNC_STA);
+ 				if (test_bit(HFC_CHIP_PLXSD, &hc->chip)
+ 				 && hc->e1_getclock) {
+@@ -2768,7 +2770,15 @@ hfcmulti_interrupt(int intno, void *dev_id)
+ 				}
+ 				dch->state = HFC_inb_nodebug(hc, R_E1_RD_STA)
+ 					& 0x7;
++#warning todo hack!!! broadcast state change!!!
++				dch = hc->chan[hc->dnum[0]].dch;
+ 				schedule_event(dch, FLG_PHCHANGE);
++				dch = hc->chan[hc->dnum[1]].dch;
++				dch->state = HFC_inb_nodebug(hc, R_E1_RD_STA)
++					& 0x7;
++				schedule_event(dch, FLG_PHCHANGE);
++
++
+ 				if (debug & DEBUG_HFCMULTI_STATE)
+ 					printk(KERN_DEBUG
+ 					    "%s: E1 (id=%d) newstate %x\n",
+@@ -3851,31 +3861,35 @@ hfcmulti_initmode(struct dchannel *dch)
+ 	if (debug & DEBUG_HFCMULTI_INIT)
+ 		printk(KERN_DEBUG "%s: entered\n", __func__);
+ 
++	i = dch->slot;
++	pt = hc->chan[i].port;
+ 	if (hc->ctype == HFC_TYPE_E1) {
+-		hc->chan[hc->dslot].slot_tx = -1;
+-		hc->chan[hc->dslot].slot_rx = -1;
+-		hc->chan[hc->dslot].conf = -1;
+-		if (hc->dslot) {
+-			mode_hfcmulti(hc, hc->dslot, dch->dev.D.protocol,
++		/* E1 */
++#warning todo: don''t do it if dnum == 0
++		hc->chan[hc->dnum[pt]].slot_tx = -1;
++		hc->chan[hc->dnum[pt]].slot_rx = -1;
++		hc->chan[hc->dnum[pt]].conf = -1;
++		if (hc->dnum[pt]) {
++			mode_hfcmulti(hc, dch->slot, dch->dev.D.protocol,
+ 				-1, 0, -1, 0);
+ 			dch->timer.function = (void *) hfcmulti_dbusy_timer;
+ 			dch->timer.data = (long) dch;
+ 			init_timer(&dch->timer);
+ 		}
+ 		for (i = 1; i <= 31; i++) {
+-			if (i == hc->dslot)
++			if (!((1 << i) & hc->bmask[pt])) /* skip unused channel */
+ 				continue;
+ 			hc->chan[i].slot_tx = -1;
+ 			hc->chan[i].slot_rx = -1;
+ 			hc->chan[i].conf = -1;
+ 			mode_hfcmulti(hc, i, ISDN_P_NONE, -1, 0, -1, 0);
+ 		}
+-		/* E1 */
+-		if (test_bit(HFC_CFG_REPORT_LOS, &hc->chan[hc->dslot].cfg)) {
++#warning todo (global)
++		if (test_bit(HFC_CFG_REPORT_LOS, &hc->chan[hc->dnum[pt]].cfg)) {
+ 			HFC_outb(hc, R_LOS0, 255); /* 2 ms */
+ 			HFC_outb(hc, R_LOS1, 255); /* 512 ms */
+ 		}
+-		if (test_bit(HFC_CFG_OPTICAL, &hc->chan[hc->dslot].cfg)) {
++		if (test_bit(HFC_CFG_OPTICAL, &hc->chan[hc->dnum[pt]].cfg)) {
+ 			HFC_outb(hc, R_RX0, 0);
+ 			hc->hw.r_tx0 = 0 | V_OUT_EN;
+ 		} else {
+@@ -3888,12 +3902,12 @@ hfcmulti_initmode(struct dchannel *dch)
+ 		HFC_outb(hc, R_TX_FR0, 0x00);
+ 		HFC_outb(hc, R_TX_FR1, 0xf8);
+ 
+-		if (test_bit(HFC_CFG_CRC4, &hc->chan[hc->dslot].cfg))
++		if (test_bit(HFC_CFG_CRC4, &hc->chan[hc->dnum[pt]].cfg))
+ 			HFC_outb(hc, R_TX_FR2, V_TX_MF | V_TX_E | V_NEG_E);
+ 
+ 		HFC_outb(hc, R_RX_FR0, V_AUTO_RESYNC | V_AUTO_RECO | 0);
+ 
+-		if (test_bit(HFC_CFG_CRC4, &hc->chan[hc->dslot].cfg))
++		if (test_bit(HFC_CFG_CRC4, &hc->chan[hc->dnum[pt]].cfg))
+ 			HFC_outb(hc, R_RX_FR1, V_RX_MF | V_RX_MF_SYNC);
+ 
+ 		if (dch->dev.D.protocol == ISDN_P_NT_E1) {
+@@ -3957,7 +3971,7 @@ hfcmulti_initmode(struct dchannel *dch)
+ 			plxsd_checksync(hc, 0);
+ 		}
+ 	} else {
+-		i = dch->slot;
++		/* ST */
+ 		hc->chan[i].slot_tx = -1;
+ 		hc->chan[i].slot_rx = -1;
+ 		hc->chan[i].conf = -1;
+@@ -3973,8 +3987,6 @@ hfcmulti_initmode(struct dchannel *dch)
+ 		hc->chan[i - 1].slot_rx = -1;
+ 		hc->chan[i - 1].conf = -1;
+ 		mode_hfcmulti(hc, i - 1, ISDN_P_NONE, -1, 0, -1, 0);
+-		/* ST */
+-		pt = hc->chan[i].port;
+ 		/* select interface */
+ 		HFC_outb(hc, R_ST_SEL, pt);
+ 		/* undocumented: delay after R_ST_SEL */
+@@ -4557,6 +4569,8 @@ release_port(struct hfc_multi *hc, struct dchannel *dch)
+ 		}
+ 		/* free channels */
+ 		for (i = 0; i <= 31; i++) {
++			if (!((1 << i) & hc->bmask[pt])) /* skip unused channel */
++				continue;
+ 			if (hc->chan[i].bch) {
+ 				if (debug & DEBUG_HFCMULTI_INIT)
+ 					printk(KERN_DEBUG
+@@ -4680,12 +4694,13 @@ release_card(struct hfc_multi *hc)
+ }
+ 
+ static int
+-init_e1_port(struct hfc_multi *hc, struct hm_map *m)
++init_e1_port(struct hfc_multi *hc, struct hm_map *m, int pt)
+ {
+ 	struct dchannel	*dch;
+ 	struct bchannel	*bch;
+ 	int		ch, ret = 0;
+ 	char		name[MISDN_MAX_IDLEN];
++	int		bcount = 0;
+ 
+ 	dch = kzalloc(sizeof(struct dchannel), GFP_KERNEL);
+ 	if (!dch)
+@@ -4698,13 +4713,12 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 	    (1 << (ISDN_P_B_HDLC & ISDN_P_B_MASK));
+ 	dch->dev.D.send = handle_dmsg;
+ 	dch->dev.D.ctrl = hfcm_dctrl;
+-	dch->dev.nrbchan = (hc->dslot) ? 30 : 31;
+-	dch->slot = hc->dslot;
+-	hc->chan[hc->dslot].dch = dch;
+-	hc->chan[hc->dslot].port = 0;
+-	hc->chan[hc->dslot].nt_timer = -1;
++	dch->slot = hc->dnum[pt];
++	hc->chan[hc->dnum[pt]].dch = dch;
++	hc->chan[hc->dnum[pt]].port = pt;
++	hc->chan[hc->dnum[pt]].nt_timer = -1;
+ 	for (ch = 1; ch <= 31; ch++) {
+-		if (ch == hc->dslot) /* skip dchannel */
++		if (!((1 << ch) & hc->bmask[pt])) /* skip unused channel */
+ 			continue;
+ 		bch = kzalloc(sizeof(struct bchannel), GFP_KERNEL);
+ 		if (!bch) {
+@@ -4733,7 +4747,10 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 		hc->chan[ch].bch = bch;
+ 		hc->chan[ch].port = 0;
+ 		set_channelmap(bch->nr, dch->dev.channelmap);
++		bcount++;
+ 	}
++	dch->dev.nrbchan = bcount;
++#warning todo: must be set globally, and must be a seperate function
+ 	/* set optical line type */
+ 	if (port[Port_cnt] & 0x001) {
+ 		if (!m->opticalsupport)  {
+@@ -4749,7 +4766,7 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 				    __func__,
+ 				    HFC_cnt + 1, 1);
+ 			test_and_set_bit(HFC_CFG_OPTICAL,
+-			    &hc->chan[hc->dslot].cfg);
++			    &hc->chan[hc->dnum[pt]].cfg);
+ 		}
+ 	}
+ 	/* set LOS report */
+@@ -4759,7 +4776,7 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 			    "LOS report: card(%d) port(%d)\n",
+ 			    __func__, HFC_cnt + 1, 1);
+ 		test_and_set_bit(HFC_CFG_REPORT_LOS,
+-		    &hc->chan[hc->dslot].cfg);
++		    &hc->chan[hc->dnum[pt]].cfg);
+ 	}
+ 	/* set AIS report */
+ 	if (port[Port_cnt] & 0x008) {
+@@ -4768,7 +4785,7 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 			    "AIS report: card(%d) port(%d)\n",
+ 			    __func__, HFC_cnt + 1, 1);
+ 		test_and_set_bit(HFC_CFG_REPORT_AIS,
+-		    &hc->chan[hc->dslot].cfg);
++		    &hc->chan[hc->dnum[pt]].cfg);
+ 	}
+ 	/* set SLIP report */
+ 	if (port[Port_cnt] & 0x010) {
+@@ -4778,7 +4795,7 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 			    "card(%d) port(%d)\n",
+ 			    __func__, HFC_cnt + 1, 1);
+ 		test_and_set_bit(HFC_CFG_REPORT_SLIP,
+-		    &hc->chan[hc->dslot].cfg);
++		    &hc->chan[hc->dnum[pt]].cfg);
+ 	}
+ 	/* set RDI report */
+ 	if (port[Port_cnt] & 0x020) {
+@@ -4788,7 +4805,7 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 			    "card(%d) port(%d)\n",
+ 			    __func__, HFC_cnt + 1, 1);
+ 		test_and_set_bit(HFC_CFG_REPORT_RDI,
+-		    &hc->chan[hc->dslot].cfg);
++		    &hc->chan[hc->dnum[pt]].cfg);
+ 	}
+ 	/* set CRC-4 Mode */
+ 	if (!(port[Port_cnt] & 0x100)) {
+@@ -4797,7 +4814,7 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 				" card(%d) port(%d)\n",
+ 				__func__, HFC_cnt + 1, 1);
+ 		test_and_set_bit(HFC_CFG_CRC4,
+-		    &hc->chan[hc->dslot].cfg);
++		    &hc->chan[hc->dnum[pt]].cfg);
+ 	} else {
+ 		if (debug & DEBUG_HFCMULTI_INIT)
+ 			printk(KERN_DEBUG "%s: PORT turn off CRC4"
+@@ -4829,20 +4846,23 @@ init_e1_port(struct hfc_multi *hc, struct hm_map *m)
+ 	}
+ 	/* set elastic jitter buffer */
+ 	if (port[Port_cnt] & 0x3000) {
+-		hc->chan[hc->dslot].jitter = (port[Port_cnt]>>12) & 0x3;
++		hc->chan[hc->dnum[pt]].jitter = (port[Port_cnt]>>12) & 0x3;
+ 		if (debug & DEBUG_HFCMULTI_INIT)
+ 			printk(KERN_DEBUG
+ 			    "%s: PORT set elastic "
+ 			    "buffer to %d: card(%d) port(%d)\n",
+-			    __func__, hc->chan[hc->dslot].jitter,
++			    __func__, hc->chan[hc->dnum[pt]].jitter,
+ 			    HFC_cnt + 1, 1);
+ 	} else
+-		hc->chan[hc->dslot].jitter = 2; /* default */
+-	snprintf(name, MISDN_MAX_IDLEN - 1, "hfc-e1.%d", HFC_cnt + 1);
++		hc->chan[hc->dnum[pt]].jitter = 2; /* default */
++	if (hc->ports > 1)
++		snprintf(name, MISDN_MAX_IDLEN - 1, "hfc-e1.%d-%d", HFC_cnt + 1, pt+1);
++	else
++		snprintf(name, MISDN_MAX_IDLEN - 1, "hfc-e1.%d", HFC_cnt + 1);
+ 	ret = mISDN_register_device(&dch->dev, &hc->pci_dev->dev, name);
+ 	if (ret)
+ 		goto free_chan;
+-	hc->created[0] = 1;
++	hc->created[pt] = 1;
+ 	return ret;
+ free_chan:
+ 	release_port(hc, dch);
+@@ -5009,18 +5029,30 @@ hfcmulti_init(struct hm_map *m, struct pci_dev *pdev,
+ 	hc->id = HFC_cnt;
+ 	hc->pcm = pcm[HFC_cnt];
+ 	hc->io_mode = iomode[HFC_cnt];
++#warning todo: rework module parameters for customizing e1 fragments.... yea, let''s call it: fragments
+ 	if (dslot[HFC_cnt] < 0 && hc->ctype == HFC_TYPE_E1) {
+-		hc->dslot = 0;
++		hc->dnum[0] = 0;
+ 		printk(KERN_INFO "HFC-E1 card has disabled D-channel, but "
+ 			"31 B-channels\n");
+ 	}
+ 	if (dslot[HFC_cnt] > 0 && dslot[HFC_cnt] < 32
+ 	    && hc->ctype == HFC_TYPE_E1) {
+-		hc->dslot = dslot[HFC_cnt];
++		hc->dnum[0] = dslot[HFC_cnt];
+ 		printk(KERN_INFO "HFC-E1 card has alternating D-channel on "
+ 			"time slot %d\n", dslot[HFC_cnt]);
+ 	} else
+-		hc->dslot = 16;
++		hc->dnum[0] = 16;
++
++#warning todo HACK!!! just a small map of two "fragments"
++	if (hc->ctype == HFC_TYPE_E1) {
++		hc->dnum[0] = 1;
++		hc->bmask[0] = 0x0000003c;
++		hc->dnum[1] = 6;
++		hc->bmask[1] = 0x00000780;
++		hc->dnum[2] = 11;
++		hc->bmask[2] = 0x00007800;
++		hc->ports = 3;
++	}
+ 
+ 	/* set chip specific features */
+ 	hc->masterclk = -1;
+@@ -5103,7 +5135,7 @@ hfcmulti_init(struct hm_map *m, struct pci_dev *pdev,
+ 			goto free_card;
+ 		}
+ 		if (hc->ctype == HFC_TYPE_E1)
+-			ret_err = init_e1_port(hc, m);
++			ret_err = init_e1_port(hc, m, pt);
+ 		else
+ 			ret_err = init_multi_port(hc, pt);
+ 		if (debug & DEBUG_HFCMULTI_INIT)
+@@ -5115,10 +5147,14 @@ hfcmulti_init(struct hm_map *m, struct pci_dev *pdev,
+ 		if (ret_err) {
+ 			while (pt) { /* release already registered ports */
+ 				pt--;
+-				release_port(hc, hc->chan[(pt << 2) + 2].dch);
++				if (hc->ctype == HFC_TYPE_E1)
++					release_port(hc, hc->chan[hc->dnum[pt]].dch);
++				else
++					release_port(hc, hc->chan[(pt << 2) + 2].dch);
+ 			}
+ 			goto free_card;
+ 		}
++#warning todo: count it right, add additional "fragment" counter...
+ 		Port_cnt++;
+ 	}
+ 
diff --git a/openbsc/.gitignore b/openbsc/.gitignore
new file mode 100644
index 0000000..f968114
--- /dev/null
+++ b/openbsc/.gitignore
@@ -0,0 +1,48 @@
+*.o
+*.a
+.deps
+Makefile
+Makefile.in
+bscconfig.h
+bscconfig.h.in
+openbsc.pc
+bsc_hack
+bsc_msc_ip
+bsc_mgcp
+*.*~
+*.sw?
+
+#configure
+aclocal.m4
+autom4te.cache/
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+stamp-h1
+
+# git-version-gen magic
+.tarball-version
+.version
+
+
+# apps and app data
+hlr.sqlite3
+bs11_config
+ipaccess-config
+ipaccess-find
+ipaccess-firmware
+ipaccess-proxy
+isdnsync
+
+#tests
+tests/channel/channel_test
+tests/db/db_test
+tests/debug/debug_test
+tests/gsm0408/gsm0408_test
+tests/sccp/sccp_test
+tests/sms/sms_test
+tests/timer/timer_test
+
diff --git a/openbsc/AUTHORS b/openbsc/AUTHORS
new file mode 100644
index 0000000..daf60e4
--- /dev/null
+++ b/openbsc/AUTHORS
@@ -0,0 +1,7 @@
+Harald Welte <laforge@gnumonks.org>
+Holger Freyther <zecke@selfish.org>
+Jan Luebbe <jluebbe@debian.org>
+Stefan Schmidt <stefan@datenfreihafen.org>
+Daniel Willmann <daniel@totalueberwachung.de>
+Andreas Eversberg <Andreas.Eversberg@versatel.de>
+Sylvain Munaut <246tnt@gmail.com>
diff --git a/COPYING b/openbsc/COPYING
similarity index 100%
copy from COPYING
copy to openbsc/COPYING
diff --git a/openbsc/Makefile.am b/openbsc/Makefile.am
new file mode 100644
index 0000000..4ce11ce
--- /dev/null
+++ b/openbsc/Makefile.am
@@ -0,0 +1,13 @@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+SUBDIRS = include src tests
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = openbsc.pc libsccp.pc
+
+BUILT_SOURCES = $(top_srcdir)/.version
+$(top_srcdir)/.version:
+	echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+	echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/openbsc/README b/openbsc/README
new file mode 100644
index 0000000..fa01ff4
--- /dev/null
+++ b/openbsc/README
@@ -0,0 +1,32 @@
+About OpenBSC
+=============
+
+OpenBSC is a minimalistic implementation of the GSM Network, with
+particular emphasis on the functionality typically provided by the BSC,
+MSC, HLR, VLR and SMSC.
+
+Its currently supported interfaces towards the BTS are:
+
+ * Classic A-bis over E1 using a mISDN based E1 interface. In other
+   words, you can connect existing GSM Base Transceiver Station (BTS)
+   through E1 to OpenBSC.  So far, we have only tested the Siemens BS-11
+   Test reports with other BTS are much appreciated!
+
+ * A-bis over IP as used by the ip.access nanoBTS product family
+
+You can find the project documentation at http://openbsc.gnumonks.org/
+
+This project is still in its early days, and there are lots of areas where it
+doesn't behave as per GSM spec.
+
+	Harald Welte <laforge@gnumonks.org>
+
+
+libosmocore
+===========
+
+Please note that as of March 2010, OpenBSC has a dependency to a library
+called "libosmocore".  You can obtain that library from
+
+	git://git.osmocom.org/libosmocore.git
+
diff --git a/openbsc/configure.in b/openbsc/configure.in
new file mode 100644
index 0000000..66d4ee1
--- /dev/null
+++ b/openbsc/configure.in
@@ -0,0 +1,59 @@
+dnl Process this file with autoconf to produce a configure script
+AC_INIT([openbsc],
+	m4_esyscmd([./git-version-gen .tarball-version]),
+	[openbsc-devel@lists.openbsc.org])
+
+AM_INIT_AUTOMAKE([dist-bzip2])
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_RANLIB
+
+dnl checks for libraries
+AC_SEARCH_LIBS(crypt, crypt,
+    [LIBCRYPT="-lcrypt"; AC_DEFINE([VTY_CRYPT_PW], [], [Use crypt functionality of vty.])])
+
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.1.6)
+
+dnl checks for header files
+AC_HEADER_STDC
+
+dnl Checks for typedefs, structures and compiler characteristics
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
+AC_COMPILE_IFELSE([char foo;],
+      [ AC_MSG_RESULT([yes])
+        SYMBOL_VISIBILITY="-fvisibility=hidden"],
+        AC_MSG_RESULT([no]))
+CFLAGS="$saved_CFLAGS"
+AC_SUBST(SYMBOL_VISIBILITY)
+
+
+dnl Generate the output
+AM_CONFIG_HEADER(bscconfig.h)
+
+AC_OUTPUT(
+    openbsc.pc
+    libsccp.pc
+    include/openbsc/Makefile
+    include/vty/Makefile
+    include/sccp/Makefile
+    include/Makefile
+    src/Makefile
+    src/ipaccess/Makefile
+    src/gprs/Makefile
+    tests/Makefile
+    tests/debug/Makefile
+    tests/gsm0408/Makefile
+    tests/db/Makefile
+    tests/channel/Makefile
+    tests/sccp/Makefile
+    Makefile)
diff --git a/openbsc/contrib/bt.py b/openbsc/contrib/bt.py
new file mode 100755
index 0000000..1b111ef
--- /dev/null
+++ b/openbsc/contrib/bt.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+import os
+
+f = open("unbalanced")
+lines = []
+for line in f:
+    lines.append(line)
+
+filenames = {}
+
+output = []
+for line in lines:
+    if "[0x" in line:
+        start = line.find("[")
+        end = line.find("]")
+        addr = line[start+1:end]
+        try:
+            file = filenames[addr]
+        except KeyError:
+            r = os.popen("addr2line -fs -e ./bsc_hack %s" % addr)
+            all = r.read().replace("\n", ",")
+            file = all
+            filenames[addr] = file
+
+        line = line.replace(addr, file)
+    output.append(line)
+
+g = open("unbalanced.2", "w")
+g.write("".join(output))
+
+
+
diff --git a/openbsc/contrib/convert_to_enum.py b/openbsc/contrib/convert_to_enum.py
new file mode 100755
index 0000000..bcd6f2c
--- /dev/null
+++ b/openbsc/contrib/convert_to_enum.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+#
+# Convert ETSI documents to an enum
+#
+
+import re, sys
+
+def convert(string):
+    string = string.strip().replace(" ", "").rjust(8, "0")
+    var = 0
+    offset = 7
+    for char in string:
+        assert offset >= 0
+        var = var | (int(char) << offset)
+        offset = offset - 1
+
+    return var
+
+def string(name):
+    name = name.replace(" ", "_")
+    name = name.replace('"', "")
+    name = name.replace('/', '_')
+    name = name.replace('(', '_')
+    name = name.replace(')', '_')
+    return "%s_%s" % (sys.argv[2], name.upper())
+
+file = open(sys.argv[1])
+
+
+for line in file:
+    m = re.match(r"[ \t]*(?P<value>[01 ]+)[ ]+(?P<name>[a-zA-Z /0-9()]+)", line[:-1])
+
+    if m:
+        print "\t%s\t\t= %d," % (string(m.groupdict()["name"]), convert(m.groupdict()["value"]))
+    else:
+        print line[:-1]
diff --git a/openbsc/contrib/mgcp_server.py b/openbsc/contrib/mgcp_server.py
new file mode 100755
index 0000000..05c489d
--- /dev/null
+++ b/openbsc/contrib/mgcp_server.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# Simple server for mgcp... send audit, receive response..
+
+import socket, time
+
+MGCP_GATEWAY_PORT = 2427
+MGCP_CALLAGENT_PORT = 2727
+
+rsip_resp = """200 321321332\r\n"""
+audit_packet = """AUEP %d 13@mgw MGCP 1.0\r\n"""
+crcx_packet = """CRCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nL: p:20, a:GSM-EFR, nt:IN\r\nM: recvonly\r\n"""
+dlcx_packet = """DLCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nI: %d\r\n"""
+mdcx_packet = """MDCX %d 14@mgw MGCP 1.0\r\nC: 4a84ad5d25f\r\nI: %d\r\nL: p:20, a:GSM-EFR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 258696477 0 IN IP4 172.16.1.107\r\ns=-\r\nc=IN IP4 172.16.1.107\r\nt=0 0\r\nm=audio 6666 RTP/AVP 127\r\na=rtpmap:127 GSM-EFR/8000/1\r\na=ptime:20\r\na=recvonly\r\nm=image 4402 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n"""
+
+def hexdump(src, length=8):
+    """Recipe is from http://code.activestate.com/recipes/142812/"""
+    result = []
+    digits = 4 if isinstance(src, unicode) else 2
+    for i in xrange(0, len(src), length):
+       s = src[i:i+length]
+       hexa = b' '.join(["%0*X" % (digits, ord(x))  for x in s])
+       text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.'  for x in s])
+       result.append( b"%04X   %-*s   %s" % (i, length*(digits + 1), hexa, text) )
+    return b'\n'.join(result)
+
+server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+server_socket.bind(("127.0.0.1", MGCP_CALLAGENT_PORT))
+server_socket.setblocking(1)
+
+last_ci = 1
+def send_and_receive(packet):
+    global last_ci
+    server_socket.sendto(packet, ("127.0.0.1", MGCP_GATEWAY_PORT))
+    try:
+        data, addr = server_socket.recvfrom(4096)
+
+        # attempt to store the CI of the response
+        list = data.split("\n")
+        for item in list:
+           if item.startswith("I: "):
+               last_ci = int(item[3:])
+
+        print hexdump(data), addr
+    except socket.error, e:
+        print e
+        pass
+
+def generate_tid():
+    import random
+    return random.randint(0, 65123)
+
+
+
+while True:
+    send_and_receive(audit_packet % generate_tid())
+    send_and_receive(crcx_packet % generate_tid() )
+    send_and_receive(mdcx_packet % (generate_tid(), last_ci))
+    send_and_receive(dlcx_packet % (generate_tid(), last_ci))
+
+    time.sleep(3)
diff --git a/openbsc/doc/BS11-OML.txt b/openbsc/doc/BS11-OML.txt
new file mode 100644
index 0000000..e5c3299
--- /dev/null
+++ b/openbsc/doc/BS11-OML.txt
@@ -0,0 +1,31 @@
+The Siemens BS-11 supports the following additional GSM 12.21 OML operations:
+
+
+CREATE OBJECT
+
+abis_om_fom_hdr.obj_class can be 
+A3:
+A5: ALCO, BBSIG, CCLK, GPSU, LI, PA
+A8: EnvaBTSE
+A9: BPORT
+
+the abis_om_obj_inst.trx_nr field indicates the index of object, whereas the
+abis_om_fom_hdr.bts_nr indicates the type of the object.
+
+enum abis_bs11_objtype {
+	BS11_OBJ_ALCO		= 0x01,
+	BS11_OBJ_BBSIG		= 0x02,	/* obj_class: 0,1 */
+	BS11_OBJ_TRX1		= 0x03,	/* only DEACTIVATE TRX1 */
+	BS11_OBJ_CCLK		= 0x04,
+	BS11_OBJ_GPSU		= 0x06,
+	BS11_OBJ_LI		= 0x07,
+	BS11_OBJ_PA		= 0x09,	/* obj_class: 0, 1*/
+};
+
+In case of CREATE ENVABTSE, the abis_om_obj_inst.trx_nr indicates the EnvaBTSEx
+number.
+
+In case of A9 (CREAETE BPORT), the abis_om_obj_inst.bts_nr indicates which BPORT
+shall be used.
+
+
diff --git a/openbsc/doc/call-routing.txt b/openbsc/doc/call-routing.txt
new file mode 100644
index 0000000..3402f9e
--- /dev/null
+++ b/openbsc/doc/call-routing.txt
@@ -0,0 +1,25 @@
+Call routing in OpenBSC
+
+Flow of events:
+
+ # MO call initiated by MS, CHANNEL RQD, IMMEDIATE ASSIGN
+ # MS sends CC SETUP message, we assume already on TCH/H FACCH
+ # OpenBSC does a subscriber lookup based on the target extension
+  * If a subscriber is found:
+   # send CALL PROCEEDING message to MO
+   # page the MT subscriber and ask itI to ask for TCH/H
+   # once paging completes, we have the TCH/H for the MT end
+   # send SETUP to MT
+   # receive CALL CONFIRMED from MT
+   # set-up the TRAU mux mapping between the E1 subslots for both TCH/H
+   # receive ALERTING from MT, route ALERTING to MO
+   # receive CONNECT from MT, confirm to MT with CONNECT_ACK
+   # send a CONNECT message to MO, receive CONNECT_ACK from MO
+ * If subscriber is not found:
+  # send RELEASE COMPLETE with apropriate cause to MO (1: unalloacated 3: no route)
+  
+
+
+Thoughts about RR/MM:
+
+* we allocate RR/MM entities on demand, when we need them
diff --git a/openbsc/doc/channel_release.txt b/openbsc/doc/channel_release.txt
new file mode 100644
index 0000000..bacf09c
--- /dev/null
+++ b/openbsc/doc/channel_release.txt
@@ -0,0 +1,74 @@
+
+GSM 04.08 7.1.7 / 9.1.7		RR CHANNEL RELESE
+
+RSL 08.58 3.4 / ?		RLL Link Release Request
+
+RSL 08.58 4.6 / 8.4.5		DEACTivate SACCH
+	* Deactivate SACCH according to Channel Release Proc 04.08
+	* to be sent after RR CHANNEL RELEASE is sent to MS
+
+RSL 08.58 4.7 / 8.4.14		RF CHANnel RELease
+	* tells the BTS to release a radio channel
+	* "when an activated radio channel is no longer needed"
+	* BTS responds with RF CHANnel RELease ACKnowledge
+
+
+GSM 04.08 3.4.13: RR connection release procedure
+
+* network sends RR CHANNEL RELEASE to MS on the DCCH
+  * start T3109
+  * deactivate SACCH
+* MS disconnects main signalling link (by sending DISC)
+  * all other data links are disconnected by local end link release
+* network receives DISC (BTS sends RLL REL IND to BSC)
+  * stop T3109
+  * start T3111 
+* when T3111 times out, the network can reuse the channls
+* if T3109 times out, the network deactivates the channels 
+  and can reuse them
+  * this probably means simply RF CHANnel RELease
+
+
+== Implementation in OpenBSC ==
+
+chan_alloc.c:lchan_auto_release()
+	* checks if use count still > 0 (abort)
+	* calls gsm48_send_rr_release()
+		* which calls rsl_deact_sacch()
+	* calls rsl_release_request()
+		* which sends RLL Link Release request
+	
+RX of RELease INDication:
+	* call rsl_rf_chan_release() (send RF_CHAN_REL)
+
+RX of RELease CONFimem:
+	* call rsl_rf_chan_release() (send RF_CHAN_REL)
+
+* RX of RF_CHAN_REL_ACK
+	* call lchan_free()
+		* subscr_put()
+		* delete release_timer
+
+
+=== Integration with SMS ===
+
+* RX of CP_ERROR or unimplemented MT
+	* trigger trans_free() which will lchan_auto_release()
+
+* CP TC1* expired while waiting for CP-ACK
+	* trigger trans_free() which will lchan_auto_release()
+
+* RX of RP_ERROR
+	* trigger trans_free() which will lchan_auto_release()
+	
+* TX of CP-ACK in MT DELIVER
+	* trigger trans_free() which will lchan_auto_release()
+
+* RX of CP-ACK in MO SUBMIT
+	* trigger trans_free() which will lchan_auto_release()
+	
+* RX of RP-ACK in MT DELIVER (and no more messages)
+	* trigger rsl_release_request() for SAPI3
+
+* RX of RP-SMMA in MT DELIVER (and no more messages)
+	* trigger rsl_release_request() for SAPI3
diff --git a/openbsc/doc/e1-data-model.txt b/openbsc/doc/e1-data-model.txt
new file mode 100644
index 0000000..8594fe4
--- /dev/null
+++ b/openbsc/doc/e1-data-model.txt
@@ -0,0 +1,172 @@
+E1 related data model
+
+This data model describes the physical relationship of the individual
+parts in the network, it is not the logical/protocol side of the GSM
+network.
+
+A BTS is connected to the BSC by some physical link.  It could be an actual
+E1 link, but it could also be abis-over-IP with a mixture of TCP and RTP/UDP.
+
+To further complicate the fact, multiple BTS can share one such pysical
+link.  On a single E1 line, we can easily accomodate up to three BTS with
+two TRX each.
+
+Thus, it is best for OpenBSC to have some kind of abstraction layer.  The BSC's
+view of a BTS connected to it.  We call this 'bts_link'.  A bts_link can be
+* all the TCP and UDP streams of a Abis-over-IP BTS
+* a set of E1 timeslots for OML, RSL and TRAU connections on a E1 link
+* a serial line exclusively used for OML messages (T-Link)
+
+A bts_link can be registered with the OpenBSC core at runtime.
+
+struct trx_link {
+	struct gsm_bts_trx *trx;
+};
+
+struct bts_link {
+	struct gsm_bts *bts;
+	struct trx_link trx_links[NUM_TRX];
+};
+
+Interface from stack to input core:
+======================================================================
+int abis_rsl_sendmsg(struct msgb *msg);
+	send a message through a RSL link to the TRX specified by the caller in
+	msg->trx.
+
+int abis_rsl_rcvmsg(struct msgb *msg);
+	receive a message from a RSL link from the TRX specified by the
+	caller in msg->trx.
+
+int abis_nm_sendmsg(struct msgb *msg);
+	send a message through a OML link to the BTS specified by the caller in
+	msg->trx->bts.  The caller can just use bts->c0 to get the first TRX
+	in a BTS. (OML messages are not really sent to a TRX but to the BTS)
+
+int abis_nm_rcvmsg(struct msgb *msg);
+	receive a message from a OML link from the BTS specified by the caller
+	in msg->trx->bts.  The caller can just use bts->c0 to get the first
+	TRX in a BTS.
+
+int abis_link_event(int event, void *data);
+	signal some event (such as layer 1 connect/disconnect) from the
+	input core to the stack.
+
+int subch_demux_in(mx, const u_int8_t *data, int len);
+	receive 'len' bytes from a given E1 timeslot (TRAU frames)
+
+int subchan_mux_out(mx, u_int8_t *data, int len);
+	obtain 'len' bytes of output data to be sent on E1 timeslot
+
+Intrface by Input Core for Input Plugins
+======================================================================
+
+int btslink_register_plugin();
+
+
+Configuration for the E1 input module
+======================================================================
+
+BTS
+	BTS number
+	number of TRX
+	OML link
+		E1 line number
+		timeslot number
+		[subslot number]
+		SAPI
+		TEI
+	for each TRX
+		RSL link
+			E1 line number
+			timeslot number
+			[subslot number]
+			SAPI
+			TEI
+		for each TS
+			E1 line number
+			timeslot number
+			subslot number
+
+
+E1 input module data model
+======================================================================
+
+
+enum e1inp_sign_type {
+	E1INP_SIGN_NONE,
+	E1INP_SIGN_OML,
+	E1INP_SIGN_RSL,
+};
+
+struct e1inp_sign_link {
+	/* list of signalling links */
+	struct llist_head list;
+
+	enum e1inp_sign_type type;
+
+	/* trx for msg->trx of received msgs */	
+	struct gsm_bts_trx *trx;
+
+	/* msgb queue of to-be-transmitted msgs */
+	struct llist_head tx_list;
+
+	/* SAPI and TEI on the E1 TS */
+	u_int8_t sapi;
+	u_int8_t tei;
+}
+
+enum e1inp_ts_type {
+	E1INP_TS_TYPE_NONE,
+	E1INP_TS_TYPE_SIGN,
+	E1INP_TS_TYPE_TRAU,
+};
+
+/* A timeslot in the E1 interface */
+struct e1inp_ts {
+	enum e1inp_ts_type type;
+	struct e1inp_line *line;
+	union {
+		struct {
+			struct llist_head sign_links;
+		} sign;
+		struct {
+			/* subchannel demuxer for frames from E1 */
+			struct subch_demux demux;
+			/* subchannel muxer for frames to E1 */
+			struct subch_mux mux;
+		} trau;
+	};
+	union {
+		struct {
+			/* mISDN driver has one fd for each ts */
+			struct bsc_fd;
+		} misdn;
+	} driver;
+};
+
+struct e1inp_line {
+	unsigned int num;
+	char *name;
+
+	struct e1inp_ts ts[NR_E1_TS];
+
+	char *e1inp_driver;
+	void *driver_data;
+};
+
+/* Call from the Stack: configuration of this TS has changed */
+int e1inp_update_ts(struct e1inp_ts *ts);
+
+/* Receive a packet from the E1 driver */
+int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
+		u_int8_t tei, u_int8_t sapi);
+
+/* Send a packet, callback function in the driver */
+int e1driver_tx_ts(struct e1inp_ts *ts, struct msgb *msg)
+
+
+struct e1inp_driver {
+	const char *name;
+	int (*want_write)(struct e1inp_ts *ts);
+};
diff --git a/openbsc/doc/gsm-hopping.txt b/openbsc/doc/gsm-hopping.txt
new file mode 100644
index 0000000..5910939
--- /dev/null
+++ b/openbsc/doc/gsm-hopping.txt
@@ -0,0 +1,54 @@
+according to GSM 05.02:
+
+general parameters from CCCH:
+* CA cell allocation of ARFCN's (System Information / BCCH)
+* FN: TDMA frame number (t1,t2,t3') in SCH
+
+specific parameters from channel assignment:
+* MA: mobile allocation, defines set of ARFCN's, up to 64
+* MAIO: index
+* HSN: hopping sequence generator number (0..64)
+
+
+hopping sequence generation (6.2.3):
+
+u_int8_t rntable[114] = {
+	 48,  98,  63,   1,  36,  95,  78, 102,  94,  73,
+	  0,  64,  25,  81,  76,  59, 124,  23, 104, 100,
+	101,  47, 118,  85,  18,  56,  96,  86,  54,   2,
+	 80,  34, 127,  13,   6,  89,  57, 103,  12,  74,
+	 55, 111,  75,  38, 109,  71, 112,  29,  11,  88,
+	 87,  19,   3,  68, 110,  26,  33,  31,   8,  45,
+	 82,  58,  40, 107,  32,   5, 106,  92,  62,  67,
+	 77, 108, 122,  37,  60,  66, 121,  42,  51, 126,
+	117, 114,   4,  90,  43,  52,  53, 113, 120,  72,
+	 16,  49,   7,  79, 119,  61,  22,  84,   9,  97,
+	125,  99,  17, 123
+};
+
+/* mai=0 represents lowest ARFCN in the MA */
+
+
+u_int8_t hopping_mai(u_int8_t hsn, u_int32_t fn, u_int8_t maio,
+		     u_int8_t t1, u_int8_t t2, u_int8_t t3_)
+{
+	u_int8_t mai;
+
+	if (hsn == 0) /* cyclic hopping */
+		mai = (fn + maio) % n;
+	else {
+		u_int32_t m, m_, t_, s;
+
+		m = t2 + rntable[(hsn xor (t1 % 64)) + t3];
+		m_ = m % (2^NBIN);
+		t_ = t3 % (2^NBIN);
+		if (m_ < n then)
+			s = m_;
+		else
+			s = (m_ + t_) % n;
+		mai = (s + maio) % n;
+	}
+
+	return mai;
+}
+
diff --git a/openbsc/doc/handover.txt b/openbsc/doc/handover.txt
new file mode 100644
index 0000000..ac19e87
--- /dev/null
+++ b/openbsc/doc/handover.txt
@@ -0,0 +1,89 @@
+Ideas about a handover algorithm
+======================================================================
+
+This is mostly based on the results presented in Chapter 8 of "Performance
+Enhancements in a Frequency Hopping GSM Network" by Thomas Toftegaard Nielsen
+and Joeroen Wigard. 
+
+
+=== Reasons for performing handover ===
+
+Section 2.1.1: Handover used in their CAPACITY simulation:
+
+1) Interference Handover
+
+Average RXLEV is satisfactory high, but average RXQUAL too low indicates
+interference to the channel.  Handover should be made.
+
+2) Bad Quality
+
+Averaged RXQUAL is lower than a threshold
+
+3) Low Level / Signal Strength
+
+Average RXLEV is lower than a threshold
+
+4) Distance Handover
+
+MS is too far away from a cell (measured by TA)
+
+5) Power budget / Better Cell
+
+RX Level of neighbor cell is at least "HO Margin dB" dB better than the
+current serving cell.
+
+=== Ideal parameters for HO algorithm ===
+
+Chapter 8, Section 2.2, Table 24:
+
+Window RXLEV averaging:		10 SACCH frames (no weighting)
+Window RXQUAL averaging:	1 SACCH frame (no averaging)
+Level Threashold:		1 of the last 1 AV-RXLEV values < -110dBm
+Quality Threshold:		3 of the last 4 AV-RXQUAL values >= 5
+Interference Threshold:		1 of the last AV-RXLEV > -85 dBm &
+				3 of the last 4 AV-RXQUAL values >= 5
+Power Budget:			Level of neighbor cell > 3 dB better
+Power Budget Interval:		Every 6 SACCH frames (6 seconds ?!?)
+Distance Handover:		Disabled
+Evaluation rule 1:		RXLEV of the candidate cell a tleast -104 dBm
+Evaluation rule 2:		Level of candidate cell > 3dB better own cell
+Timer Successful HO:		5 SACCH frames
+Timer Unsuccessful HO:		1 SACCH frame
+
+In a non-frequency hopping case, RXQUAL threshold can be decreased to
+RXLEV >= 4
+
+When frequency hopping is enabled, the following additional parameters
+should be introduced:
+
+* No intra-cell handover
+* Use a HO Margin of 2dB
+
+=== Handover Channel Reservation ===
+
+In loaded network, each cell should reserve some channels for handovers,
+rather than using all of them for new call establishment.  This reduces the
+need to drop calls due to failing handovers, at the expense of failing new call
+attempts.
+
+=== Dynamic HO Margin ===
+
+The handover margin (hysteresis) should depend on the RXQUAL. Optimal results
+were achieved with the following settings:
+* RXQUAL <= 4: 9 dB
+* RXQUAL == 5: 6 dB
+* RXQUAL >= 6: 1 dB
+
+
+
+== Actual Handover on a protocol level ==
+
+After the BSC has decided a handover shall be done, it has to
+
+# allocate a channel at the new BTS
+# allocate a handover reference
+# activate the channel on the BTS side using RSL CHANNEL ACTIVATION,
+  indicating the HO reference
+# BTS responds with CHAN ACT ACK, including GSM frame number
+# BSC sends 04.08 HO CMD to MS using old BTS
+
diff --git a/openbsc/doc/oml-interface.txt b/openbsc/doc/oml-interface.txt
new file mode 100644
index 0000000..8ddcfea
--- /dev/null
+++ b/openbsc/doc/oml-interface.txt
@@ -0,0 +1,21 @@
+oml interface design notes
+
+problems:
+
+* there is no way how to tag a command sent to the BTS, with the response
+  having the same tag to identify the originator of the command
+* therefore, we can have e.g. both the BSC and the OML interface send a
+  SET ATTRIBUTE message, where the responses would end up at the wrong
+  query.
+
+the only possible solutions i can imagine:
+* have some kind of exclusive locking, where the OML interface gets blocked
+  from the BSC and is exclusively assigned to the OML console until all commands
+  of the OML console have terminated.  This can either be done explicitly
+  dynamically or on demand
+
+* use the OML interface synchronously, i.e. always wait for the response from
+  the BTS before
+
+* unilateral / unsolicited messages need to be broadcasted to both the BSC and
+  the OML console
diff --git a/openbsc/git-version-gen b/openbsc/git-version-gen
new file mode 100755
index 0000000..f2ad4a7
--- /dev/null
+++ b/openbsc/git-version-gen
@@ -0,0 +1,151 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-01-28.01
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+#   produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+#   presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+#   a checked-out repository.  Created with contents that were learned at
+#   the last time autoconf was run, and used by git-version-gen.  Must not
+#   be present in either $(srcdir) or $(builddir) for git-version-gen to
+#   give accurate answers during normal development with a checked out tree,
+#   but must be present in a tarball when there is no version control system.
+#   Therefore, it cannot be used in any dependencies.  GNUmakefile has
+#   hooks to force a reconfigure at distribution time to get the value
+#   correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+#   tarball.  Usable in dependencies, particularly for files that don't
+#   want to depend on config.h but do want to track version changes.
+#   Delete this file prior to any autoconf run where you want to rebuild
+#   files to pick up a version string change; and leave it stale to
+#   minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+#         m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+#         [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+#	echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+#	echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+    1) ;;
+    *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+    v=`cat $tarball_version_file` || exit 1
+    case $v in
+	*$nl*) v= ;; # reject multi-line output
+	[0-9]*) ;;
+	*) v= ;;
+    esac
+    test -z "$v" \
+	&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+    : # use $v
+elif test -d ./../.git \
+    && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+	  || git describe --abbrev=4 HEAD 2>/dev/null` \
+    && case $v in
+	 [0-9]*) ;;
+	 v[0-9]*) ;;
+	 *) (exit 1) ;;
+       esac
+then
+    # Is this a new git that lists number of commits since the last
+    # tag or the previous older version that did not?
+    #   Newer: v6.10-77-g0f8faeb
+    #   Older: v6.10-g0f8faeb
+    case $v in
+	*-*-*) : git describe is okay three part flavor ;;
+	*-*)
+	    : git describe is older two part flavor
+	    # Recreate the number of commits and rewrite such that the
+	    # result is the same as if we were using the newer version
+	    # of git describe.
+	    vtag=`echo "$v" | sed 's/-.*//'`
+	    numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+	    v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+	    ;;
+    esac
+
+    # Change the first '-' to a '.', so version-comparing tools work properly.
+    # Remove the "g" in git describe's output string, to save a byte.
+    v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+    v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+    '') ;;
+    *) # Append the suffix only if there isn't one already.
+	case $v in
+	  *-dirty) ;;
+	  *) v="$v-dirty" ;;
+	esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/openbsc/include/Makefile.am b/openbsc/include/Makefile.am
new file mode 100644
index 0000000..56b2a33
--- /dev/null
+++ b/openbsc/include/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = openbsc vty sccp
+
+noinst_HEADERS = mISDNif.h compat_af_isdn.h
diff --git a/openbsc/include/compat_af_isdn.h b/openbsc/include/compat_af_isdn.h
new file mode 100644
index 0000000..56cbfb3
--- /dev/null
+++ b/openbsc/include/compat_af_isdn.h
@@ -0,0 +1,39 @@
+#ifdef MISDN_OLD_AF_COMPATIBILITY
+#undef AF_ISDN
+#undef PF_ISDN
+
+extern	int	AF_ISDN;
+#define PF_ISDN	AF_ISDN
+
+int	AF_ISDN;
+
+#endif
+
+extern void init_af_isdn(void);
+
+#ifdef AF_COMPATIBILITY_FUNC
+#ifdef MISDN_OLD_AF_COMPATIBILITY
+void init_af_isdn(void)
+{
+	int	s;
+
+	/* test for new value */
+	AF_ISDN = 34;
+	s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (s >= 0) {
+		close(s);
+		return;
+	}
+	AF_ISDN = 27;
+	s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (s >= 0) {
+		close(s);
+		return;
+	}
+}
+#else
+void init_af_isdn(void)
+{
+}
+#endif
+#endif
diff --git a/openbsc/include/mISDNif.h b/openbsc/include/mISDNif.h
new file mode 100644
index 0000000..8e065d2
--- /dev/null
+++ b/openbsc/include/mISDNif.h
@@ -0,0 +1,387 @@
+/*
+ *
+ * Author	Karsten Keil <kkeil@novell.com>
+ *
+ * Copyright 2008  by Karsten Keil <kkeil@novell.com>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This code 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 LESSER GENERAL PUBLIC LICENSE for more details.
+ *
+ */
+
+#ifndef mISDNIF_H
+#define mISDNIF_H
+
+#include <stdarg.h>
+#ifdef linux
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/socket.h>
+#else
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#endif
+
+/*
+ * ABI Version 32 bit
+ *
+ * <8 bit> Major version
+ *		- changed if any interface become backwards incompatible
+ *
+ * <8 bit> Minor version
+ *              - changed if any interface is extended but backwards compatible
+ *
+ * <16 bit> Release number
+ *              - should be incremented on every checkin
+ */
+#define	MISDN_MAJOR_VERSION	1
+#define	MISDN_MINOR_VERSION	1
+#define MISDN_RELEASE		20
+
+/* primitives for information exchange
+ * generell format
+ * <16  bit  0 >
+ * <8  bit command>
+ *    BIT 8 = 1 LAYER private
+ *    BIT 7 = 1 answer
+ *    BIT 6 = 1 DATA
+ * <8  bit target layer mask>
+ *
+ * Layer = 00 is reserved for general commands
+   Layer = 01  L2 -> HW
+   Layer = 02  HW -> L2
+   Layer = 04  L3 -> L2
+   Layer = 08  L2 -> L3
+ * Layer = FF is reserved for broadcast commands
+ */
+
+#define MISDN_CMDMASK		0xff00
+#define MISDN_LAYERMASK		0x00ff
+
+/* generell commands */
+#define OPEN_CHANNEL		0x0100
+#define CLOSE_CHANNEL		0x0200
+#define CONTROL_CHANNEL		0x0300
+#define CHECK_DATA		0x0400
+
+/* layer 2 -> layer 1 */
+#define PH_ACTIVATE_REQ		0x0101
+#define PH_DEACTIVATE_REQ	0x0201
+#define PH_DATA_REQ		0x2001
+#define MPH_ACTIVATE_REQ	0x0501
+#define MPH_DEACTIVATE_REQ	0x0601
+#define MPH_INFORMATION_REQ	0x0701
+#define PH_CONTROL_REQ		0x0801
+
+/* layer 1 -> layer 2 */
+#define PH_ACTIVATE_IND		0x0102
+#define PH_ACTIVATE_CNF		0x4102
+#define PH_DEACTIVATE_IND	0x0202
+#define PH_DEACTIVATE_CNF	0x4202
+#define PH_DATA_IND		0x2002
+#define PH_DATA_E_IND		0x3002
+#define MPH_ACTIVATE_IND	0x0502
+#define MPH_DEACTIVATE_IND	0x0602
+#define MPH_INFORMATION_IND	0x0702
+#define PH_DATA_CNF		0x6002
+#define PH_CONTROL_IND		0x0802
+#define PH_CONTROL_CNF		0x4802
+
+/* layer 3 -> layer 2 */
+#define DL_ESTABLISH_REQ	0x1004
+#define DL_RELEASE_REQ		0x1104
+#define DL_DATA_REQ		0x3004
+#define DL_UNITDATA_REQ		0x3104
+#define DL_INFORMATION_REQ	0x0004
+
+/* layer 2 -> layer 3 */
+#define DL_ESTABLISH_IND	0x1008
+#define DL_ESTABLISH_CNF	0x5008
+#define DL_RELEASE_IND		0x1108
+#define DL_RELEASE_CNF		0x5108
+#define DL_DATA_IND		0x3008
+#define DL_UNITDATA_IND		0x3108
+#define DL_INFORMATION_IND	0x0008
+
+/* intern layer 2 managment */
+#define MDL_ASSIGN_REQ		0x1804
+#define MDL_ASSIGN_IND		0x1904
+#define MDL_REMOVE_REQ		0x1A04
+#define MDL_REMOVE_IND		0x1B04
+#define MDL_STATUS_UP_IND	0x1C04
+#define MDL_STATUS_DOWN_IND	0x1D04
+#define MDL_STATUS_UI_IND	0x1E04
+#define MDL_ERROR_IND		0x1F04
+#define MDL_ERROR_RSP		0x5F04
+
+/* DL_INFORMATION_IND types */
+#define DL_INFO_L2_CONNECT	0x0001
+#define DL_INFO_L2_REMOVED	0x0002
+
+/* PH_CONTROL types */
+/* TOUCH TONE IS 0x20XX  XX "0"..."9", "A","B","C","D","*","#" */
+#define DTMF_TONE_VAL		0x2000
+#define DTMF_TONE_MASK		0x007F
+#define DTMF_TONE_START		0x2100
+#define DTMF_TONE_STOP		0x2200
+#define DTMF_HFC_COEF		0x4000
+#define DSP_CONF_JOIN		0x2403
+#define DSP_CONF_SPLIT		0x2404
+#define DSP_RECEIVE_OFF		0x2405
+#define DSP_RECEIVE_ON		0x2406
+#define DSP_ECHO_ON		0x2407
+#define DSP_ECHO_OFF		0x2408
+#define DSP_MIX_ON		0x2409
+#define DSP_MIX_OFF		0x240a
+#define DSP_DELAY		0x240b
+#define DSP_JITTER		0x240c
+#define DSP_TXDATA_ON		0x240d
+#define DSP_TXDATA_OFF		0x240e
+#define DSP_TX_DEJITTER		0x240f
+#define DSP_TX_DEJ_OFF		0x2410
+#define DSP_TONE_PATT_ON	0x2411
+#define DSP_TONE_PATT_OFF	0x2412
+#define DSP_VOL_CHANGE_TX	0x2413
+#define DSP_VOL_CHANGE_RX	0x2414
+#define DSP_BF_ENABLE_KEY	0x2415
+#define DSP_BF_DISABLE		0x2416
+#define DSP_BF_ACCEPT		0x2416
+#define DSP_BF_REJECT		0x2417
+#define DSP_PIPELINE_CFG	0x2418
+#define HFC_VOL_CHANGE_TX	0x2601
+#define HFC_VOL_CHANGE_RX	0x2602
+#define HFC_SPL_LOOP_ON		0x2603
+#define HFC_SPL_LOOP_OFF	0x2604
+
+/* DSP_TONE_PATT_ON parameter */
+#define TONE_OFF			0x0000
+#define TONE_GERMAN_DIALTONE		0x0001
+#define TONE_GERMAN_OLDDIALTONE		0x0002
+#define TONE_AMERICAN_DIALTONE		0x0003
+#define TONE_GERMAN_DIALPBX		0x0004
+#define TONE_GERMAN_OLDDIALPBX		0x0005
+#define TONE_AMERICAN_DIALPBX		0x0006
+#define TONE_GERMAN_RINGING		0x0007
+#define TONE_GERMAN_OLDRINGING		0x0008
+#define TONE_AMERICAN_RINGPBX		0x000b
+#define TONE_GERMAN_RINGPBX		0x000c
+#define TONE_GERMAN_OLDRINGPBX		0x000d
+#define TONE_AMERICAN_RINGING		0x000e
+#define TONE_GERMAN_BUSY		0x000f
+#define TONE_GERMAN_OLDBUSY		0x0010
+#define TONE_AMERICAN_BUSY		0x0011
+#define TONE_GERMAN_HANGUP		0x0012
+#define TONE_GERMAN_OLDHANGUP		0x0013
+#define TONE_AMERICAN_HANGUP		0x0014
+#define TONE_SPECIAL_INFO		0x0015
+#define TONE_GERMAN_GASSENBESETZT	0x0016
+#define TONE_GERMAN_AUFSCHALTTON	0x0016
+
+/* MPH_INFORMATION_IND */
+#define L1_SIGNAL_LOS_OFF	0x0010
+#define L1_SIGNAL_LOS_ON	0x0011
+#define L1_SIGNAL_AIS_OFF	0x0012
+#define L1_SIGNAL_AIS_ON	0x0013
+#define L1_SIGNAL_RDI_OFF	0x0014
+#define L1_SIGNAL_RDI_ON	0x0015
+#define L1_SIGNAL_SLIP_RX	0x0020
+#define L1_SIGNAL_SLIP_TX	0x0021
+
+/*
+ * protocol ids
+ * D channel 1-31
+ * B channel 33 - 63
+ */
+
+#define ISDN_P_NONE		0
+#define ISDN_P_BASE		0
+#define ISDN_P_TE_S0		0x01
+#define ISDN_P_NT_S0  		0x02
+#define ISDN_P_TE_E1		0x03
+#define ISDN_P_NT_E1  		0x04
+#define ISDN_P_TE_UP0		0x05
+#define ISDN_P_NT_UP0		0x06
+
+#define IS_ISDN_P_TE(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_TE_E1) || \
+				(p == ISDN_P_TE_UP0) || (p == ISDN_P_LAPD_TE))
+#define IS_ISDN_P_NT(p) ((p == ISDN_P_NT_S0) || (p == ISDN_P_NT_E1) || \
+				(p == ISDN_P_NT_UP0) || (p == ISDN_P_LAPD_NT))
+#define IS_ISDN_P_S0(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_NT_S0))
+#define IS_ISDN_P_E1(p) ((p == ISDN_P_TE_E1) || (p == ISDN_P_NT_E1))
+#define IS_ISDN_P_UP0(p) ((p == ISDN_P_TE_UP0) || (p == ISDN_P_NT_UP0))
+
+
+#define ISDN_P_LAPD_TE		0x10
+#define	ISDN_P_LAPD_NT		0x11
+
+#define ISDN_P_B_MASK		0x1f
+#define ISDN_P_B_START		0x20
+
+#define ISDN_P_B_RAW		0x21
+#define ISDN_P_B_HDLC		0x22
+#define ISDN_P_B_X75SLP		0x23
+#define ISDN_P_B_L2DTMF		0x24
+#define ISDN_P_B_L2DSP		0x25
+#define ISDN_P_B_L2DSPHDLC	0x26
+
+#define OPTION_L2_PMX		1
+#define OPTION_L2_PTP		2
+#define OPTION_L2_FIXEDTEI	3
+#define OPTION_L2_CLEANUP	4
+
+/* should be in sync with linux/kobject.h:KOBJ_NAME_LEN */
+#define MISDN_MAX_IDLEN		20
+
+struct mISDNhead {
+	unsigned int	prim;
+	unsigned int	id;
+}  __attribute__((packed));
+
+#define MISDN_HEADER_LEN	sizeof(struct mISDNhead)
+#define MAX_DATA_SIZE		2048
+#define MAX_DATA_MEM		(MAX_DATA_SIZE + MISDN_HEADER_LEN)
+#define MAX_DFRAME_LEN		260
+
+#define MISDN_ID_ADDR_MASK	0xFFFF
+#define MISDN_ID_TEI_MASK	0xFF00
+#define MISDN_ID_SAPI_MASK	0x00FF
+#define MISDN_ID_TEI_ANY	0x7F00
+
+#define MISDN_ID_ANY		0xFFFF
+#define MISDN_ID_NONE		0xFFFE
+
+#define GROUP_TEI		127
+#define TEI_SAPI		63
+#define CTRL_SAPI		0
+
+#define MISDN_MAX_CHANNEL	127
+#define MISDN_CHMAP_SIZE	((MISDN_MAX_CHANNEL + 1) >> 3)
+
+#define SOL_MISDN	0
+
+struct sockaddr_mISDN {
+	sa_family_t    family;
+	unsigned char	dev;
+	unsigned char	channel;
+	unsigned char	sapi;
+	unsigned char	tei;
+};
+
+struct mISDNversion {
+	unsigned char	major;
+	unsigned char	minor;
+	unsigned short	release;
+};
+
+#define MAX_DEVICE_ID 63
+
+struct mISDN_devinfo {
+	u_int			id;
+	u_int			Dprotocols;
+	u_int			Bprotocols;
+	u_int			protocol;
+	u_char			channelmap[MISDN_CHMAP_SIZE];
+	u_int			nrbchan;
+	char			name[MISDN_MAX_IDLEN];
+};
+
+struct mISDN_devrename {
+	u_int			id;
+	char			name[MISDN_MAX_IDLEN];
+};
+
+struct ph_info_ch {
+	int32_t 		protocol;
+	int64_t			Flags;
+};
+
+struct ph_info_dch {
+	struct ph_info_ch	ch;
+	int16_t			state;
+	int16_t			num_bch;
+};
+
+struct ph_info {
+	struct ph_info_dch	dch;
+	struct ph_info_ch 	bch[];
+};
+
+/* timer device ioctl */
+#define IMADDTIMER	_IOR('I', 64, int)
+#define IMDELTIMER	_IOR('I', 65, int)
+/* socket ioctls */
+#define	IMGETVERSION	_IOR('I', 66, int)
+#define	IMGETCOUNT	_IOR('I', 67, int)
+#define IMGETDEVINFO	_IOR('I', 68, int)
+#define IMCTRLREQ	_IOR('I', 69, int)
+#define IMCLEAR_L2	_IOR('I', 70, int)
+#define IMSETDEVNAME	_IOR('I', 71, struct mISDN_devrename)
+
+static inline int
+test_channelmap(u_int nr, u_char *map)
+{
+	if (nr <= MISDN_MAX_CHANNEL)
+		return map[nr >> 3] & (1 << (nr & 7));
+	else
+		return 0;
+}
+
+static inline void
+set_channelmap(u_int nr, u_char *map)
+{
+	map[nr >> 3] |= (1 << (nr & 7));
+}
+
+static inline void
+clear_channelmap(u_int nr, u_char *map)
+{
+	map[nr >> 3] &= ~(1 << (nr & 7));
+}
+
+/* CONTROL_CHANNEL parameters */
+#define MISDN_CTRL_GETOP		0x0000
+#define MISDN_CTRL_LOOP			0x0001
+#define MISDN_CTRL_CONNECT		0x0002
+#define MISDN_CTRL_DISCONNECT		0x0004
+#define MISDN_CTRL_PCMCONNECT		0x0010
+#define MISDN_CTRL_PCMDISCONNECT	0x0020
+#define MISDN_CTRL_SETPEER		0x0040
+#define MISDN_CTRL_UNSETPEER		0x0080
+#define MISDN_CTRL_RX_OFF		0x0100
+#define MISDN_CTRL_FILL_EMPTY		0x0200
+#define MISDN_CTRL_GETPEER		0x0400
+#define MISDN_CTRL_HW_FEATURES_OP	0x2000
+#define MISDN_CTRL_HW_FEATURES		0x2001
+#define MISDN_CTRL_HFC_OP		0x4000
+#define MISDN_CTRL_HFC_PCM_CONN		0x4001
+#define MISDN_CTRL_HFC_PCM_DISC		0x4002
+#define MISDN_CTRL_HFC_CONF_JOIN	0x4003
+#define MISDN_CTRL_HFC_CONF_SPLIT	0x4004
+#define MISDN_CTRL_HFC_RECEIVE_OFF	0x4005
+#define MISDN_CTRL_HFC_RECEIVE_ON	0x4006
+#define MISDN_CTRL_HFC_ECHOCAN_ON 	0x4007
+#define MISDN_CTRL_HFC_ECHOCAN_OFF 	0x4008
+
+
+/* socket options */
+#define MISDN_TIME_STAMP		0x0001
+
+struct mISDN_ctrl_req {
+	int		op;
+	int		channel;
+	int		p1;
+	int		p2;
+};
+
+/* muxer options */
+#define MISDN_OPT_ALL		1
+#define MISDN_OPT_TEIMGR	2
+
+#endif /* mISDNIF_H */
diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am
new file mode 100644
index 0000000..afb62de
--- /dev/null
+++ b/openbsc/include/openbsc/Makefile.am
@@ -0,0 +1,14 @@
+noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h gsm_data.h \
+		 gsm_subscriber.h gsm_04_11.h debug.h signal.h \
+		 misdn.h chan_alloc.h telnet_interface.h paging.h \
+		 subchan_demux.h trau_frame.h e1_input.h trau_mux.h \
+		 ipaccess.h rs232.h openbscdefines.h rtp_proxy.h \
+		 bsc_rll.h mncc.h transaction.h ussd.h gsm_04_80.h \
+		 silent_call.h mgcp.h meas_rep.h rest_octets.h \
+		 system_information.h handover.h mgcp_internal.h \
+		 vty.h \
+		crc24.h gprs_bssgp.h gprs_llc.h gprs_ns.h \
+		gb_proxy.h gprs_sgsn.h gsm_04_08_gprs.h sgsn.h
+
+openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h
+openbscdir = $(includedir)/openbsc
diff --git a/openbsc/include/openbsc/abis_nm.h b/openbsc/include/openbsc/abis_nm.h
new file mode 100644
index 0000000..45307e3
--- /dev/null
+++ b/openbsc/include/openbsc/abis_nm.h
@@ -0,0 +1,172 @@
+/* GSM Network Management messages on the A-bis interface 
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef _NM_H
+#define _NM_H
+
+#include <sys/types.h>
+#include <osmocore/tlv.h>
+#include <osmocore/protocol/gsm_12_21.h>
+
+struct cell_global_id {
+	u_int16_t mcc;
+	u_int16_t mnc;
+	u_int16_t lac;
+	u_int16_t ci;
+};
+
+/* The BCCH info from an ip.access test, in host byte order
+ * and already parsed... */
+struct ipac_bcch_info {
+	struct llist_head list;
+
+	u_int16_t info_type;
+	u_int8_t freq_qual;
+	u_int16_t arfcn;
+	u_int8_t rx_lev;
+	u_int8_t rx_qual;
+	int16_t freq_err;
+	u_int16_t frame_offset;
+	u_int32_t frame_nr_offset;
+	u_int8_t bsic;
+	struct cell_global_id cgi;
+	u_int8_t ba_list_si2[16];
+	u_int8_t ba_list_si2bis[16];
+	u_int8_t ba_list_si2ter[16];
+	u_int8_t ca_list_si1[16];
+};
+
+extern const struct tlv_definition nm_att_tlvdef;
+
+/* PUBLIC */
+
+struct msgb;
+
+struct abis_nm_cfg {
+	/* callback for unidirectional reports */
+	int (*report_cb)(struct msgb *,
+			 struct abis_om_fom_hdr *);
+	/* callback for software activate requests from BTS */
+	int (*sw_act_req)(struct msgb *);
+};
+
+extern int abis_nm_rcvmsg(struct msgb *msg);
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const u_int8_t *buf, int len);
+int abis_nm_rx(struct msgb *msg);
+int abis_nm_opstart(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, u_int8_t i2);
+int abis_nm_chg_adm_state(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0,
+			  u_int8_t i1, u_int8_t i2, enum abis_nm_adm_state adm_state);
+int abis_nm_establish_tei(struct gsm_bts *bts, u_int8_t trx_nr,
+			  u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot,
+			  u_int8_t tei);
+int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx,
+			   u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot);
+int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
+			   u_int8_t e1_port, u_int8_t e1_timeslot,
+			   u_int8_t e1_subslot);
+int abis_nm_set_bts_attr(struct gsm_bts *bts, u_int8_t *attr, int attr_len);
+int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, u_int8_t *attr, int attr_len);
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb);
+int abis_nm_sw_act_req_ack(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i1,
+			u_int8_t i2, u_int8_t i3, int nack, u_int8_t *attr, int att_len);
+int abis_nm_raw_msg(struct gsm_bts *bts, int len, u_int8_t *msg);
+int abis_nm_event_reports(struct gsm_bts *bts, int on);
+int abis_nm_reset_resource(struct gsm_bts *bts);
+int abis_nm_software_load(struct gsm_bts *bts, const char *fname,
+			  u_int8_t win_size, int forced,
+			  gsm_cbfn *cbfn, void *cb_data);
+int abis_nm_software_load_status(struct gsm_bts *bts);
+int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
+			      gsm_cbfn *cbfn, void *cb_data);
+
+int abis_nm_conn_mdrop_link(struct gsm_bts *bts, u_int8_t e1_port0, u_int8_t ts0,
+			    u_int8_t e1_port1, u_int8_t ts1);
+
+int abis_nm_perform_test(struct gsm_bts *bts, u_int8_t obj_class,
+			 u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t test_nr, u_int8_t auton_report,
+			 u_int8_t *phys_config, u_int16_t phys_config_len);
+
+int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan);
+
+/* Siemens / BS-11 specific */
+int abis_nm_bs11_reset_resource(struct gsm_bts *bts);
+int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin);
+int abis_nm_bs11_create_object(struct gsm_bts *bts, enum abis_bs11_objtype type,
+			  u_int8_t idx, u_int8_t attr_len, const u_int8_t *attr);
+int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, u_int8_t idx);
+int abis_nm_bs11_create_bport(struct gsm_bts *bts, u_int8_t idx);
+int abis_nm_bs11_delete_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, u_int8_t idx);
+int abis_nm_bs11_delete_bport(struct gsm_bts *bts, u_int8_t idx);
+int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, u_int8_t e1_port,
+			  u_int8_t e1_timeslot, u_int8_t e1_subslot, u_int8_t tei);
+int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts);
+int abis_nm_bs11_get_serno(struct gsm_bts *bts);
+int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, u_int8_t level);
+int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx);
+int abis_nm_bs11_logon(struct gsm_bts *bts, u_int8_t level, const char *name, int on);
+int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on);
+int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on);
+int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password);
+int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked);
+int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts);
+int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value);
+int abis_nm_bs11_get_cclk(struct gsm_bts *bts);
+int abis_nm_bs11_get_state(struct gsm_bts *bts);
+int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname,
+			  u_int8_t win_size, int forced, gsm_cbfn *cbfn);
+int abis_nm_bs11_set_ext_time(struct gsm_bts *bts);
+int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport, enum abis_bs11_line_cfg line_cfg);
+int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect);
+int abis_nm_bs11_restart(struct gsm_bts *bts);
+
+/* ip.access nanoBTS specific commands */
+int abis_nm_ipaccess_msg(struct gsm_bts *bts, u_int8_t msg_type,
+			 u_int8_t obj_class, u_int8_t bts_nr,
+			 u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t *attr, int attr_len);
+int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, u_int8_t *attr,
+				int attr_len);
+int abis_nm_ipaccess_restart(struct gsm_bts *bts);
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, u_int8_t obj_class,
+				u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+				u_int8_t *attr, u_int8_t attr_len);
+int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx, 
+				 u_int32_t ip, u_int16_t port, u_int8_t stream);
+void abis_nm_ipaccess_cgi(u_int8_t *buf, struct gsm_bts *bts);
+int ipac_parse_bcch_info(struct ipac_bcch_info *binf, u_int8_t *buf);
+const char *ipacc_testres_name(u_int8_t res);
+
+/* Functions calling into other code parts */
+enum nm_evt {
+	EVT_STATECHG_OPER,
+	EVT_STATECHG_ADM,
+};
+int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
+		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state); 
+
+const char *nm_opstate_name(u_int8_t os);
+const char *nm_avail_name(u_int8_t avail);
+int nm_is_running(struct gsm_nm_state *s);
+#endif /* _NM_H */
diff --git a/openbsc/include/openbsc/abis_rsl.h b/openbsc/include/openbsc/abis_rsl.h
new file mode 100644
index 0000000..8e6774d
--- /dev/null
+++ b/openbsc/include/openbsc/abis_rsl.h
@@ -0,0 +1,88 @@
+/* GSM Radio Signalling Link messages on the A-bis interface 
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef _RSL_H
+#define _RSL_H
+
+#include <osmocore/protocol/gsm_08_58.h>
+
+#include <osmocore/msgb.h>
+
+int rsl_bcch_info(struct gsm_bts_trx *trx, u_int8_t type,
+		  const u_int8_t *data, int len);
+int rsl_sacch_filling(struct gsm_bts_trx *trx, u_int8_t type, 
+		      const u_int8_t *data, int len);
+int rsl_chan_activate(struct gsm_bts_trx *trx, u_int8_t chan_nr,
+		      u_int8_t act_type,
+		      struct rsl_ie_chan_mode *chan_mode,
+		      struct rsl_ie_chan_ident *chan_ident,
+		      u_int8_t bs_power, u_int8_t ms_power,
+		      u_int8_t ta);
+int rsl_chan_activate_lchan(struct gsm_lchan *lchan, u_int8_t act_type, 
+			    u_int8_t ta, u_int8_t ho_ref);
+int rsl_chan_mode_modify_req(struct gsm_lchan *ts);
+int rsl_encryption_cmd(struct msgb *msg);
+int rsl_paging_cmd(struct gsm_bts *bts, u_int8_t paging_group, u_int8_t len,
+		   u_int8_t *ms_ident, u_int8_t chan_needed);
+int rsl_paging_cmd_subscr(struct gsm_bts *bts, u_int8_t chan_needed,
+			 struct gsm_subscriber *subscr);
+int rsl_imm_assign_cmd(struct gsm_bts *bts, u_int8_t len, u_int8_t *val);
+
+int rsl_data_request(struct msgb *msg, u_int8_t link_id);
+int rsl_establish_request(struct gsm_lchan *lchan, u_int8_t link_id);
+int rsl_relase_request(struct gsm_lchan *lchan, u_int8_t link_id);
+
+/* Siemens vendor-specific RSL extensions */
+int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci);
+
+/* ip.access specfic RSL extensions */
+int rsl_ipacc_crcx(struct gsm_lchan *lchan);
+int rsl_ipacc_mdcx(struct gsm_lchan *lchan, u_int32_t ip,
+		   u_int16_t port, u_int8_t rtp_payload2);
+int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan);
+int rsl_ipacc_pdch_activate(struct gsm_lchan *lchan, int act);
+
+int abis_rsl_rcvmsg(struct msgb *msg);
+
+unsigned int get_paging_group(u_int64_t imsi, unsigned int bs_cc_chans,
+			      int n_pag_blocks);
+unsigned int n_pag_blocks(int bs_ccch_sdcch_comb, unsigned int bs_ag_blks_res);
+u_int64_t str_to_imsi(const char *imsi_str);
+u_int8_t lchan2chan_nr(const struct gsm_lchan *lchan);
+int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id);
+
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int);
+
+/* to be provided by external code */
+int abis_rsl_sendmsg(struct msgb *msg);
+int rsl_deact_sacch(struct gsm_lchan *lchan);
+
+/* BCCH related code */
+int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf);
+int rsl_ccch_conf_to_bs_ccch_sdcch_comb(int ccch_conf);
+int rsl_number_of_paging_subchannels(struct gsm_bts *bts);
+
+int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db);
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm);
+
+#endif /* RSL_MT_H */
+
diff --git a/openbsc/include/openbsc/bsc_api.h b/openbsc/include/openbsc/bsc_api.h
new file mode 100644
index 0000000..51e344f
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_api.h
@@ -0,0 +1,6 @@
+/* GSM 08.08 like API for OpenBSC */
+
+#include "gsm_data.h"
+
+
+int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, int link_id);
diff --git a/openbsc/include/openbsc/bsc_rll.h b/openbsc/include/openbsc/bsc_rll.h
new file mode 100644
index 0000000..b2898d1
--- /dev/null
+++ b/openbsc/include/openbsc/bsc_rll.h
@@ -0,0 +1,19 @@
+#ifndef _BSC_RLL_H
+#define _BSC_RLL_H
+
+#include <openbsc/gsm_data.h>
+
+enum bsc_rllr_ind {
+	BSC_RLLR_IND_EST_CONF,
+	BSC_RLLR_IND_REL_IND,
+	BSC_RLLR_IND_ERR_IND,
+	BSC_RLLR_IND_TIMEOUT,
+};
+
+int rll_establish(struct gsm_lchan *lchan, u_int8_t link_id,
+		  void (*cb)(struct gsm_lchan *, u_int8_t, void *,
+			     enum bsc_rllr_ind),
+		  void *data);
+void rll_indication(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t type);
+
+#endif /* _BSC_RLL_H */
diff --git a/openbsc/include/openbsc/chan_alloc.h b/openbsc/include/openbsc/chan_alloc.h
new file mode 100644
index 0000000..d4f5858
--- /dev/null
+++ b/openbsc/include/openbsc/chan_alloc.h
@@ -0,0 +1,67 @@
+/* Management functions to allocate/release struct gsm_lchan */
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#ifndef _CHAN_ALLOC_H
+#define _CHAN_ALLOC_H
+
+#include "gsm_subscriber.h"
+
+/* Special allocator for C0 of BTS */
+struct gsm_bts_trx_ts *ts_c0_alloc(struct gsm_bts *bts,
+				   enum gsm_phys_chan_config pchan);
+
+/* Regular physical channel allocator */
+struct gsm_bts_trx_ts *ts_alloc(struct gsm_bts *bts,
+				enum gsm_phys_chan_config pchan);
+
+/* Regular physical channel (TS) */
+void ts_free(struct gsm_bts_trx_ts *ts);
+
+/* Find an allocated channel */
+struct gsm_lchan *lchan_find(struct gsm_bts *bts, struct gsm_subscriber *subscr);
+
+/* Find an allocated channel for a specified subscriber */
+struct gsm_lchan *lchan_for_subscr(struct gsm_subscriber *subscr);
+
+/* Allocate a logical channel (SDCCH, TCH, ...) */
+struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type);
+
+/* Free a logical channel (SDCCH, TCH, ...) */
+void lchan_free(struct gsm_lchan *lchan);
+void lchan_reset(struct gsm_lchan *lchan);
+
+/* Consider releasing the channel */
+int lchan_auto_release(struct gsm_lchan *lchan);
+
+struct load_counter {
+	unsigned int total;
+	unsigned int used;
+};
+
+struct pchan_load {
+	struct load_counter pchan[GSM_PCHAN_UNKNOWN];
+};
+
+void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts);
+void network_chan_load(struct pchan_load *pl, struct gsm_network *net);
+
+int trx_is_usable(struct gsm_bts_trx *trx);
+
+#endif /* _CHAN_ALLOC_H */
diff --git a/openbsc/include/openbsc/crc24.h b/openbsc/include/openbsc/crc24.h
new file mode 100644
index 0000000..358fcb5
--- /dev/null
+++ b/openbsc/include/openbsc/crc24.h
@@ -0,0 +1,8 @@
+#ifndef _CRC24_H
+#define _CRC24_H
+
+#define INIT_CRC24	0xffffff
+
+u_int32_t crc24_calc(u_int32_t fcs, u_int8_t *cp, unsigned int len);
+
+#endif
diff --git a/openbsc/include/openbsc/db.h b/openbsc/include/openbsc/db.h
new file mode 100644
index 0000000..d0a1278
--- /dev/null
+++ b/openbsc/include/openbsc/db.h
@@ -0,0 +1,71 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef _DB_H
+#define _DB_H
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_subscriber.h>
+
+/* one time initialisation */
+int db_init(const char *name);
+int db_prepare();
+int db_fini();
+
+/* subscriber management */
+struct gsm_subscriber* db_create_subscriber(struct gsm_network *net,
+					    char *imsi);
+struct gsm_subscriber* db_get_subscriber(struct gsm_network *net,
+					 enum gsm_subscriber_field field,
+					 const char *subscr);
+int db_sync_subscriber(struct gsm_subscriber* subscriber);
+int db_subscriber_alloc_tmsi(struct gsm_subscriber* subscriber);
+int db_subscriber_alloc_exten(struct gsm_subscriber* subscriber);
+int db_subscriber_alloc_token(struct gsm_subscriber* subscriber, u_int32_t* token);
+int db_subscriber_assoc_imei(struct gsm_subscriber* subscriber, char *imei);
+int db_sync_equipment(struct gsm_equipment *equip);
+
+/* auth info */
+int get_authinfo_by_subscr(struct gsm_auth_info *ainfo,
+                           struct gsm_subscriber *subscr);
+int set_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                            struct gsm_subscriber *subscr);
+int get_authtuple_by_subscr(struct gsm_auth_tuple *atuple,
+                            struct gsm_subscriber *subscr);
+int set_authtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                             struct gsm_subscriber *subscr);
+
+/* SMS store-and-forward */
+int db_sms_store(struct gsm_sms *sms);
+struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id);
+struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net, unsigned long long min_subscr_id);
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr);
+int db_sms_mark_sent(struct gsm_sms *sms);
+
+/* APDU blob storage */
+int db_apdu_blob_store(struct gsm_subscriber *subscr, 
+			u_int8_t apdu_id_flags, u_int8_t len,
+			u_int8_t *apdu);
+
+/* Statistics counter storage */
+int db_store_counter(struct counter *ctr);
+
+#endif /* _DB_H */
diff --git a/openbsc/include/openbsc/debug.h b/openbsc/include/openbsc/debug.h
new file mode 100644
index 0000000..65fd0bb
--- /dev/null
+++ b/openbsc/include/openbsc/debug.h
@@ -0,0 +1,55 @@
+#ifndef _DEBUG_H
+#define _DEBUG_H
+
+#include <stdio.h>
+#include <osmocore/linuxlist.h>
+
+#define DEBUG
+#include <osmocore/logging.h>
+
+/* Debug Areas of the code */
+enum {
+	DRLL,
+	DCC,
+	DMM,
+	DRR,
+	DRSL,
+	DNM,
+	DMNCC,
+	DSMS,
+	DPAG,
+	DMEAS,
+	DMI,
+	DMIB,
+	DMUX,
+	DINP,
+	DSCCP,
+	DMSC,
+	DMGCP,
+	DHO,
+	DDB,
+	DREF,
+	DGPRS,
+	DNS,
+	DBSSGP,
+	Debug_LastEntry,
+};
+
+/* context */
+#define BSC_CTX_LCHAN	0
+#define BSC_CTX_SUBSCR	1
+#define BSC_CTX_BTS	2
+#define BSC_CTX_SCCP	3
+
+/* target */
+
+enum {
+	//DEBUG_FILTER_ALL = 1 << 0,
+	LOG_FILTER_IMSI = 1 << 1,
+};
+
+void log_set_imsi_filter(struct log_target *target, const char *imsi);
+
+extern const struct log_info log_info;
+
+#endif /* _DEBUG_H */
diff --git a/openbsc/include/openbsc/e1_input.h b/openbsc/include/openbsc/e1_input.h
new file mode 100644
index 0000000..1a3d9d6
--- /dev/null
+++ b/openbsc/include/openbsc/e1_input.h
@@ -0,0 +1,168 @@
+#ifndef _E1_INPUT_H
+#define _E1_INPUT_H
+
+#include <stdlib.h>
+#include <netinet/in.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/select.h>
+#include <openbsc/subchan_demux.h>
+
+#define NUM_E1_TS   32
+
+enum e1inp_sign_type {
+	E1INP_SIGN_NONE,
+	E1INP_SIGN_OML,
+	E1INP_SIGN_RSL,
+};
+const char *e1inp_signtype_name(enum e1inp_sign_type tp);
+
+struct e1inp_ts;
+
+struct e1inp_sign_link {
+	/* list of signalling links */
+	struct llist_head list;
+
+	/* to which timeslot do we belong? */
+	struct e1inp_ts *ts;
+
+	enum e1inp_sign_type type;
+
+	/* trx for msg->trx of received msgs */	
+	struct gsm_bts_trx *trx;
+
+	/* msgb queue of to-be-transmitted msgs */
+	struct llist_head tx_list;
+
+	/* SAPI and TEI on the E1 TS */
+	u_int8_t sapi;
+	u_int8_t tei;
+
+	union {
+		struct {
+			u_int8_t channel;
+		} misdn;
+	} driver;
+};
+
+enum e1inp_ts_type {
+	E1INP_TS_TYPE_NONE,
+	E1INP_TS_TYPE_SIGN,
+	E1INP_TS_TYPE_TRAU,
+};
+const char *e1inp_tstype_name(enum e1inp_ts_type tp);
+
+/* A timeslot in the E1 interface */
+struct e1inp_ts {
+	enum e1inp_ts_type type;
+	int num;
+
+	/* to which line do we belong ? */
+	struct e1inp_line *line;
+
+	union {
+		struct {
+			/* list of all signalling links on this TS */
+			struct llist_head sign_links;
+			/* timer when to dequeue next frame */
+			struct timer_list tx_timer;
+		} sign;
+		struct {
+			/* subchannel demuxer for frames from E1 */
+			struct subch_demux demux;
+			/* subchannel muxer for frames to E1 */
+			struct subch_mux mux;
+		} trau;
+	};
+	union {
+		struct {
+			/* mISDN driver has one fd for each ts */
+			struct bsc_fd fd;
+		} misdn;
+		struct {
+			/* ip.access driver has one fd for each ts */
+			struct bsc_fd fd;
+		} ipaccess;
+
+	} driver;
+};
+
+struct e1inp_driver {
+	struct llist_head list;
+	const char *name;
+	int (*want_write)(struct e1inp_ts *ts);
+};	
+
+struct e1inp_line {
+	struct llist_head list;
+	unsigned int num;
+	const char *name;
+
+	/* array of timestlots */
+	struct e1inp_ts ts[NUM_E1_TS];
+
+	struct e1inp_driver *driver;
+	void *driver_data;
+};
+
+/* register a driver with the E1 core */
+int e1inp_driver_register(struct e1inp_driver *drv);
+
+/* register a line with the E1 core */
+int e1inp_line_register(struct e1inp_line *line);
+
+/* ensure a certain line exists, return pointer to it */
+struct e1inp_line *e1inp_line_get_create(u_int8_t e1_nr);
+
+/* find a sign_link for given TEI and SAPI in a TS */
+struct e1inp_sign_link *
+e1inp_lookup_sign_link(struct e1inp_ts *ts, u_int8_t tei,
+			u_int8_t sapi);
+
+/* create a new signalling link in a E1 timeslot */
+struct e1inp_sign_link *
+e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type,
+			struct gsm_bts_trx *trx, u_int8_t tei,
+			u_int8_t sapi);
+
+/* configure and initialize one e1inp_ts */
+int e1inp_ts_config(struct e1inp_ts *ts, struct e1inp_line *line,
+		    enum e1inp_ts_type type);
+
+/* Call from the Stack: configuration of this TS has changed */
+int e1inp_update_ts(struct e1inp_ts *ts);
+
+/* Receive a packet from the E1 driver */
+int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
+		u_int8_t tei, u_int8_t sapi);
+
+/* called by driver if it wants to transmit on a given TS */
+struct msgb *e1inp_tx_ts(struct e1inp_ts *e1i_ts,
+			 struct e1inp_sign_link **sign_link);
+
+/* called by driver in case some kind of link state event */
+int e1inp_event(struct e1inp_ts *ts, int evt, u_int8_t tei, u_int8_t sapi);
+
+/* Write LAPD frames to the fd. */
+void e1_set_pcap_fd(int fd);
+
+/* called by TRAU muxer to obtain the destination mux entity */
+struct subch_mux *e1inp_get_mux(u_int8_t e1_nr, u_int8_t ts_nr);
+
+void e1inp_sign_link_destroy(struct e1inp_sign_link *link);
+int e1inp_line_update(struct e1inp_line *line);
+
+/* e1_config.c */
+int e1_reconfig_ts(struct gsm_bts_trx_ts *ts);
+int e1_reconfig_trx(struct gsm_bts_trx *trx);
+int e1_reconfig_bts(struct gsm_bts *bts);
+
+int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin);
+int ipaccess_setup(struct gsm_network *gsmnet);
+
+extern struct llist_head e1inp_driver_list;
+extern struct llist_head e1inp_line_list;
+
+#endif /* _E1_INPUT_H */
diff --git a/openbsc/include/openbsc/gb_proxy.h b/openbsc/include/openbsc/gb_proxy.h
new file mode 100644
index 0000000..db236b5
--- /dev/null
+++ b/openbsc/include/openbsc/gb_proxy.h
@@ -0,0 +1,42 @@
+#ifndef _GB_PROXY_H
+#define _GB_PROXY_H
+
+#include <sys/types.h>
+
+#include <osmocore/msgb.h>
+
+#include <openbsc/gprs_ns.h>
+#include <vty/command.h>
+
+struct gbproxy_config {
+	/* parsed from config file */
+	u_int32_t nsip_listen_ip;
+	u_int16_t nsip_listen_port;
+
+	u_int32_t nsip_sgsn_ip;
+	u_int16_t nsip_sgsn_port;
+
+	u_int16_t nsip_sgsn_nsei;
+	u_int16_t nsip_sgsn_nsvci;
+
+	/* misc */
+	struct gprs_ns_inst *nsi;
+};
+
+extern struct gbproxy_config gbcfg;
+extern struct cmd_element show_gbproxy_cmd;
+
+/* gb_proxy_vty .c */
+
+int gbproxy_vty_init(void);
+int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg);
+
+
+/* gb_proxy.c */
+
+/* Main input function for Gb proxy */
+int gbprox_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci);
+
+int gbprox_signal(unsigned int subsys, unsigned int signal,
+		  void *handler_data, void *signal_data);
+#endif
diff --git a/openbsc/include/openbsc/gprs_bssgp.h b/openbsc/include/openbsc/gprs_bssgp.h
new file mode 100644
index 0000000..d3ccb12
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_bssgp.h
@@ -0,0 +1,163 @@
+#ifndef _GPRS_BSSGP_H
+#define _GPRS_BSSGP_H
+
+#include <stdint.h>
+
+/* Section 11.3.26 / Table 11.27 */
+enum bssgp_pdu_type {
+	/* PDUs between RL and BSSGP SAPs */
+	BSSGP_PDUT_DL_UNITDATA		= 0x00,
+	BSSGP_PDUT_UL_UNITDATA		= 0x01,
+	BSSGP_PDUT_RA_CAPABILITY	= 0x02,
+	BSSGP_PDUT_PTM_UNITDATA		= 0x03,
+	/* PDUs between GMM SAPs */
+	BSSGP_PDUT_PAGING_PS		= 0x06,
+	BSSGP_PDUT_PAGING_CS		= 0x07,
+	BSSGP_PDUT_RA_CAPA_UDPATE	= 0x08,
+	BSSGP_PDUT_RA_CAPA_UPDATE_ACK	= 0x09,
+	BSSGP_PDUT_RADIO_STATUS		= 0x0a,
+	BSSGP_PDUT_SUSPEND		= 0x0b,
+	BSSGP_PDUT_SUSPEND_ACK		= 0x0c,
+	BSSGP_PDUT_SUSPEND_NACK		= 0x0d,
+	BSSGP_PDUT_RESUME		= 0x0e,
+	BSSGP_PDUT_RESUME_ACK		= 0x0f,
+	BSSGP_PDUT_RESUME_NACK		= 0x10,
+	/* PDus between NM SAPs */
+	BSSGP_PDUT_BVC_BLOCK		= 0x20,
+	BSSGP_PDUT_BVC_BLOCK_ACK	= 0x21,
+	BSSGP_PDUT_BVC_RESET		= 0x22,
+	BSSGP_PDUT_BVC_RESET_ACK	= 0x23,
+	BSSGP_PDUT_BVC_UNBLOCK		= 0x24,
+	BSSGP_PDUT_BVC_UNBLOCK_ACK	= 0x25,
+	BSSGP_PDUT_FLOW_CONTROL_BVC	= 0x26,
+	BSSGP_PDUT_FLOW_CONTROL_BVC_ACK	= 0x27,
+	BSSGP_PDUT_FLOW_CONTROL_MS	= 0x28,
+	BSSGP_PDUT_FLOW_CONTROL_MS_ACK	= 0x29,
+	BSSGP_PDUT_FLUSH_LL		= 0x2a,
+	BSSGP_PDUT_FLUSH_LL_ACK		= 0x2b,
+	BSSGP_PDUT_LLC_DISCARD		= 0x2c,
+	BSSGP_PDUT_SGSN_INVOKE_TRACE	= 0x40,
+	BSSGP_PDUT_STATUS		= 0x41,
+	/* PDUs between PFM SAP's */
+	BSSGP_PDUT_DOWNLOAD_BSS_PFC	= 0x50,
+	BSSGP_PDUT_CREATE_BSS_PFC	= 0x51,
+	BSSGP_PDUT_CREATE_BSS_PFC_ACK	= 0x52,
+	BSSGP_PDUT_CREATE_BSS_PFC_NACK	= 0x53,
+	BSSGP_PDUT_MODIFY_BSS_PFC	= 0x54,
+	BSSGP_PDUT_MODIFY_BSS_PFC_ACK	= 0x55,
+	BSSGP_PDUT_DELETE_BSS_PFC	= 0x56,
+	BSSGP_PDUT_DELETE_BSS_PFC_ACK	= 0x57,
+};
+
+/* Section 10.2.1 and 10.2.2 */
+struct bssgp_ud_hdr {
+	uint8_t pdu_type;
+	uint32_t tlli;
+	uint8_t qos_profile[3];
+	uint8_t data[0];	/* TLV's */
+} __attribute__((packed));
+
+struct bssgp_normal_hdr {
+	uint8_t pdu_type;
+	uint8_t data[0];	/* TLV's */
+};
+
+enum bssgp_iei_type {
+	BSSGP_IE_ALIGNMENT		= 0x00,
+	BSSGP_IE_BMAX_DEFAULT_MS	= 0x01,
+	BSSGP_IE_BSS_AREA_ID		= 0x02,
+	BSSGP_IE_BUCKET_LEAK_RATE	= 0x03,
+	BSSGP_IE_BVCI			= 0x04,
+	BSSGP_IE_BVC_BUCKET_SIZE	= 0x05,
+	BSSGP_IE_BVC_MEASUREMENT	= 0x06,
+	BSSGP_IE_CAUSE			= 0x07,
+	BSSGP_IE_CELL_ID		= 0x08,
+	BSSGP_IE_CHAN_NEEDED		= 0x09,
+	BSSGP_IE_DRX_PARAMS		= 0x0a,
+	BSSGP_IE_EMLPP_PRIO		= 0x0b,
+	BSSGP_IE_FLUSH_ACTION		= 0x0c,
+	BSSGP_IE_IMSI			= 0x0d,
+	BSSGP_IE_LLC_PDU		= 0x0e,
+	BSSGP_IE_LLC_FRAMES_DISCARDED	= 0x0f,
+	BSSGP_IE_LOCATION_AREA		= 0x10,
+	BSSGP_IE_MOBILE_ID		= 0x11,
+	BSSGP_IE_MS_BUCKET_SIZE		= 0x12,
+	BSSGP_IE_MS_RADIO_ACCESS_CAP	= 0x13,
+	BSSGP_IE_OMC_ID			= 0x14,
+	BSSGP_IE_PDU_IN_ERROR		= 0x15,
+	BSSGP_IE_PDU_LIFETIME		= 0x16,
+	BSSGP_IE_PRIORITY		= 0x17,
+	BSSGP_IE_QOS_PROFILE		= 0x18,
+	BSSGP_IE_RADIO_CAUSE		= 0x19,
+	BSSGP_IE_RA_CAP_UPD_CAUSE	= 0x1a,
+	BSSGP_IE_ROUTEING_AREA		= 0x1b,
+	BSSGP_IE_R_DEFAULT_MS		= 0x1c,
+	BSSGP_IE_SUSPEND_REF_NR		= 0x1d,
+	BSSGP_IE_TAG			= 0x1e,
+	BSSGP_IE_TLLI			= 0x1f,
+	BSSGP_IE_TMSI			= 0x20,
+	BSSGP_IE_TRACE_REFERENC		= 0x21,
+	BSSGP_IE_TRACE_TYPE		= 0x22,
+	BSSGP_IE_TRANSACTION_ID		= 0x23,
+	BSSGP_IE_TRIGGER_ID		= 0x24,
+	BSSGP_IE_NUM_OCT_AFF		= 0x25,
+	BSSGP_IE_LSA_ID_LIST		= 0x26,
+	BSSGP_IE_LSA_INFORMATION	= 0x27,
+	BSSGP_IE_PACKET_FLOW_ID		= 0x28,
+	BSSGP_IE_PACKET_FLOW_TIMER	= 0x29,
+	BSSGP_IE_AGG_BSS_QOS_PROFILE	= 0x3a,
+	BSSGP_IE_FEATURE_BITMAP		= 0x3b,
+	BSSGP_IE_BUCKET_FULL_RATIO	= 0x3c,
+	BSSGP_IE_SERVICE_UTRAN_CCO	= 0x3d,
+};
+
+/* Section 11.3.8 / Table 11.10: Cause coding */
+enum gprs_bssgp_cause {
+	BSSGP_CAUSE_PROC_OVERLOAD	= 0x00,
+	BSSGP_CAUSE_EQUIP_FAIL		= 0x01,
+	BSSGP_CAUSE_TRASIT_NET_FAIL	= 0x02,
+	BSSGP_CAUSE_CAPA_GREATER_0KPBS	= 0x03,
+	BSSGP_CAUSE_UNKNOWN_MS		= 0x04,
+	BSSGP_CAUSE_UNKNOWN_BVCI	= 0x05,
+	BSSGP_CAUSE_CELL_TRAF_CONG	= 0x06,
+	BSSGP_CAUSE_SGSN_CONG		= 0x07,
+	BSSGP_CAUSE_OML_INTERV		= 0x08,
+	BSSGP_CAUSE_BVCI_BLOCKED	= 0x09,
+	BSSGP_CAUSE_PFC_CREATE_FAIL	= 0x0a,
+	BSSGP_CAUSE_SEM_INCORR_PDU	= 0x20,
+	BSSGP_CAUSE_INV_MAND_INF	= 0x21,
+	BSSGP_CAUSE_MISSING_MAND_IE	= 0x22,
+	BSSGP_CAUSE_MISSING_COND_IE	= 0x23,
+	BSSGP_CAUSE_UNEXP_COND_IE	= 0x24,
+	BSSGP_CAUSE_COND_IE_ERR		= 0x25,
+	BSSGP_CAUSE_PDU_INCOMP_STATE	= 0x26,
+	BSSGP_CAUSE_PROTO_ERR_UNSPEC	= 0x27,
+	BSSGP_CAUSE_PDU_INCOMP_FEAT	= 0x28,
+};
+
+/* Our implementation */
+
+/* gprs_bssgp_util.c */
+extern struct gprs_ns_inst *bssgp_nsi;
+struct msgb *bssgp_msgb_alloc(void);
+const char *bssgp_cause_str(enum gprs_bssgp_cause cause);
+/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */
+int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
+			 uint16_t bvci, uint16_t ns_bvci);
+/* Chapter 10.4.14: Status */
+int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg);
+
+/* gprs_bssgp.c */
+
+#include <osmocore/tlv.h>
+
+extern int gprs_bssgp_rcvmsg(struct msgb *msg);
+uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf);
+
+/* Wrapper around TLV parser to parse BSSGP IEs */
+static inline int bssgp_tlv_parse(struct tlv_parsed *tp, uint8_t *buf, int len)
+{
+	return tlv_parse(tp, &tvlv_att_def, buf, len, 0, 0);
+}
+
+#endif /* _GPRS_BSSGP_H */
diff --git a/openbsc/include/openbsc/gprs_llc.h b/openbsc/include/openbsc/gprs_llc.h
new file mode 100644
index 0000000..5a6682d
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_llc.h
@@ -0,0 +1,32 @@
+#ifndef _GPRS_LLC_H
+#define _GPRS_LLC_H
+
+#include <stdint.h>
+
+/* Section 4.7 LLC Layer Structure */
+enum gprs_llc_sapi {
+	GPRS_SAPI_GMM		= 1,
+	GPRS_SAPI_TOM2		= 2,
+	GPRS_SAPI_SNDCP3	= 3,
+	GPRS_SAPI_SNDCP5	= 5,
+	GPRS_SAPI_SMS		= 7,
+	GPRS_SAPI_TOM8		= 8,
+	GPRS_SAPI_SNDCP9	= 9,
+	GPRS_SAPI_SNDCP11	= 11,
+};
+
+/* Section 6.4 Commands and Responses */
+enum gprs_llc_u_cmd {
+	GPRS_LLC_U_DM_RESP		= 0x01,
+	GPRS_LLC_U_DISC_CMD		= 0x04,
+	GPRS_LLC_U_UA_RESP		= 0x06,
+	GPRS_LLC_U_SABM_CMD		= 0x07,
+	GPRS_LLC_U_FRMR_RESP		= 0x08,
+	GPRS_LLC_U_XID			= 0x0b,
+	GPRS_LLC_U_NULL_CMD		= 0x00,
+};
+
+int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv);
+int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command);
+
+#endif
diff --git a/openbsc/include/openbsc/gprs_ns.h b/openbsc/include/openbsc/gprs_ns.h
new file mode 100644
index 0000000..c74546a
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_ns.h
@@ -0,0 +1,181 @@
+#ifndef _GPRS_NS_H
+#define _GPRS_NS_H
+
+#include <stdint.h>
+
+/* GPRS Networks Service (NS) messages on the Gb interface
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * 3GPP TS 48.016 version 6.5.0 Release 6 / ETSI TS 148 016 V6.5.0 (2005-11) */
+
+struct gprs_ns_hdr {
+	uint8_t pdu_type;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* TS 08.16, Section 10.3.7, Table 14 */
+enum ns_pdu_type {
+	NS_PDUT_UNITDATA	= 0x00,
+	NS_PDUT_RESET		= 0x02,
+	NS_PDUT_RESET_ACK	= 0x03,
+	NS_PDUT_BLOCK		= 0x04,
+	NS_PDUT_BLOCK_ACK	= 0x05,
+	NS_PDUT_UNBLOCK		= 0x06,
+	NS_PDUT_UNBLOCK_ACK	= 0x07,
+	NS_PDUT_STATUS		= 0x08,
+	NS_PDUT_ALIVE		= 0x0a,
+	NS_PDUT_ALIVE_ACK	= 0x0b,
+	/* TS 48.016 Section 10.3.7, Table 10.3.7.1 */
+	SNS_PDUT_ACK		= 0x0c,
+	SNS_PDUT_ADD		= 0x0d,
+	SNS_PDUT_CHANGE_WEIGHT	= 0x0e,
+	SNS_PDUT_CONFIG		= 0x0f,
+	SNS_PDUT_CONFIG_ACK	= 0x10,
+	SNS_PDUT_DELETE		= 0x11,
+	SNS_PDUT_SIZE		= 0x12,
+	SNS_PDUT_SIZE_ACK	= 0x13,
+};
+
+/* TS 08.16, Section 10.3, Table 12 */
+enum ns_ctrl_ie {
+	NS_IE_CAUSE		= 0x00,
+	NS_IE_VCI		= 0x01,
+	NS_IE_PDU		= 0x02,
+	NS_IE_BVCI		= 0x03,
+	NS_IE_NSEI		= 0x04,
+	/* TS 48.016 Section 10.3, Table 10.3.1 */
+	NS_IE_IPv4_LIST		= 0x05,
+	NS_IE_IPv6_LIST		= 0x06,
+	NS_IE_MAX_NR_NSVC	= 0x07,
+	NS_IE_IPv4_EP_NR	= 0x08,
+	NS_IE_IPv6_EP_NR	= 0x09,
+	NS_IE_RESET_FLAG	= 0x0a,
+	NS_IE_IP_ADDR		= 0x0b,
+};
+
+/* TS 08.16, Section 10.3.2, Table 13 */
+enum ns_cause {
+	NS_CAUSE_TRANSIT_FAIL		= 0x00,
+	NS_CAUSE_OM_INTERVENTION	= 0x01,
+	NS_CAUSE_EQUIP_FAIL		= 0x02,
+	NS_CAUSE_NSVC_BLOCKED		= 0x03,
+	NS_CAUSE_NSVC_UNKNOWN		= 0x04,
+	NS_CAUSE_BVCI_UNKNOWN		= 0x05,
+	NS_CAUSE_SEM_INCORR_PDU		= 0x08,
+	NS_CAUSE_PDU_INCOMP_PSTATE	= 0x0a,
+	NS_CAUSE_PROTO_ERR_UNSPEC	= 0x0b,
+	NS_CAUSE_INVAL_ESSENT_IE	= 0x0c,
+	NS_CAUSE_MISSING_ESSENT_IE	= 0x0d,
+	/* TS 48.016 Section 10.3.2, Table 10.3.2.1 */
+	NS_CAUSE_INVAL_NR_IPv4_EP	= 0x0e,
+	NS_CAUSE_INVAL_NR_IPv6_EP	= 0x0f,
+	NS_CAUSE_INVAL_NR_NS_VC		= 0x10,
+	NS_CAUSE_INVAL_WEIGH		= 0x11,
+	NS_CAUSE_UNKN_IP_EP		= 0x12,
+	NS_CAUSE_UNKN_IP_ADDR		= 0x13,
+	NS_CAUSE_UNKN_IP_TEST_FAILED	= 0x14,
+};
+
+
+/* Our Implementation */
+#include <netinet/in.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/msgb.h>
+#include <osmocore/timer.h>
+#include <osmocore/select.h>
+
+#define NSE_S_BLOCKED	0x0001
+#define NSE_S_ALIVE	0x0002
+
+enum gprs_ns_ll {
+	GPRS_NS_LL_UDP,
+	GPRS_NS_LL_E1,
+};
+
+enum gprs_ns_evt {
+	GPRS_NS_EVT_UNIT_DATA,
+};
+
+struct gprs_nsvc;
+typedef int gprs_ns_cb_t(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+			 struct msgb *msg, uint16_t bvci);
+
+/* An instance of the NS protocol stack */
+struct gprs_ns_inst {
+	/* callback to the user for incoming UNIT DATA IND */
+	gprs_ns_cb_t *cb;
+
+	/* linked lists of all NSVC in this instance */
+	struct llist_head gprs_nsvcs;
+
+	/* which link-layer are we based on? */
+	enum gprs_ns_ll ll;
+
+	union {
+		/* NS-over-IP specific bits */
+		struct {
+			struct bsc_fd fd;
+		} nsip;
+	};
+};
+
+enum nsvc_timer_mode {
+	/* standard timers */
+	NSVC_TIMER_TNS_TEST,
+	NSVC_TIMER_TNS_ALIVE,
+	NSVC_TIMER_TNS_RESET,
+	_NSVC_TIMER_NR,
+};
+
+struct gprs_nsvc {
+	struct llist_head list;
+	struct gprs_ns_inst *nsi;
+
+	uint16_t nsei;		/* end-to-end significance */
+	uint16_t nsvci;	/* uniquely identifies NS-VC at SGSN */
+
+	uint32_t state;
+	uint32_t remote_state;
+
+	struct timer_list timer;
+	enum nsvc_timer_mode timer_mode;
+	int alive_retries;
+
+	int remote_end_is_sgsn;
+
+	union {
+		struct {
+			struct sockaddr_in bts_addr;
+		} ip;
+	};
+};
+
+/* Create a new NS protocol instance */
+struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb);
+
+/* Destroy a NS protocol instance */
+void gprs_ns_destroy(struct gprs_ns_inst *nsi);
+
+/* Listen for incoming GPRS packets */
+int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port);
+
+struct sockaddr_in;
+
+/* main entry point, here incoming NS frames enter */
+int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
+		   struct sockaddr_in *saddr);
+
+/* main function for higher layers (BSSGP) to send NS messages */
+int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg);
+
+int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause);
+int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause);
+int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc);
+
+/* Listen for incoming GPRS packets */
+int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port);
+
+/* Establish a connection (from the BSS) to the SGSN */
+struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi,
+				struct sockaddr_in *dest, uint16_t nsei,
+				uint16_t nsvci);
+#endif
diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
new file mode 100644
index 0000000..bdc0b1c
--- /dev/null
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -0,0 +1,114 @@
+#ifndef _GPRS_SGSN_H
+#define _GPRS_SGSN_H
+
+#include <stdint.h>
+
+/* TS 04.08 4.1.3.3 GMM mobility management states on the network side */
+enum gprs_mm_state {
+	GMM_DEREGISTERED,		/* 4.1.3.3.1.1 */
+	GMM_COMMON_PROC_INIT,		/* 4.1.3.3.1.2 */
+	GMM_REGISTERED_NORMAL,		/* 4.1.3.3.2.1 */
+	GMM_REGISTERED_SUSPENDED,	/* 4.1.3.3.2.2 */
+	GMM_DEREGISTERED_INIT,		/* 4.1.3.3.1.4 */
+};
+
+enum gprs_ciph_algo {
+	GPRS_ALGO_GEA0,
+	GPRS_ALGO_GEA1,
+	GPRS_ALGO_GEA2,
+};
+
+#define MS_RADIO_ACCESS_CAPA
+
+/* According to TS 03.60, Table 5: SGSN MM and PDP Contexts */
+/* Extended by 3GPP TS 23.060, Table 6: SGSN MM and PDP Contexts */
+struct sgsn_mm_ctx {
+	struct llist_head	list;
+
+	char 			imsi[GSM_IMSI_LENGTH];
+	enum gprs_mm_state	mm_state;
+	uint32_t 		p_tmsi;
+	uint32_t 		p_tmsi_sig;
+	char 			imei[GSM_IMEI_LENGTH];
+	/* Opt: Software Version Numbber / TS 23.195 */
+	char 			msisdn[GSM_EXTENSION_LENGTH];
+	struct gprs_ra_id	ra;
+	uint16_t		cell_id;
+	uint32_t		cell_id_age;
+	uint16_t		sac;	/* Iu: Service Area Code */
+	uint32_t		sac_age;/* Iu: Service Area Code age */
+	/* VLR number */
+	uint32_t		new_sgsn_addr;
+	/* Authentication Triplets */
+	/* Kc */
+	/* Iu: CK, IK, KSI */
+	/* CKSN */
+	enum gprs_ciph_algo	ciph_algo;
+	struct {
+		uint8_t	buf[14];	/* 10.5.5.12a */
+		uint8_t	len;
+	} ms_radio_access_capa;
+	struct {
+		uint8_t	buf[4];		/* 10.5.5.12 */
+		uint8_t	len;
+	} ms_network_capa;
+	uint16_t		drx_parms;
+	int			mnrg;	/* MS reported to HLR? */
+	int			ngaf;	/* MS reported to MSC/VLR? */
+	int			ppf;	/* paging for GPRS + non-GPRS? */
+	/* SMS Parameters */
+	int			recovery;
+	uint8_t			radio_prio_sms;
+
+	struct llist_head	pdp_list;
+
+	/* Additional bits not present in the GSM TS */
+	uint32_t		tlli;
+	struct timer_list	timer;
+	unsigned int		T;
+};
+
+enum pdp_ctx_state {
+	PDP_STAE_NONE,
+};
+
+enum pdp_type {
+	PDP_TYPE_NONE,
+};
+
+struct sgsn_pdp_ctx {
+	struct llist_head	list;
+
+	unsigned int		id;
+	enum pdp_ctx_state	state;
+	enum pdp_type		type;
+	uint32_t		addresss;
+	char 			*apn_subscribed;
+	char 			*apn_used;
+	uint16_t		nsapi;
+	uint8_t			ti;	/* transaction identifier */
+	uint32_t		ggsn_in_use;
+	int			vplmn_allowed;
+	uint32_t		qos_profile_subscr;
+	uint32_t		qos_profile_req;
+	uint32_t		qos_profile_neg;
+	uint8_t			radio_prio;
+	uint32_t		tx_npdu_nr;
+	uint32_t		rx_npdu_nr;
+	uint32_t		tx_gtp_snd;
+	uint32_t		rx_gtp_snu;
+	uint32_t		charging_id;
+	int			reordering_reqd;
+};
+
+/* look-up a SGSN MM context based on TLLI + RAI */
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
+					const struct gprs_ra_id *raid);
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t tmsi);
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi);
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
+					const struct gprs_ra_id *raid);
+
+#endif /* _GPRS_SGSN_H */
diff --git a/openbsc/include/openbsc/gsm_04_08.h b/openbsc/include/openbsc/gsm_04_08.h
new file mode 100644
index 0000000..74dcbe5
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_08.h
@@ -0,0 +1,63 @@
+#ifndef _GSM_04_08_H
+#define _GSM_04_08_H
+
+#include <openbsc/meas_rep.h>
+
+#include <osmocore/protocol/gsm_04_08.h>
+#include <osmocore/gsm48.h>
+
+struct msgb;
+struct gsm_bts;
+struct gsm_subscriber;
+struct gsm_network;
+struct gsm_trans;
+
+#define GSM48_ALLOC_SIZE	1024
+#define GSM48_ALLOC_HEADROOM	128
+
+static inline struct msgb *gsm48_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(GSM48_ALLOC_SIZE, GSM48_ALLOC_HEADROOM,
+				   "GSM 04.08");
+}
+
+/* config options controlling the behaviour of the lower leves */
+void gsm0408_allow_everyone(int allow);
+
+int gsm0408_rcvmsg(struct msgb *msg, u_int8_t link_id);
+enum gsm_chan_t get_ctype_by_chreq(struct gsm_bts *bts, u_int8_t ra, int neci);
+enum gsm_chreq_reason_t get_reason_by_chreq(struct gsm_bts *bts, u_int8_t ra, int neci);
+
+int gsm48_tx_mm_info(struct gsm_lchan *lchan);
+int gsm48_tx_mm_auth_req(struct gsm_lchan *lchan, u_int8_t *rand, int key_seq);
+int gsm48_tx_mm_auth_rej(struct gsm_lchan *lchan);
+int gsm48_sendmsg(struct msgb *msg, struct gsm_trans *trans);
+
+int gsm48_send_rr_release(struct gsm_lchan *lchan);
+int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv);
+int gsm48_send_rr_app_info(struct gsm_lchan *lchan, u_int8_t apdu_id,
+			   u_int8_t apdu_len, const u_int8_t *apdu);
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, u_int8_t power_class);
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
+		      u_int8_t power_command, u_int8_t ho_ref);
+
+int bsc_upqueue(struct gsm_network *net);
+
+int mncc_send(struct gsm_network *net, int msg_type, void *arg);
+
+/* convert a ASCII phone number to call-control BCD */
+int encode_bcd_number(u_int8_t *bcd_lv, u_int8_t max_len,
+		      int h_len, const char *input);
+int decode_bcd_number(char *output, int output_len, const u_int8_t *bcd_lv,
+		      int h_len);
+
+int send_siemens_mrpci(struct gsm_lchan *lchan, u_int8_t *classmark2_lv);
+int gsm48_paging_extract_mi(struct msgb *msg, char *mi_string, u_int8_t *mi_type);
+int gsm48_handle_paging_resp(struct msgb *msg, struct gsm_subscriber *subscr);
+
+int gsm48_lchan_modify(struct gsm_lchan *lchan, u_int8_t lchan_mode);
+int gsm48_rx_rr_modif_ack(struct msgb *msg);
+int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg);
+
+
+#endif
diff --git a/openbsc/include/openbsc/gsm_04_08_gprs.h b/openbsc/include/openbsc/gsm_04_08_gprs.h
new file mode 100644
index 0000000..344b277
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_08_gprs.h
@@ -0,0 +1,350 @@
+#ifndef _GSM48_GPRS_H
+#define _GSM48_GPRS_H
+
+#include <stdint.h>
+
+/* Table 10.4 / 10.4a, GPRS Mobility Management (GMM) */
+#define GSM48_MT_GMM_ATTACH_REQ		0x01
+#define GSM48_MT_GMM_ATTACH_ACK		0x02
+#define GSM48_MT_GMM_ATTACH_COMPL	0x03
+#define GSM48_MT_GMM_ATTACH_REJ		0x04
+#define GSM48_MT_GMM_DETACH_REQ		0x05
+#define GSM48_MT_GMM_DETACH_ACK		0x06
+
+#define GSM48_MT_GMM_RA_UPD_REQ		0x08
+#define GSM48_MT_GMM_RA_UPD_ACK		0x09
+#define GSM48_MT_GMM_RA_UPD_COMPL	0x0a
+#define GSM48_MT_GMM_RA_UPD_REJ		0x0b
+
+#define GSM48_MT_GMM_PTMSI_REALL_CMD	0x10
+#define GSM48_MT_GMM_PTMSI_REALL_COMPL	0x11
+#define GSM48_MT_GMM_AUTH_CIPH_REQ	0x12
+#define GSM48_MT_GMM_AUTH_CIPH_RESP	0x13
+#define GSM48_MT_GMM_AUTH_CIPH_REJ	0x14
+#define GSM48_MT_GMM_ID_REQ		0x15
+#define GSM48_MT_GMM_ID_RESP		0x16
+#define GSM48_MT_GMM_STATUS		0x20
+#define GSM48_MT_GMM_INFO		0x21
+
+/* Table 10.4a, GPRS Session Management (GSM) */
+#define GSM48_MT_GSM_ACT_PDP_REQ	0x41
+#define GSM48_MT_GSM_ACT_PDP_ACK	0x42
+#define GSM48_MT_GSM_ACT_PDP_REJ	0x43
+#define GSM48_MT_GSM_REQ_PDP_ACT	0x44
+#define GSM48_MT_GSM_REQ_PDP_ACT_REJ	0x45
+#define GSM48_MT_GSM_DEACT_PDP_REQ	0x46
+#define GSM48_MT_GSM_DEACT_PDP_ACK	0x47
+#define GSM48_MT_GSM_ACT_AA_PDP_REQ	0x50
+#define GSM48_MT_GSM_ACT_AA_PDP_ACK	0x51
+#define GSM48_MT_GSM_ACT_AA_PDP_REJ	0x52
+#define GSM48_MT_GSM_DEACT_AA_PDP_REQ	0x53
+#define GSM48_MT_GSM_DEACT_AA_PDP_ACK	0x54
+#define GSM48_MT_GSM_STATUS		0x55
+
+/* Chapter 10.5.5.2 / Table 10.5.135 */
+#define GPRS_ATT_T_ATTACH		1
+#define GPRS_ATT_T_ATT_WHILE_IMSI	2
+#define GPRS_ATT_T_COMBINED		3
+
+/* Chapter 10.5.5.18 / Table 105.150 */
+#define GPRS_UPD_T_RA			0
+#define GPRS_UPD_T_RA_LA		1
+#define GPRS_UPD_T_RA_LA_IMSI_ATT	2
+#define GPRS_UPD_T_PERIODIC		3
+
+enum gsm48_gprs_ie_mm {
+	GSM48_IE_GMM_TIMER_READY	= 0x17, /* 10.5.7.3 */
+	GSM48_IE_GMM_PTMSI_SIG		= 0x19,	/* 10.5.5.8 */
+	GSM48_IE_GMM_AUTH_RAND		= 0x21, /* 10.5.3.1 */
+	GSM48_IE_GMM_AUTH_SRES		= 0x22, /* 10.5.3.2 */
+	GSM48_IE_GMM_IMEISV		= 0x23, /* 10.5.1.4 */
+	GSM48_IE_GMM_DRX_PARAM		= 0x27,	/* 10.5.5.6 */
+	GSM48_IE_GMM_MS_NET_CAPA	= 0x31,	/* 10.5.5.12 */
+};
+
+enum gsm48_gprs_ie_sm {
+	GSM48_IE_GSM_APN		= 0x28,	/* 10.5.6.1 */
+	GSM48_IE_GSM_PROTO_CONF_OPT	= 0x27,	/* 10.5.6.3 */
+	GSM48_IE_GSM_PDP_ADDR		= 0x2b, /* 10.5.6.4 */
+	GSM48_IE_GSM_AA_TMR		= 0x29,	/* 10.5.7.3 */
+	GSM48_IE_GSM_NAME_FULL		= 0x43, /* 10.5.3.5a */
+	GSM48_IE_GSM_NAME_SHORT		= 0x45, /* 10.5.3.5a */
+	GSM48_IE_GSM_TIMEZONE		= 0x46, /* 10.5.3.8 */
+	GSM48_IE_GSM_UTC_AND_TZ		= 0x47, /* 10.5.3.9 */
+	GSM48_IE_GSM_LSA_ID		= 0x48, /* 10.5.3.11 */
+};
+
+/* Chapter 9.4.15 / Table 9.4.15 */
+struct gsm48_ra_upd_ack {
+	uint8_t force_stby:4,	/* 10.5.5.7 */
+		 upd_result:4;	/* 10.5.5.17 */
+	uint8_t ra_upd_timer;	/* 10.5.7.3 */
+	struct gsm48_ra_id ra_id; /* 10.5.5.15 */
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 10.5.7.3 */
+enum gsm48_gprs_tmr_unit {
+	GPRS_TMR_2SECONDS	= 0 << 5,
+	GPRS_TMR_MINUTE		= 1 << 5,
+	GPRS_TMR_6MINUTE	= 2 << 5,
+	GPRS_TMR_DEACTIVATED	= 3 << 5,
+};
+
+/* Chapter 9.4.2 / Table 9.4.2 */
+struct gsm48_attach_ack {
+	uint8_t att_result:4,	/* 10.5.5.7 */
+		 force_stby:4;	/* 10.5.5.1 */
+	uint8_t ra_upd_timer;	/* 10.5.7.3 */
+	uint8_t radio_prio;	/* 10.5.7.2 */
+	struct gsm48_ra_id ra_id; /* 10.5.5.15 */
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 9.5.1 / Table 9.5.1 */
+struct gsm48_act_pdp_ctx_req {
+	uint8_t req_nsapi;
+	uint8_t req_llc_sapi;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 10.5.5.14 / Table 10.5.147 */
+enum gsm48_gmm_cause {
+	GMM_CAUSE_IMSI_UNKNOWN		= 0x02,
+	GMM_CAUSE_ILLEGAL_MS		= 0x03,
+	GMM_CAUSE_ILLEGAL_ME		= 0x06,
+	GMM_CAUSE_GPRS_NOTALLOWED	= 0x07,
+	GMM_CAUSE_GPRS_OTHER_NOTALLOWED	= 0x08,
+	GMM_CAUSE_MS_ID_NOT_DERIVED	= 0x09,
+	GMM_CAUSE_IMPL_DETACHED		= 0x0a,
+	GMM_CAUSE_PLMN_NOTALLOWED	= 0x0b,
+	GMM_CAUSE_LA_NOTALLOWED		= 0x0c,
+	GMM_CAUSE_ROAMING_NOTALLOWED	= 0x0d,
+	GMM_CAUSE_NO_GPRS_PLMN		= 0x0e,
+	GMM_CAUSE_MSC_TEMP_NOTREACH	= 0x10,
+	GMM_CAUSE_NET_FAIL		= 0x11,
+	GMM_CAUSE_CONGESTION		= 0x16,
+	GMM_CAUSE_SEM_INCORR_MSG	= 0x5f,
+	GMM_CAUSE_INV_MAND_INFO		= 0x60,
+	GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL	= 0x61,
+	GMM_CAUSE_MSGT_INCOMP_P_STATE	= 0x62,
+	GMM_CAUSE_IE_NOTEXIST_NOTIMPL	= 0x63,
+	GMM_CAUSE_COND_IE_ERR		= 0x64,
+	GMM_CAUSE_MSG_INCOMP_P_STATE	= 0x65,
+	GMM_CAUSE_PROTO_ERR_UNSPEC	= 0x6f,
+};
+
+/* Chapter 10.4.6.6 / Table 10.5.157 */
+enum gsm48_gsm_cause {
+	GSM_CAUSE_INSUFF_RSRC		= 0x1a,
+	GSM_CAUSE_MISSING_APN		= 0x1b,
+	GSM_CAUSE_UNKNOWN_PDP		= 0x1c,
+	GSM_CAUSE_AUTH_FAILED		= 0x1d,
+	GSM_CAUSE_ACT_REJ_GGSN		= 0x1e,
+	GSM_CAUSE_ACT_REJ_UNSPEC	= 0x1f,
+	GSM_CAUSE_SERV_OPT_NOTSUPP	= 0x20,
+	GSM_CAUSE_REQ_SERV_OPT_NOTSUB	= 0x21,
+	GSM_CAUSE_SERV_OPT_TEMP_OOO	= 0x22,
+	GSM_CAUSE_NSAPI_IN_USE		= 0x23,
+	GSM_CAUSE_DEACT_REGULAR		= 0x24,
+	GSM_CAUSE_QOS_NOT_ACCEPTED	= 0x25,
+	GSM_CAUSE_NET_FAIL		= 0x26,
+	GSM_CAUSE_REACT_RQD		= 0x27,
+	GSM_CAUSE_FEATURE_NOTSUPP	= 0x28,
+	GSM_CAUSE_INVALID_TRANS_ID	= 0x51,
+	GSM_CAUSE_SEM_INCORR_MSG	= 0x5f,
+	GSM_CAUSE_INV_MAND_INFO		= 0x60,
+	GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL	= 0x61,
+	GSM_CAUSE_MSGT_INCOMP_P_STATE	= 0x62,
+	GSM_CAUSE_IE_NOTEXIST_NOTIMPL	= 0x63,
+	GSM_CAUSE_COND_IE_ERR		= 0x64,
+	GSM_CAUSE_MSG_INCOMP_P_STATE	= 0x65,
+	GSM_CAUSE_PROTO_ERR_UNSPEC	= 0x6f,
+};
+
+/* Section 6.1.2.2: Session management states on the network side */
+enum gsm48_pdp_state {
+	PDP_S_INACTIVE,
+	PDP_S_ACTIVE_PENDING,
+	PDP_S_ACTIVE,
+	PDP_S_INACTIVE_PENDING,
+	PDP_S_MODIFY_PENDING,
+};
+
+/* Table 10.5.155/3GPP TS 24.008 */
+enum gsm48_pdp_type_org {
+	PDP_TYPE_ORG_ETSI		= 0x00,
+	PDP_TYPE_ORG_IETF		= 0x01,
+};
+enum gsm48_pdp_type_nr {
+	PDP_TYPE_N_ETSI_RESERVED	= 0x00,
+	PDP_TYPE_N_ETSI_PPP		= 0x01,
+	PDP_TYPE_N_IETF_IPv4		= 0x21,
+	PDP_TYPE_N_IETF_IPv6		= 0x57,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_reliab_class {
+	GSM48_QOS_RC_LLC_ACK_RLC_ACK_DATA_PROT	= 2,
+	GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT	= 3,
+	GSM48_QOS_RC_LLC_UN_RLC_UN_PROT_DATA	= 4,
+	GSM48_QOS_RC_LLC_UN_RLC_UN_DATA_UN	= 5,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_preced_class {
+	GSM48_QOS_PC_HIGH	= 1,
+	GSM48_QOS_PC_NORMAL	= 2,
+	GSM48_QOS_PC_LOW	= 3,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_peak_tput {
+	GSM48_QOS_PEAK_TPUT_1000bps	= 1,
+	GSM48_QOS_PEAK_TPUT_2000bps	= 2,
+	GSM48_QOS_PEAK_TPUT_4000bps	= 3,
+	GSM48_QOS_PEAK_TPUT_8000bps	= 4,
+	GSM48_QOS_PEAK_TPUT_16000bps	= 5,
+	GSM48_QOS_PEAK_TPUT_32000bps	= 6,
+	GSM48_QOS_PEAK_TPUT_64000bps	= 7,
+	GSM48_QOS_PEAK_TPUT_128000bps	= 8,
+	GSM48_QOS_PEAK_TPUT_256000bps	= 9,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_mean_tput {
+	GSM48_QOS_MEAN_TPUT_100bph	= 1,
+	GSM48_QOS_MEAN_TPUT_200bph	= 2,
+	GSM48_QOS_MEAN_TPUT_500bph	= 3,
+	GSM48_QOS_MEAN_TPUT_1000bph	= 4,
+	GSM48_QOS_MEAN_TPUT_2000bph	= 5,
+	GSM48_QOS_MEAN_TPUT_5000bph	= 6,
+	GSM48_QOS_MEAN_TPUT_10000bph	= 7,
+	GSM48_QOS_MEAN_TPUT_20000bph	= 8,
+	GSM48_QOS_MEAN_TPUT_50000bph	= 9,
+	GSM48_QOS_MEAN_TPUT_100kbph	= 10,
+	GSM48_QOS_MEAN_TPUT_200kbph	= 11,
+	GSM48_QOS_MEAN_TPUT_500kbph	= 0xc,
+	GSM48_QOS_MEAN_TPUT_1Mbph	= 0xd,
+	GSM48_QOS_MEAN_TPUT_2Mbph	= 0xe,
+	GSM48_QOS_MEAN_TPUT_5Mbph	= 0xf,
+	GSM48_QOS_MEAN_TPUT_10Mbph	= 0x10,
+	GSM48_QOS_MEAN_TPUT_20Mbph	= 0x11,
+	GSM48_QOS_MEAN_TPUT_50Mbph	= 0x12,
+	GSM48_QOS_MEAN_TPUT_BEST_EFFORT	= 0x1f,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_err_sdu {
+	GSM48_QOS_ERRSDU_NODETECT	= 1,
+	GSM48_QOS_ERRSDU_YES		= 2,
+	GSM48_QOS_ERRSDU_NO		= 3,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_deliv_order {
+	GSM48_QOS_DO_ORDERED		= 1,
+	GSM48_QOS_DO_UNORDERED		= 2,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_traf_class {
+	GSM48_QOS_TC_CONVERSATIONAL	= 1,
+	GSM48_QOS_TC_STREAMING		= 2,
+	GSM48_QOS_TC_INTERACTIVE	= 3,
+	GSM48_QOS_TC_BACKGROUND		= 4,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_max_sdu_size {
+	/* values below in 10 octet granularity */
+	GSM48_QOS_MAXSDU_1502		= 0x97,
+	GSM48_QOS_MAXSDU_1510		= 0x98,
+	GSM48_QOS_MAXSDU_1520		= 0x99,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_max_bitrate {
+	GSM48_QOS_MBRATE_1k		= 0x01,
+	GSM48_QOS_MBRATE_63k		= 0x3f,
+	GSM48_QOS_MBRATE_64k		= 0x40,
+	GSM48_QOS_MBRATE_568k		= 0x7f,
+	GSM48_QOS_MBRATE_576k		= 0x80,
+	GSM48_QOS_MBRATE_8640k		= 0xfe,
+	GSM48_QOS_MBRATE_0k		= 0xff,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_resid_ber {
+	GSM48_QOS_RBER_5e_2		= 0x01,
+	GSM48_QOS_RBER_1e_2		= 0x02,
+	GSM48_QOS_RBER_5e_3		= 0x03,
+	GSM48_QOS_RBER_4e_3		= 0x04,
+	GSM48_QOS_RBER_1e_3		= 0x05,
+	GSM48_QOS_RBER_1e_4		= 0x06,
+	GSM48_QOS_RBER_1e_5		= 0x07,
+	GSM48_QOS_RBER_1e_6		= 0x08,
+	GSM48_QOS_RBER_6e_8		= 0x09,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_sdu_err {
+	GSM48_QOS_SERR_1e_2		= 0x01,
+	GSM48_QOS_SERR_7e_2		= 0x02,
+	GSM48_QOS_SERR_1e_3		= 0x03,
+	GSM48_QOS_SERR_1e_4		= 0x04,
+	GSM48_QOS_SERR_1e_5		= 0x05,
+	GSM48_QOS_SERR_1e_6		= 0x06,
+	GSM48_QOS_SERR_1e_1		= 0x07,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+struct gsm48_qos {
+	/* octet 3 */
+	uint8_t reliab_class:3;
+	uint8_t delay_class:3;
+	uint8_t spare:2;
+	/* octet 4 */
+	uint8_t preced_class:3;
+	uint8_t spare2:1;
+	uint8_t peak_tput:4;
+	/* octet 5 */
+	uint8_t mean_tput:5;
+	uint8_t spare3:3;
+	/* octet 6 */
+	uint8_t deliv_err_sdu:3;
+	uint8_t deliv_order:2;
+	uint8_t traf_class:3;
+	/* octet 7 */
+	uint8_t max_sdu_size;
+	/* octet 8 */
+	uint8_t max_bitrate_up;
+	/* octet 9 */
+	uint8_t max_bitrate_down;
+	/* octet 10 */
+	uint8_t sdu_err_ratio:4;
+	uint8_t resid_ber:4;
+	/* octet 11 */
+	uint8_t handling_prio:2;
+	uint8_t xfer_delay:6;
+	/* octet 12 */
+	uint8_t guar_bitrate_up;
+	/* octet 13 */
+	uint8_t guar_bitrate_down;
+	/* octet 14 */
+	uint8_t src_stats_desc:4;
+	uint8_t sig_ind:1;
+	uint8_t spare5:3;
+	/* octet 15 */
+	uint8_t max_bitrate_down_ext;
+	/* octet 16 */
+	uint8_t guar_bitrate_down_ext;
+};
+
+
+int gprs_tlli_type(uint32_t tlli);
+
+struct gsm_bts *gsm48_bts_by_ra_id(struct gsm_network *net,
+				   const uint8_t *buf, unsigned int len);
+
+#endif /* _GSM48_GPRS_H */
diff --git a/openbsc/include/openbsc/gsm_04_11.h b/openbsc/include/openbsc/gsm_04_11.h
new file mode 100644
index 0000000..8127af1
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_11.h
@@ -0,0 +1,36 @@
+#ifndef _GSM_04_11_H
+#define _GSM_04_11_H
+
+#include <osmocore/protocol/gsm_04_11.h>
+
+/* SMS deliver PDU */
+struct sms_deliver {
+	u_int8_t mti:2;		/* message type indicator */
+	u_int8_t mms:1;		/* more messages to send */
+	u_int8_t rp:1;		/* reply path */
+	u_int8_t udhi:1;	/* user data header indicator */
+	u_int8_t sri:1;		/* status report indication */
+	u_int8_t *orig_addr;	/* originating address */
+	u_int8_t pid;		/* protocol identifier */
+	u_int8_t dcs;		/* data coding scheme */
+				/* service centre time stamp */
+	u_int8_t ud_len;	/* user data length */
+	u_int8_t *user_data;	/* user data */
+
+	u_int8_t msg_ref;	/* message reference */
+	u_int8_t *smsc;
+};
+
+struct msgb;
+
+int gsm0411_rcv_sms(struct msgb *msg, u_int8_t link_id);
+
+int gsm411_send_sms_lchan(struct gsm_subscriber_connection *conn, struct gsm_sms *sms);
+
+struct gsm_sms *sms_alloc(void);
+void sms_free(struct gsm_sms *sms);
+
+void _gsm411_sms_trans_free(struct gsm_trans *trans);
+int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
+			   struct gsm_sms *sms);
+#endif
diff --git a/openbsc/include/openbsc/gsm_04_80.h b/openbsc/include/openbsc/gsm_04_80.h
new file mode 100644
index 0000000..b5ab1c6
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_04_80.h
@@ -0,0 +1,22 @@
+#ifndef _GSM_04_80_H
+#define _GSM_04_80_H
+
+#include <osmocore/msgb.h>
+#include <osmocore/protocol/gsm_04_80.h>
+
+#define MAX_LEN_USSD_STRING	31
+
+struct ussd_request {
+			char text[MAX_LEN_USSD_STRING + 1];
+			u_int8_t transaction_id;
+			u_int8_t invoke_id;
+};
+
+int gsm0480_decode_ussd_request(const struct msgb *msg, 
+				struct ussd_request *request); 
+int gsm0480_send_ussd_response(const struct msgb *in_msg, const char* response_text, 
+						const struct ussd_request *req);
+int gsm0480_send_ussd_reject(const struct msgb *msg, 
+				const struct ussd_request *request);
+
+#endif
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
new file mode 100644
index 0000000..03794c2
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -0,0 +1,752 @@
+#ifndef _GSM_DATA_H
+#define _GSM_DATA_H
+
+#include <sys/types.h>
+
+enum gsm_phys_chan_config {
+	GSM_PCHAN_NONE,
+	GSM_PCHAN_CCCH,
+	GSM_PCHAN_CCCH_SDCCH4,
+	GSM_PCHAN_TCH_F,
+	GSM_PCHAN_TCH_H,
+	GSM_PCHAN_SDCCH8_SACCH8C,
+	GSM_PCHAN_PDCH,		/* GPRS PDCH */
+	GSM_PCHAN_TCH_F_PDCH,	/* TCH/F if used, PDCH otherwise */
+	GSM_PCHAN_UNKNOWN,
+};
+
+enum gsm_chan_t {
+	GSM_LCHAN_NONE,
+	GSM_LCHAN_SDCCH,
+	GSM_LCHAN_TCH_F,
+	GSM_LCHAN_TCH_H,
+	GSM_LCHAN_UNKNOWN,
+};
+
+/* RRLP mode of operation */
+enum rrlp_mode {
+	RRLP_MODE_NONE,
+	RRLP_MODE_MS_BASED,
+	RRLP_MODE_MS_PREF,
+	RRLP_MODE_ASS_PREF,
+};
+
+/* Channel Request reason */
+enum gsm_chreq_reason_t {
+	GSM_CHREQ_REASON_EMERG,
+	GSM_CHREQ_REASON_PAG,
+	GSM_CHREQ_REASON_CALL,
+	GSM_CHREQ_REASON_LOCATION_UPD,
+	GSM_CHREQ_REASON_OTHER,
+};
+
+#include <osmocore/timer.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/mncc.h>
+#include <osmocore/tlv.h>
+#include <osmocore/bitvec.h>
+#include <osmocore/statistics.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/utils.h>
+
+#define TRX_NR_TS	8
+#define TS_MAX_LCHAN	8
+
+#define HARDCODED_ARFCN 123
+#define HARDCODED_TSC	7
+#define HARDCODED_BSIC	0x3f	/* NCC = 7 / BCC = 7 */
+
+/* for multi-drop config */
+#define HARDCODED_BTS0_TS	1
+#define HARDCODED_BTS1_TS	6
+#define HARDCODED_BTS2_TS	11
+
+enum gsm_hooks {
+	GSM_HOOK_NM_SWLOAD,
+	GSM_HOOK_RR_PAGING,
+};
+
+enum gsm_paging_event {
+	GSM_PAGING_SUCCEEDED,
+	GSM_PAGING_EXPIRED,
+	GSM_PAGING_OOM,
+};
+
+enum bts_gprs_mode {
+	BTS_GPRS_NONE = 0,
+	BTS_GPRS_GPRS = 1,
+	BTS_GPRS_EGPRS = 2,
+};
+
+/* the data structure stored in msgb->cb for openbsc apps */
+struct openbsc_msgb_cb {
+	unsigned char *bssgph;
+	unsigned char *llch;
+
+	/* Cell Identifier */
+	unsigned char *bssgp_cell_id;
+
+	/* Identifiers of a BTS, equal to 'struct bssgp_bts_ctx' */
+	u_int16_t nsei;
+	u_int16_t bvci;
+
+	/* Identifier of a MS (inside BTS), equal to 'struct sgsn_mm_ctx' */
+	u_int32_t tlli;
+} __attribute__((packed));
+#define OBSC_MSGB_CB(__msgb)	((struct openbsc_msgb_cb *)&((__msgb)->cb[0]))
+#define msgb_tlli(__x)		OBSC_MSGB_CB(__x)->tlli
+#define msgb_nsei(__x)		OBSC_MSGB_CB(__x)->nsei
+#define msgb_bvci(__x)		OBSC_MSGB_CB(__x)->bvci
+#define msgb_gmmh(__x)		(__x)->l3h
+#define msgb_bssgph(__x)	OBSC_MSGB_CB(__x)->bssgph
+#define msgb_bssgp_len(__x)	((__x)->tail - (uint8_t *)msgb_bssgph(__x))
+#define msgb_bcid(__x)		OBSC_MSGB_CB(__x)->bssgp_cell_id
+#define msgb_llch(__x)		OBSC_MSGB_CB(__x)->llch
+
+struct msgb;
+typedef int gsm_cbfn(unsigned int hooknum,
+		     unsigned int event,
+		     struct msgb *msg,
+		     void *data, void *param);
+
+/*
+ * Use the channel. As side effect the lchannel recycle timer
+ * will be started.
+ */
+#define LCHAN_RELEASE_TIMEOUT 20, 0
+#define use_subscr_con(con) \
+	do {	(con)->use_count++; \
+		DEBUGP(DREF, "lchan (bts=%d,trx=%d,ts=%d,ch=%d) increases usage to: %d\n", \
+			(con)->lchan->ts->trx->bts->nr, (con)->lchan->ts->trx->nr, (con)->lchan->ts->nr, \
+			(con)->lchan->nr, (con)->use_count); \
+		bsc_schedule_timer(&(con)->release_timer, LCHAN_RELEASE_TIMEOUT); } while(0);
+
+#define put_subscr_con(con) \
+	do { (con)->use_count--; \
+		DEBUGP(DREF, "lchan (bts=%d,trx=%d,ts=%d,ch=%d) decreases usage to: %d\n", \
+			(con)->lchan->ts->trx->bts->nr, (con)->lchan->ts->trx->nr, (con)->lchan->ts->nr, \
+			(con)->lchan->nr, (con)->use_count); \
+	} while(0);
+
+
+/* Real authentication information containing Ki */
+enum gsm_auth_algo {
+	AUTH_ALGO_NONE,
+	AUTH_ALGO_XOR,
+	AUTH_ALGO_COMP128v1,
+};
+
+struct gsm_auth_info {
+	enum gsm_auth_algo auth_algo;
+	unsigned int a3a8_ki_len;
+	u_int8_t a3a8_ki[16];
+};
+
+struct gsm_auth_tuple {
+	int use_count;
+	int key_seq;
+	u_int8_t rand[16];
+	u_int8_t sres[4];
+	u_int8_t kc[8];
+};
+
+
+struct gsm_lchan;
+struct gsm_subscriber;
+struct gsm_mncc;
+struct rtp_socket;
+
+/* Network Management State */
+struct gsm_nm_state {
+	u_int8_t operational;
+	u_int8_t administrative;
+	u_int8_t availability;
+};
+
+/*
+ * LOCATION UPDATING REQUEST state
+ *
+ * Our current operation is:
+ *	- Get imei/tmsi
+ *	- Accept/Reject according to global policy
+ */
+struct gsm_loc_updating_operation {
+        struct timer_list updating_timer;
+	unsigned int waiting_for_imsi : 1;
+	unsigned int waiting_for_imei : 1;
+};
+
+/* Maximum number of neighbor cells whose average we track */
+#define MAX_NEIGH_MEAS		10
+/* Maximum size of the averaging window for neighbor cells */
+#define MAX_WIN_NEIGH_AVG	10
+
+/* processed neighbor measurements for one cell */
+struct neigh_meas_proc {
+	u_int16_t arfcn;
+	u_int8_t bsic;
+	u_int8_t rxlev[MAX_WIN_NEIGH_AVG];
+	unsigned int rxlev_cnt;
+	u_int8_t last_seen_nr;
+};
+
+#define MAX_A5_KEY_LEN	(128/8)
+#define RSL_ENC_ALG_A5(x)	(x+1)
+
+/* is the data link established? who established it? */
+#define LCHAN_SAPI_UNUSED	0
+#define LCHAN_SAPI_MS		1
+#define LCHAN_SAPI_NET		2
+
+/* state of a logical channel */
+enum gsm_lchan_state {
+	LCHAN_S_NONE,		/* channel is not active */
+	LCHAN_S_ACT_REQ,	/* channel activatin requested */
+	LCHAN_S_ACTIVE,		/* channel is active and operational */
+	LCHAN_S_REL_REQ,	/* channel release has been requested */
+	LCHAN_S_INACTIVE,	/* channel is set inactive */
+};
+
+/* the per subscriber data for lchan */
+struct gsm_subscriber_connection {
+	/* To whom we are allocated at the moment */
+	struct gsm_subscriber *subscr;
+
+	/* Timer started to release the channel */
+	struct timer_list release_timer;
+
+	/*
+	 * Operations that have a state and might be pending
+	 */
+	struct gsm_loc_updating_operation *loc_operation;
+
+	/* use count. how many users use this channel */
+	unsigned int use_count;
+
+	/* Are we part of a special "silent" call */
+	int silent_call;
+
+	/* back pointers */
+	struct gsm_lchan *lchan;
+	struct gsm_bts *bts;
+};
+
+struct gsm_lchan {
+	/* The TS that we're part of */
+	struct gsm_bts_trx_ts *ts;
+	/* The logical subslot number in the TS */
+	u_int8_t nr;
+	/* The logical channel type */
+	enum gsm_chan_t type;
+	/* RSL channel mode */
+	enum rsl_cmod_spd rsl_cmode;
+	/* If TCH, traffic channel mode */
+	enum gsm48_chan_mode tch_mode;
+	/* State */
+	enum gsm_lchan_state state;
+	/* Power levels for MS and BTS */
+	u_int8_t bs_power;
+	u_int8_t ms_power;
+	/* Encryption information */
+	struct {
+		u_int8_t alg_id;
+		u_int8_t key_len;
+		u_int8_t key[MAX_A5_KEY_LEN];
+	} encr;
+
+	struct timer_list T3101;
+
+	/* AMR bits */
+	struct gsm48_multi_rate_conf mr_conf;
+	
+	/* Established data link layer services */
+	u_int8_t sapis[8];
+
+	/* cache of last measurement reports on this lchan */
+	struct gsm_meas_rep meas_rep[6];
+	int meas_rep_idx;
+
+	/* table of neighbor cell measurements */
+	struct neigh_meas_proc neigh_meas[MAX_NEIGH_MEAS];
+
+	struct {
+		u_int32_t bound_ip;
+		u_int32_t connect_ip;
+		u_int16_t bound_port;
+		u_int16_t connect_port;
+		u_int16_t conn_id;
+		u_int8_t rtp_payload;
+		u_int8_t rtp_payload2;
+		u_int8_t speech_mode;
+		struct rtp_socket *rtp_socket;
+	} abis_ip;
+
+	struct gsm_subscriber_connection conn;
+};
+
+struct gsm_e1_subslot {
+	/* Number of E1 link */
+	u_int8_t	e1_nr;
+	/* Number of E1 TS inside E1 link */
+	u_int8_t	e1_ts;
+	/* Sub-slot within the E1 TS, 0xff if full TS */
+	u_int8_t	e1_ts_ss;
+};
+
+#define TS_F_PDCH_MODE	0x1000
+/* One Timeslot in a TRX */
+struct gsm_bts_trx_ts {
+	struct gsm_bts_trx *trx;
+	/* number of this timeslot at the TRX */
+	u_int8_t nr;
+
+	enum gsm_phys_chan_config pchan;
+
+	unsigned int flags;
+	struct gsm_nm_state nm_state;
+	struct tlv_parsed nm_attr;
+	u_int8_t nm_chan_comb;
+
+	/* To which E1 subslot are we connected */
+	struct gsm_e1_subslot e1_link;
+
+	struct gsm_lchan lchan[TS_MAX_LCHAN];
+};
+
+/* One TRX in a BTS */
+struct gsm_bts_trx {
+	/* list header in bts->trx_list */
+	struct llist_head list;
+
+	struct gsm_bts *bts;
+	/* number of this TRX in the BTS */
+	u_int8_t nr;
+	/* how do we talk RSL with this TRX? */
+	struct gsm_e1_subslot rsl_e1_link;
+	u_int8_t rsl_tei;
+	struct e1inp_sign_link *rsl_link;
+
+	struct gsm_nm_state nm_state;
+	struct tlv_parsed nm_attr;
+	struct {
+		struct gsm_nm_state nm_state;
+	} bb_transc;
+
+	u_int16_t arfcn;
+	int nominal_power;		/* in dBm */
+	unsigned int max_power_red;	/* in actual dB */
+
+	union {
+		struct {
+			struct {
+				struct gsm_nm_state nm_state;
+			} bbsig;
+			struct {
+				struct gsm_nm_state nm_state;
+			} pa;
+		} bs11;
+	};
+	struct gsm_bts_trx_ts ts[TRX_NR_TS];
+};
+
+enum gsm_bts_type {
+	GSM_BTS_TYPE_UNKNOWN,
+	GSM_BTS_TYPE_BS11,
+	GSM_BTS_TYPE_NANOBTS,
+};
+
+struct gsm_bts_model {
+	struct llist_head list;
+
+	enum gsm_bts_type type;
+	const char *name;
+
+	struct tlv_definition nm_att_tlvdef;
+};
+
+/**
+ * A pending paging request 
+ */
+struct gsm_paging_request {
+	/* list_head for list of all paging requests */
+	struct llist_head entry;
+	/* the subscriber which we're paging. Later gsm_paging_request
+	 * should probably become a part of the gsm_subscriber struct? */
+	struct gsm_subscriber *subscr;
+	/* back-pointer to the BTS on which we are paging */
+	struct gsm_bts *bts;
+	/* what kind of channel type do we ask the MS to establish */
+	int chan_type;
+
+	/* Timer 3113: how long do we try to page? */
+	struct timer_list T3113;
+
+	/* callback to be called in case paging completes */
+	gsm_cbfn *cbfn;
+	void *cbfn_param;
+};
+
+/*
+ * This keeps track of the paging status of one BTS. It
+ * includes a number of pending requests, a back pointer
+ * to the gsm_bts, a timer and some more state.
+ */
+struct gsm_bts_paging_state {
+	/* pending requests */
+	struct llist_head pending_requests;
+	struct gsm_bts *bts;
+
+	struct timer_list work_timer;
+
+	/* load */
+	u_int16_t available_slots;
+};
+
+struct gsm_envabtse {
+	struct gsm_nm_state nm_state;
+};
+
+struct gsm_bts_gprs_nsvc {
+	struct gsm_bts *bts;
+	/* data read via VTY config file, to configure the BTS
+	 * via OML from BSC */
+	int id;
+	u_int16_t nsvci;
+	u_int16_t local_port;	/* on the BTS */
+	u_int16_t remote_port;	/* on the SGSN */
+	u_int32_t remote_ip;	/* on the SGSN */
+
+	struct gsm_nm_state nm_state;
+};
+
+/* One BTS */
+struct gsm_bts {
+	/* list header in net->bts_list */
+	struct llist_head list;
+
+	struct gsm_network *network;
+	/* number of ths BTS in network */
+	u_int8_t nr;
+	/* Cell Identity */
+	u_int16_t cell_identity;
+	/* location area code of this BTS */
+	u_int16_t location_area_code;
+	/* Training Sequence Code */
+	u_int8_t tsc;
+	/* Base Station Identification Code (BSIC) */
+	u_int8_t bsic;
+	/* type of BTS */
+	enum gsm_bts_type type;
+	struct gsm_bts_model *model;
+	enum gsm_band band;
+	/* should the channel allocator allocate channels from high TRX to TRX0,
+	 * rather than starting from TRX0 and go upwards? */
+	int chan_alloc_reverse;
+	/* maximum Tx power that the MS is permitted to use in this cell */
+	int ms_max_power;
+
+	/* how do we talk OML with this TRX? */
+	struct gsm_e1_subslot oml_e1_link;
+	u_int8_t oml_tei;
+	struct e1inp_sign_link *oml_link;
+
+	/* Abis network management O&M handle */
+	struct abis_nm_h *nmh;
+	struct gsm_nm_state nm_state;
+	struct tlv_parsed nm_attr;
+
+	/* number of this BTS on given E1 link */
+	u_int8_t bts_nr;
+
+	/* paging state and control */
+	struct gsm_bts_paging_state paging;
+
+	/* CCCH is on C0 */
+	struct gsm_bts_trx *c0;
+
+	struct {
+		struct gsm_nm_state nm_state;
+	} site_mgr;
+
+	/* parameters from which we build SYSTEM INFORMATION */
+	struct {
+		struct gsm48_rach_control rach_control;
+		u_int8_t ncc_permitted;
+		struct gsm48_cell_sel_par cell_sel_par;
+		struct gsm48_cell_options cell_options;
+		struct gsm48_control_channel_descr chan_desc;
+		struct bitvec neigh_list;
+		struct bitvec cell_alloc;
+		struct {
+			/* bitmask large enough for all possible ARFCN's */
+			u_int8_t neigh_list[1024/8];
+			u_int8_t cell_alloc[1024/8];
+		} data;
+	} si_common;
+
+	/* ip.accesss Unit ID's have Site/BTS/TRX layout */
+	union {
+		struct {
+			u_int16_t site_id;
+			u_int16_t bts_id;
+			u_int32_t flags;
+		} ip_access;
+		struct {
+			struct {
+				struct gsm_nm_state nm_state;
+			} cclk;
+			struct {
+				struct gsm_nm_state nm_state;
+			} rack;
+			struct gsm_envabtse envabtse[4];
+		} bs11;
+	};
+
+	/* Not entirely sure how ip.access specific this is */
+	struct {
+		enum bts_gprs_mode mode;
+		struct {
+			struct gsm_nm_state nm_state;
+			u_int16_t nsei;
+		} nse;
+		struct {
+			struct gsm_nm_state nm_state;
+			u_int16_t bvci;
+		} cell;
+		struct gsm_bts_gprs_nsvc nsvc[2];
+		u_int8_t rac;
+	} gprs;
+
+	/* RACH NM values */
+	int rach_b_thresh;
+	int rach_ldavg_slots;
+	
+	/* transceivers */
+	int num_trx;
+	struct llist_head trx_list;
+};
+
+/* Some statistics of our network */
+struct gsmnet_stats {
+	struct {
+		struct counter *total;
+		struct counter *no_channel;
+	} chreq;
+	struct {
+		struct counter *attempted;
+		struct counter *no_channel;	/* no channel available */
+		struct counter *timeout;		/* T3103 timeout */
+		struct counter *completed;	/* HO COMPL received */
+		struct counter *failed;		/* HO FAIL received */
+	} handover;
+	struct {
+		struct counter *attach;
+		struct counter *normal;
+		struct counter *periodic;
+		struct counter *detach;
+	} loc_upd_type;
+	struct {
+		struct counter *reject;
+		struct counter *accept;
+	} loc_upd_resp;
+	struct {
+		struct counter *attempted;
+		struct counter *detached;
+		struct counter *completed;
+		struct counter *expired;
+	} paging;
+	struct {
+		struct counter *submitted; /* MO SMS submissions */
+		struct counter *no_receiver;
+		struct counter *delivered; /* MT SMS deliveries */
+		struct counter *rp_err_mem;
+		struct counter *rp_err_other;
+	} sms;
+	struct {
+		struct counter *dialled;	/* total number of dialled calls */
+		struct counter *alerted;	/* we alerted the other end */
+		struct counter *connected;/* how many calls were accepted */
+	} call;
+	struct {
+		struct counter *rf_fail;
+		struct counter *rll_err;
+	} chan;
+	struct {
+		struct counter *oml_fail;
+		struct counter *rsl_fail;
+	} bts;
+};
+
+enum gsm_auth_policy {
+	GSM_AUTH_POLICY_CLOSED, /* only subscribers authorized in DB */
+	GSM_AUTH_POLICY_ACCEPT_ALL, /* accept everyone, even if not authorized in DB */
+	GSM_AUTH_POLICY_TOKEN, /* accept first, send token per sms, then revoke authorization */
+};
+
+#define GSM_T3101_DEFAULT 10
+#define GSM_T3113_DEFAULT 60
+
+struct gsm_network {
+	/* global parameters */
+	u_int16_t country_code;
+	u_int16_t network_code;
+	char *name_long;
+	char *name_short;
+	enum gsm_auth_policy auth_policy;
+	enum gsm48_reject_value reject_cause;
+	int a5_encryption;
+	int neci;
+	int send_mm_info;
+	struct {
+		int active;
+		/* Window RXLEV averaging */
+		unsigned int win_rxlev_avg;	/* number of SACCH frames */
+		/* Window RXQUAL averaging */
+		unsigned int win_rxqual_avg;	/* number of SACCH frames */
+		/* Window RXLEV neighbouring cells averaging */
+		unsigned int win_rxlev_avg_neigh; /* number of SACCH frames */
+
+		/* how often should we check for power budget HO */
+		unsigned int pwr_interval;	/* SACCH frames */
+		/* how much better does a neighbor cell have to be ? */
+		unsigned int pwr_hysteresis;	/* dBm */
+		/* maximum distacne before we try a handover */
+		unsigned int max_distance;	/* TA values */
+	} handover;
+
+	struct gsmnet_stats stats;
+
+	/* layer 4 */
+	int (*mncc_recv) (struct gsm_network *net, int msg_type, void *arg);
+	struct llist_head upqueue;
+	struct llist_head trans_list;
+
+	unsigned int num_bts;
+	struct llist_head bts_list;
+
+	/* timer values */
+	int T3101;
+	int T3103;
+	int T3105;
+	int T3107;
+	int T3109;
+	int T3111;
+	int T3113;
+	int T3115;
+	int T3117;
+	int T3119;
+	int T3141;
+
+	/* Radio Resource Location Protocol (TS 04.31) */
+	struct {
+		enum rrlp_mode mode;
+	} rrlp;
+};
+
+#define SMS_HDR_SIZE	128
+#define SMS_TEXT_SIZE	256
+struct gsm_sms {
+	unsigned long long id;
+	struct gsm_subscriber *sender;
+	struct gsm_subscriber *receiver;
+
+	unsigned long validity_minutes;
+	u_int8_t reply_path_req;
+	u_int8_t status_rep_req;
+	u_int8_t ud_hdr_ind;
+	u_int8_t protocol_id;
+	u_int8_t data_coding_scheme;
+	u_int8_t msg_ref;
+	char dest_addr[20+1];	/* DA LV is 12 bytes max, i.e. 10 bytes
+				 * BCD == 20 bytes string */
+	u_int8_t user_data_len;
+	u_int8_t user_data[SMS_TEXT_SIZE];
+
+	char text[SMS_TEXT_SIZE];
+};
+
+
+struct gsm_network *gsm_network_init(u_int16_t country_code, u_int16_t network_code,
+				     int (*mncc_recv)(struct gsm_network *, int, void *));
+struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, enum gsm_bts_type type,
+			      u_int8_t tsc, u_int8_t bsic);
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts);
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type);
+
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num);
+
+/* Get reference to a neighbor cell on a given BCCH ARFCN */
+struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts,
+				 u_int16_t arfcn, u_int8_t bsic);
+
+struct gsm_bts_trx *gsm_bts_trx_num(struct gsm_bts *bts, int num);
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c);
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name);
+const char *gsm_lchant_name(enum gsm_chan_t c);
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c);
+char *gsm_trx_name(struct gsm_bts_trx *trx);
+char *gsm_ts_name(struct gsm_bts_trx_ts *ts);
+char *gsm_lchan_name(struct gsm_lchan *lchan);
+const char *gsm_lchans_name(enum gsm_lchan_state s);
+
+enum gsm_e1_event {
+	EVT_E1_NONE,
+	EVT_E1_TEI_UP,
+	EVT_E1_TEI_DN,
+};
+
+void set_ts_e1link(struct gsm_bts_trx_ts *ts, u_int8_t e1_nr,
+		   u_int8_t e1_ts, u_int8_t e1_ts_ss);
+enum gsm_bts_type parse_btstype(const char *arg);
+const char *btstype2str(enum gsm_bts_type type);
+struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr);
+struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
+				struct gsm_bts *start_bts);
+
+extern void *tall_bsc_ctx;
+extern int ipacc_rtp_direct;
+
+static inline int is_ipaccess_bts(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		return 1;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static inline int is_siemens_bts(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+
+enum gsm_auth_policy gsm_auth_policy_parse(const char *arg);
+const char *gsm_auth_policy_name(enum gsm_auth_policy policy);
+
+enum rrlp_mode rrlp_mode_parse(const char *arg);
+const char *rrlp_mode_name(enum rrlp_mode mode);
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg);
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode);
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked);
+
+int gsm48_ra_id_by_bts(u_int8_t *buf, struct gsm_bts *bts);
+void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts);
+struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan);
+
+int gsm_bts_model_register(struct gsm_bts_model *model);
+
+#endif
diff --git a/openbsc/include/openbsc/gsm_subscriber.h b/openbsc/include/openbsc/gsm_subscriber.h
new file mode 100644
index 0000000..0653996
--- /dev/null
+++ b/openbsc/include/openbsc/gsm_subscriber.h
@@ -0,0 +1,95 @@
+#ifndef _GSM_SUBSCR_H
+#define _GSM_SUBSCR_H
+
+#include <sys/types.h>
+#include "gsm_data.h"
+#include <osmocore/linuxlist.h>
+
+#define GSM_IMEI_LENGTH 17
+#define GSM_IMSI_LENGTH 17
+#define GSM_NAME_LENGTH 128
+
+#define GSM_EXTENSION_LENGTH 15 /* MSISDN can only be 15 digits length */
+#define GSM_MIN_EXTEN 20000
+#define GSM_MAX_EXTEN 49999
+
+/* reserved according to GSM 03.03 § 2.4 */
+#define GSM_RESERVED_TMSI   0xFFFFFFFF
+
+
+#define GSM_SUBSCRIBER_FIRST_CONTACT	0x00000001
+#define tmsi_from_string(str) strtoul(str, NULL, 10)
+
+struct gsm_equipment {
+	long long unsigned int id;
+	char imei[GSM_IMEI_LENGTH];
+	char name[GSM_NAME_LENGTH];
+
+	struct gsm48_classmark1 classmark1;
+	u_int8_t classmark2_len;
+	u_int8_t classmark2[3];
+	u_int8_t classmark3_len;
+	u_int8_t classmark3[14];
+};
+
+struct gsm_subscriber {
+	struct gsm_network *net;
+	long long unsigned int id;
+	char imsi[GSM_IMSI_LENGTH];
+	u_int32_t tmsi;
+	u_int16_t lac;
+	char name[GSM_NAME_LENGTH];
+	char extension[GSM_EXTENSION_LENGTH];
+	int authorized;
+
+	/* Temporary field which is not stored in the DB/HLR */
+	u_int32_t flags;
+
+	/* Every user can only have one equipment in use at any given
+	 * point in time */
+	struct gsm_equipment equipment;
+
+	/* for internal management */
+	int use_count;
+	struct llist_head entry;
+
+	/* pending requests */
+	int in_callback;
+	struct llist_head requests;
+};
+
+enum gsm_subscriber_field {
+	GSM_SUBSCRIBER_IMSI,
+	GSM_SUBSCRIBER_TMSI,
+	GSM_SUBSCRIBER_EXTENSION,
+	GSM_SUBSCRIBER_ID,
+};
+
+enum gsm_subscriber_update_reason {
+	GSM_SUBSCRIBER_UPDATE_ATTACHED,
+	GSM_SUBSCRIBER_UPDATE_DETACHED,
+	GSM_SUBSCRIBER_UPDATE_EQUIPMENT,
+};
+
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr);
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr);
+struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_network *net,
+					  u_int32_t tmsi);
+struct gsm_subscriber *subscr_get_by_imsi(struct gsm_network *net,
+					  const char *imsi);
+struct gsm_subscriber *subscr_get_by_extension(struct gsm_network *net,
+					       const char *ext);
+struct gsm_subscriber *subscr_get_by_id(struct gsm_network *net,
+					unsigned long long id);
+int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason);
+void subscr_put_channel(struct gsm_lchan *lchan);
+void subscr_get_channel(struct gsm_subscriber *subscr,
+                        int type, gsm_cbfn *cbfn, void *param);
+
+char *subscr_name(struct gsm_subscriber *subscr);
+
+/* internal */
+struct gsm_subscriber *subscr_alloc(void);
+extern struct llist_head active_subscribers;
+
+#endif /* _GSM_SUBSCR_H */
diff --git a/openbsc/include/openbsc/handover.h b/openbsc/include/openbsc/handover.h
new file mode 100644
index 0000000..8ab1b06
--- /dev/null
+++ b/openbsc/include/openbsc/handover.h
@@ -0,0 +1,8 @@
+#ifndef _HANDOVER_H
+#define _HANDOVER_H
+/* Hand over the specified logical channel to the specified new BTS.
+ * This is the main entry point for the actual handover algorithm,
+ * after it has decided it wants to initiate HO to a specific BTS */
+int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts);
+
+#endif /* _HANDOVER_H */
diff --git a/openbsc/include/openbsc/ipaccess.h b/openbsc/include/openbsc/ipaccess.h
new file mode 100644
index 0000000..f8ddfd4
--- /dev/null
+++ b/openbsc/include/openbsc/ipaccess.h
@@ -0,0 +1,109 @@
+#ifndef _IPACCESS_H
+#define _IPACCESS_H
+
+#include "e1_input.h"
+#include <osmocore/linuxlist.h>
+
+#define IPA_TCP_PORT_OML	3002
+#define IPA_TCP_PORT_RSL	3003
+
+struct ipaccess_head {
+	u_int16_t len;	/* network byte order */
+	u_int8_t proto;
+	u_int8_t data[0];
+} __attribute__ ((packed));
+
+enum ipaccess_proto {
+	IPAC_PROTO_RSL		= 0x00,
+	IPAC_PROTO_IPACCESS	= 0xfe,
+	IPAC_PROTO_SCCP		= 0xfd,
+	IPAC_PROTO_OML		= 0xff,
+};
+
+enum ipaccess_msgtype {
+	IPAC_MSGT_PING		= 0x00,
+	IPAC_MSGT_PONG		= 0x01,
+	IPAC_MSGT_ID_GET	= 0x04,
+	IPAC_MSGT_ID_RESP	= 0x05,
+	IPAC_MSGT_ID_ACK	= 0x06,
+};
+
+enum ipaccess_id_tags {
+	IPAC_IDTAG_SERNR		= 0x00,
+	IPAC_IDTAG_UNITNAME		= 0x01,
+	IPAC_IDTAG_LOCATION1		= 0x02,
+	IPAC_IDTAG_LOCATION2		= 0x03,
+	IPAC_IDTAG_EQUIPVERS		= 0x04,
+	IPAC_IDTAG_SWVERSION		= 0x05,
+	IPAC_IDTAG_IPADDR		= 0x06,
+	IPAC_IDTAG_MACADDR		= 0x07,
+	IPAC_IDTAG_UNIT			= 0x08,
+};
+
+int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa);
+
+/*
+ * methods for parsing and sending a message
+ */
+int ipaccess_rcvmsg_base(struct msgb *msg, struct bsc_fd *bfd);
+struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error);
+void ipaccess_prepend_header(struct msgb *msg, int proto);
+int ipaccess_send_id_ack(int fd);
+int ipaccess_send_id_req(int fd);
+
+int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len);
+
+int ipaccess_drop_oml(struct gsm_bts *bts);
+int ipaccess_drop_rsl(struct gsm_bts_trx *trx);
+
+/*
+ * Firmware specific header
+ */
+struct sdp_firmware {
+	char magic[4];
+	char more_magic[2];
+	u_int16_t more_more_magic;
+	u_int32_t header_length;
+	u_int32_t file_length;
+	char sw_part[20];
+	char text1[64];
+	char time[12];
+	char date[14];
+	char text2[10];
+	char version[20];
+	u_int16_t table_offset;
+	/* stuff i don't know */
+} __attribute__((packed));
+
+struct sdp_header_entry {
+	u_int16_t something1;
+	char text1[64];
+	char time[12];
+	char date[14];
+	char text2[10];
+	char version[20];
+	u_int32_t length;
+	u_int32_t addr1;
+	u_int32_t addr2;
+	u_int32_t start;
+} __attribute__((packed));
+
+struct sdp_header_item {
+	struct sdp_header_entry header_entry;
+	struct llist_head entry;
+	off_t absolute_offset;
+};
+
+struct sdp_header {
+	struct sdp_firmware firmware_info;
+
+	/* for more_magic a list of sdp_header_entry_list */
+	struct llist_head header_list;
+
+	/* the entry of the sdp_header */
+	struct llist_head entry;
+};
+
+int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned base_offset, struct llist_head *list);
+
+#endif /* _IPACCESS_H */
diff --git a/openbsc/include/openbsc/meas_rep.h b/openbsc/include/openbsc/meas_rep.h
new file mode 100644
index 0000000..3c2c8d1
--- /dev/null
+++ b/openbsc/include/openbsc/meas_rep.h
@@ -0,0 +1,85 @@
+#ifndef _MEAS_REP_H
+#define _MEAS_REP_H
+
+#define MRC_F_PROCESSED	0x0001
+
+/* extracted from a L3 measurement report IE */
+struct gsm_meas_rep_cell {
+	u_int8_t rxlev;
+	u_int8_t bsic;
+	u_int8_t neigh_idx;
+	u_int16_t arfcn;
+	unsigned int flags;
+};
+
+/* RX Level and RX Quality */
+struct gsm_rx_lev_qual {
+	u_int8_t rx_lev;
+	u_int8_t rx_qual;
+};
+
+/* unidirectional measumrement report */
+struct gsm_meas_rep_unidir {
+	struct gsm_rx_lev_qual full;
+	struct gsm_rx_lev_qual sub;
+};
+
+#define MEAS_REP_F_UL_DTX	0x01
+#define MEAS_REP_F_DL_VALID	0x02
+#define MEAS_REP_F_BA1		0x04
+#define MEAS_REP_F_DL_DTX	0x08
+#define MEAS_REP_F_MS_TO	0x10
+#define MEAS_REP_F_MS_L1	0x20
+#define MEAS_REP_F_FPC		0x40
+
+/* parsed uplink and downlink measurement result */
+struct gsm_meas_rep {
+	/* back-pointer to the logical channel */
+	struct gsm_lchan *lchan;
+
+	/* number of the measurement report */
+	u_int8_t nr;
+	/* flags, see MEAS_REP_F_* */
+	unsigned int flags;
+
+	/* uplink and downlink rxlev, rxqual; full and sub */
+	struct gsm_meas_rep_unidir ul;
+	struct gsm_meas_rep_unidir dl;
+
+	u_int8_t bs_power;
+	u_int8_t ms_timing_offset;
+	struct {
+		int8_t pwr;	/* MS power in dBm */
+		u_int8_t ta;	/* MS timing advance */
+	} ms_l1;
+
+	/* neighbor measurement reports for up to 6 cells */
+	int num_cell;
+	struct gsm_meas_rep_cell cell[6];
+};
+
+enum meas_rep_field {
+	MEAS_REP_DL_RXLEV_FULL,
+	MEAS_REP_DL_RXLEV_SUB,
+	MEAS_REP_DL_RXQUAL_FULL,
+	MEAS_REP_DL_RXQUAL_SUB,
+	MEAS_REP_UL_RXLEV_FULL,
+	MEAS_REP_UL_RXLEV_SUB,
+	MEAS_REP_UL_RXQUAL_FULL,
+	MEAS_REP_UL_RXQUAL_SUB,
+};
+
+/* obtain an average over the last 'num' fields in the meas reps */
+int get_meas_rep_avg(const struct gsm_lchan *lchan,
+		     enum meas_rep_field field, unsigned int num);
+
+/* Check if N out of M last values for FIELD are >= bd */
+int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
+			enum meas_rep_field field,
+			unsigned int n, unsigned int m, int be);
+
+unsigned int calc_initial_idx(unsigned int array_size,
+			      unsigned int meas_rep_idx,
+			      unsigned int num_values);
+
+#endif /* _MEAS_REP_H */
diff --git a/openbsc/include/openbsc/mgcp.h b/openbsc/include/openbsc/mgcp.h
new file mode 100644
index 0000000..71b7fc1
--- /dev/null
+++ b/openbsc/include/openbsc/mgcp.h
@@ -0,0 +1,135 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+
+/*
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef OPENBSC_MGCP_H
+#define OPENBSC_MGCP_H
+
+#include <osmocore/msgb.h>
+
+#include <arpa/inet.h>
+
+#define RTP_PORT_DEFAULT 4000
+
+/**
+ * Calculate the RTP audio port for the given multiplex
+ * and the direction. This allows a semi static endpoint
+ * to port calculation removing the need for the BSC
+ * and the MediaGateway to communicate.
+ *
+ * Port usage explained:
+ *       base + (multiplex * 2) + 0 == local port to wait for network packets
+ *       base + (multiplex * 2) + 1 == local port for rtcp
+ *
+ * The above port will receive packets from the BTS that need
+ * to be patched and forwarded to the network.
+ * The above port will receive packets from the network that
+ * need to be patched and forwarded to the BTS.
+ *
+ * We assume to have a static BTS IP address so we can differentiate
+ * network and BTS.
+ *
+ */
+static inline int rtp_calculate_port(int multiplex, int base)
+{
+	return base + (multiplex * 2);
+}
+
+
+/*
+ * Handling of MGCP Endpoints and the MGCP Config
+ */
+struct mgcp_endpoint;
+struct mgcp_config;
+
+#define MGCP_ENDP_CRCX 1
+#define MGCP_ENDP_DLCX 2
+#define MGCP_ENDP_MDCX 3
+
+/*
+ * what to do with the msg?
+ *	- continue as usual?
+ *	- reject and send a failure code?
+ *	- defer? do not send anything
+ */
+#define MGCP_POLICY_CONT	4
+#define MGCP_POLICY_REJECT	5
+#define MGCP_POLICY_DEFER	6
+
+typedef int (*mgcp_change)(struct mgcp_config *cfg, int endpoint, int state, int local_rtp);
+typedef int (*mgcp_policy)(struct mgcp_config *cfg, int endpoint, int state, const char *transactio_id);
+typedef int (*mgcp_reset)(struct mgcp_config *cfg);
+
+struct mgcp_config {
+	int source_port;
+	char *local_ip;
+	char *source_addr;
+	unsigned int number_endpoints;
+	char *bts_ip;
+	char *call_agent_addr;
+
+	struct in_addr bts_in;
+	char *audio_name;
+	int audio_payload;
+	int audio_loop;
+	int early_bind;
+	int rtp_base_port;
+
+	char *forward_ip;
+	int forward_port;
+
+	/* spec handling */
+	int force_realloc;
+
+	mgcp_change change_cb;
+	mgcp_policy policy_cb;
+	mgcp_reset reset_cb;
+	void *data;
+
+	struct mgcp_endpoint *endpoints;
+	unsigned int last_call_id;
+};
+
+/* config management */
+struct mgcp_config *mgcp_config_alloc(void);
+int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg);
+int mgcp_vty_init(void);
+int mgcp_endpoints_allocate(struct mgcp_config *cfg);
+int mgcp_bind_rtp_port(struct mgcp_endpoint *endp, int rtp_port);
+void mgcp_free_endp(struct mgcp_endpoint *endp);
+
+/*
+ * format helper functions
+ */
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg);
+struct msgb *mgcp_create_response_with_data(int code, const char *msg, const char *trans, const char *data);
+
+/* adc helper */
+static inline int mgcp_timeslot_to_endpoint(int multiplex, int timeslot)
+{
+	if (timeslot == 0)
+		timeslot = 1;
+	return timeslot + (31 * multiplex);
+}
+
+
+#endif
diff --git a/openbsc/include/openbsc/mgcp_internal.h b/openbsc/include/openbsc/mgcp_internal.h
new file mode 100644
index 0000000..d5aec30
--- /dev/null
+++ b/openbsc/include/openbsc/mgcp_internal.h
@@ -0,0 +1,78 @@
+/* MGCP Private Data */
+
+/*
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef OPENBSC_MGCP_DATA_H
+#define OPENBSC_MGCP_DATA_H
+
+#include <osmocore/select.h>
+
+#define CI_UNUSED 0
+
+struct mgcp_endpoint {
+	int ci;
+	char *callid;
+	char *local_options;
+	int conn_mode;
+
+	int bts_payload_type;
+	int net_payload_type;
+
+	/* the local rtp port we are binding to */
+	int rtp_port;
+
+	/*
+	 * RTP mangling:
+	 *  - we get RTP and RTCP to us and need to forward to the BTS
+	 *  - we get RTP and RTCP from the BTS and forward to the network
+	 */
+	struct bsc_fd local_rtp;
+	struct bsc_fd local_rtcp;
+
+	struct in_addr remote;
+	struct in_addr bts;
+
+	/* in network byte order */
+	int net_rtp, net_rtcp;
+	int bts_rtp, bts_rtcp;
+
+	/* backpointer */
+	struct mgcp_config *cfg;
+
+	/* statistics */
+	unsigned int in_bts;
+	unsigned int in_remote;
+};
+
+#define ENDPOINT_NUMBER(endp) abs(endp - endp->cfg->endpoints)
+
+struct mgcp_msg_ptr {
+	unsigned int start;
+	unsigned int length;
+};
+
+int mgcp_analyze_header(struct mgcp_config *cfg, struct msgb *msg,
+			struct mgcp_msg_ptr *ptr, int size,
+			const char **transaction_id, struct mgcp_endpoint **endp);
+int mgcp_send_dummy(struct mgcp_endpoint *endp);
+
+#endif
diff --git a/openbsc/include/openbsc/misdn.h b/openbsc/include/openbsc/misdn.h
new file mode 100644
index 0000000..6c38de7
--- /dev/null
+++ b/openbsc/include/openbsc/misdn.h
@@ -0,0 +1,29 @@
+/* (C) 2008 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef MISDN_H
+#define MISDN_H
+
+#include "e1_input.h"
+
+int mi_setup(int cardnr,  struct e1inp_line *line, int release_l2);
+int _abis_nm_sendmsg(struct msgb *msg);
+int mi_e1_line_update(struct e1inp_line *line);
+
+#endif
diff --git a/openbsc/include/openbsc/mncc.h b/openbsc/include/openbsc/mncc.h
new file mode 100644
index 0000000..cb8339a
--- /dev/null
+++ b/openbsc/include/openbsc/mncc.h
@@ -0,0 +1,159 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface 
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef _MNCC_H
+#define _MNCC_H
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/mncc.h>
+
+/* One end of a call */
+struct gsm_call {
+	struct llist_head entry;
+
+	/* network handle */
+	void *net;
+
+	/* the 'local' transaction */
+	u_int32_t callref;
+	/* the 'remote' transaction */
+	u_int32_t remote_ref;
+};
+
+#define MNCC_SETUP_REQ		0x0101
+#define MNCC_SETUP_IND		0x0102
+#define MNCC_SETUP_RSP		0x0103
+#define MNCC_SETUP_CNF		0x0104
+#define MNCC_SETUP_COMPL_REQ	0x0105
+#define MNCC_SETUP_COMPL_IND	0x0106
+/* MNCC_REJ_* is perfomed via MNCC_REL_* */
+#define MNCC_CALL_CONF_IND	0x0107
+#define MNCC_CALL_PROC_REQ	0x0108
+#define MNCC_PROGRESS_REQ	0x0109
+#define MNCC_ALERT_REQ		0x010a
+#define MNCC_ALERT_IND		0x010b
+#define MNCC_NOTIFY_REQ		0x010c
+#define MNCC_NOTIFY_IND		0x010d
+#define MNCC_DISC_REQ		0x010e
+#define MNCC_DISC_IND		0x010f
+#define MNCC_REL_REQ		0x0110
+#define MNCC_REL_IND		0x0111
+#define MNCC_REL_CNF		0x0112
+#define MNCC_FACILITY_REQ	0x0113
+#define MNCC_FACILITY_IND	0x0114
+#define MNCC_START_DTMF_IND	0x0115
+#define MNCC_START_DTMF_RSP	0x0116
+#define MNCC_START_DTMF_REJ	0x0117
+#define MNCC_STOP_DTMF_IND	0x0118
+#define MNCC_STOP_DTMF_RSP	0x0119
+#define MNCC_MODIFY_REQ		0x011a
+#define MNCC_MODIFY_IND		0x011b
+#define MNCC_MODIFY_RSP		0x011c
+#define MNCC_MODIFY_CNF		0x011d
+#define MNCC_MODIFY_REJ		0x011e
+#define MNCC_HOLD_IND		0x011f
+#define MNCC_HOLD_CNF		0x0120
+#define MNCC_HOLD_REJ		0x0121
+#define MNCC_RETRIEVE_IND	0x0122
+#define MNCC_RETRIEVE_CNF	0x0123
+#define MNCC_RETRIEVE_REJ	0x0124
+#define MNCC_USERINFO_REQ	0x0125
+#define MNCC_USERINFO_IND	0x0126
+#define MNCC_REJ_REQ		0x0127
+#define MNCC_REJ_IND		0x0128
+
+#define MNCC_BRIDGE		0x0200
+#define MNCC_FRAME_RECV		0x0201
+#define MNCC_FRAME_DROP		0x0202
+#define MNCC_LCHAN_MODIFY	0x0203
+
+#define GSM_TCHF_FRAME		0x0300
+#define GSM_TCHF_FRAME_EFR	0x0301
+
+#define GSM_MAX_FACILITY	128
+#define GSM_MAX_SSVERSION	128
+#define GSM_MAX_USERUSER	128
+
+#define	MNCC_F_BEARER_CAP	0x0001
+#define MNCC_F_CALLED		0x0002
+#define MNCC_F_CALLING		0x0004
+#define MNCC_F_REDIRECTING	0x0008
+#define MNCC_F_CONNECTED	0x0010
+#define MNCC_F_CAUSE		0x0020
+#define MNCC_F_USERUSER		0x0040
+#define MNCC_F_PROGRESS		0x0080
+#define MNCC_F_EMERGENCY	0x0100
+#define MNCC_F_FACILITY		0x0200
+#define MNCC_F_SSVERSION	0x0400
+#define MNCC_F_CCCAP		0x0800
+#define MNCC_F_KEYPAD		0x1000
+#define MNCC_F_SIGNAL		0x2000
+
+struct gsm_mncc {
+	/* context based information */
+	u_int32_t	msg_type;
+	u_int32_t	callref;
+
+	/* which fields are present */
+	u_int32_t	fields;
+
+	/* data derived informations (MNCC_F_ based) */
+	struct gsm_mncc_bearer_cap	bearer_cap;
+	struct gsm_mncc_number		called;
+	struct gsm_mncc_number		calling;
+	struct gsm_mncc_number		redirecting;
+	struct gsm_mncc_number		connected;
+	struct gsm_mncc_cause		cause;
+	struct gsm_mncc_progress	progress;
+	struct gsm_mncc_useruser	useruser;
+	struct gsm_mncc_facility	facility;
+	struct gsm_mncc_cccap		cccap;
+	struct gsm_mncc_ssversion	ssversion;
+	struct	{
+		int		sup;
+		int		inv;
+	} clir;
+	int		signal;
+
+	/* data derived information, not MNCC_F based */
+	int		keypad;
+	int		more;
+	int		notify; /* 0..127 */
+	int		emergency;
+	char		imsi[16];
+
+	unsigned char	lchan_mode;
+};
+
+struct gsm_data_frame {
+	u_int32_t	msg_type;
+	u_int32_t	callref;
+	unsigned char	data[0];
+};
+
+char *get_mncc_name(int value);
+int mncc_recv(struct gsm_network *net, int msg_type, void *arg);
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
+
+#endif
diff --git a/openbsc/include/openbsc/openbscdefines.h b/openbsc/include/openbsc/openbscdefines.h
new file mode 100644
index 0000000..082e592
--- /dev/null
+++ b/openbsc/include/openbsc/openbscdefines.h
@@ -0,0 +1,35 @@
+/* 
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef OPENBSCDEFINES_H
+#define OPENBSCDEFINES_H
+
+#ifdef BUILDING_ON_WINDOWS
+    #ifdef BUILDING_OPENBSC
+        #define BSC_API __declspec(dllexport)
+    #else
+        #define BSC_API __declspec(dllimport)
+    #endif
+#else
+    #define BSC_API __attribute__((visibility("default")))
+#endif
+
+#endif
diff --git a/openbsc/include/openbsc/paging.h b/openbsc/include/openbsc/paging.h
new file mode 100644
index 0000000..6cbdca9
--- /dev/null
+++ b/openbsc/include/openbsc/paging.h
@@ -0,0 +1,46 @@
+/* Paging helper and manager.... */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef PAGING_H
+#define PAGING_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocore/linuxlist.h>
+#include "gsm_data.h"
+#include "gsm_subscriber.h"
+#include <osmocore/timer.h>
+
+/* call once for every gsm_bts... */
+void paging_init(struct gsm_bts *bts);
+
+/* schedule paging request */
+int paging_request(struct gsm_network *network, struct gsm_subscriber *subscr,
+		   int type, gsm_cbfn *cbfn, void *data);
+
+/* stop paging requests */
+void paging_request_stop(struct gsm_bts *bts, struct gsm_subscriber *subscr,
+			 struct gsm_lchan *lchan);
+
+/* update paging load */
+void paging_update_buffer_space(struct gsm_bts *bts, u_int16_t);
+
+#endif
diff --git a/openbsc/include/openbsc/rest_octets.h b/openbsc/include/openbsc/rest_octets.h
new file mode 100644
index 0000000..6d90119
--- /dev/null
+++ b/openbsc/include/openbsc/rest_octets.h
@@ -0,0 +1,133 @@
+#ifndef _REST_OCTETS_H
+#define _REST_OCTETS_H
+
+#include <sys/types.h>
+#include <openbsc/gsm_04_08.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(u_int8_t *data, u_int8_t *nch_pos);
+
+struct gsm48_si_selection_params {
+	u_int16_t penalty_time:5,
+		  temp_offs:3,
+		  cell_resel_off:6,
+		  cbq:1,
+		  present:1;
+};
+
+struct gsm48_si_power_offset {
+	u_int8_t power_offset:2,
+		 present:1;
+};
+
+struct gsm48_si3_gprs_ind {
+	u_int8_t si13_position:1,
+		 ra_colour:3,
+		 present:1;
+};
+
+struct gsm48_lsa_params {
+	u_int32_t prio_thr:3,
+		 lsa_offset:3,
+		 mcc:12,
+		 mnc:12;
+	unsigned int present;
+};
+
+struct gsm48_si_ro_info {
+	struct gsm48_si_selection_params selection_params;
+	struct gsm48_si_power_offset power_offset;
+	u_int8_t si2ter_indicator;
+	u_int8_t early_cm_ctrl;
+	struct {
+		u_int8_t where:3,
+			 present:1;
+	} scheduling;
+	struct gsm48_si3_gprs_ind gprs_ind;
+
+	/* SI 4 specific */
+	struct gsm48_lsa_params lsa_params;
+	u_int16_t cell_id;
+	u_int8_t break_ind;	/* do we have SI7 + SI8 ? */
+};
+
+
+/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
+int rest_octets_si3(u_int8_t *data, const struct gsm48_si_ro_info *si3);
+
+/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
+int rest_octets_si4(u_int8_t *data, const struct gsm48_si_ro_info *si4);
+
+enum pbcch_carrier_type {
+	PBCCH_BCCH,
+	PBCCH_ARFCN,
+	PBCCH_MAIO
+};
+
+/* TS 03.60 Chapter 6.3.3.1: Network Mode of Operation */
+enum gprs_nmo {
+	GPRS_NMO_I	= 0,	/* CS pagin on GPRS paging or traffic channel */
+	GPRS_NMO_II	= 1,	/* all paging on CCCH */
+	GPRS_NMO_III	= 2,	/* no paging coordination */
+};
+
+/* TS 04.60 12.24 */
+struct gprs_cell_options {
+	enum gprs_nmo nmo;
+	/* T3168: wait for packet uplink assignment message */
+	u_int32_t t3168;	/* in milliseconds */
+	/* T3192: wait for release of the TBF after reception of the final block */
+	u_int32_t t3192;	/* in milliseconds */
+	u_int32_t drx_timer_max;/* in seconds */
+	u_int32_t bs_cv_max;
+
+	u_int8_t ext_info_present;
+	struct {
+		u_int8_t egprs_supported;
+			u_int8_t use_egprs_p_ch_req;
+			u_int8_t bep_period;
+		u_int8_t pfc_supported;
+		u_int8_t dtm_supported;
+		u_int8_t bss_paging_coordination;
+	} ext_info;
+};
+
+/* TS 04.60 Table 12.9.2 */
+struct gprs_power_ctrl_pars {
+	u_int8_t alpha;
+	u_int8_t t_avg_w;
+	u_int8_t t_avg_t;
+	u_int8_t pc_meas_chan;
+	u_int8_t n_avg_i;
+};
+
+struct gsm48_si13_info {
+	struct gprs_cell_options cell_opts;
+	struct gprs_power_ctrl_pars pwr_ctrl_pars;
+	u_int8_t bcch_change_mark;
+	u_int8_t si_change_field;
+	u_int8_t pbcch_present;
+
+	union {
+		struct {
+			u_int8_t rac;
+			u_int8_t spgc_ccch_sup;
+			u_int8_t net_ctrl_ord;
+			u_int8_t prio_acc_thr;
+		} no_pbcch;
+		struct {
+			u_int8_t psi1_rep_per;
+			u_int8_t pb;
+			u_int8_t tsc;
+			u_int8_t tn;
+			enum pbcch_carrier_type carrier_type;
+			u_int16_t arfcn;
+			u_int8_t maio;
+		} pbcch;
+	};
+};
+
+/* Generate SI13 Rest Octests (Chapter 10.5.2.37b) */
+int rest_octets_si13(u_int8_t *data, const struct gsm48_si13_info *si13);
+
+#endif /* _REST_OCTETS_H */
diff --git a/openbsc/include/openbsc/rs232.h b/openbsc/include/openbsc/rs232.h
new file mode 100644
index 0000000..61187ca
--- /dev/null
+++ b/openbsc/include/openbsc/rs232.h
@@ -0,0 +1,9 @@
+#ifndef _RS232_H
+#define _RS232_H
+
+int rs232_setup(const char *serial_port, unsigned int delay_ms,
+		struct gsm_bts *bts);
+
+int handle_serial_msg(struct msgb *msg);
+
+#endif /* _RS232_H */
diff --git a/openbsc/include/openbsc/rtp_proxy.h b/openbsc/include/openbsc/rtp_proxy.h
new file mode 100644
index 0000000..65b1a5f
--- /dev/null
+++ b/openbsc/include/openbsc/rtp_proxy.h
@@ -0,0 +1,91 @@
+#ifndef _RTP_PROXY_H
+#define _RTP_PROXY_H
+
+/* RTP proxy handling for ip.access nanoBTS */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <netinet/in.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/select.h>
+
+#define RTP_PT_GSM_FULL 3
+#define RTP_PT_GSM_HALF 96
+#define RTP_PT_GSM_EFR 97
+#define RTP_PT_AMR_FULL 98
+#define RTP_PT_AMR_HALF 99
+
+enum rtp_rx_action {
+	RTP_NONE,
+	RTP_PROXY,
+	RTP_RECV_UPSTREAM,
+};
+
+enum rtp_tx_action {
+	RTP_SEND_NONE,
+	RTP_SEND_DOWNSTREAM,
+};
+
+struct rtp_sub_socket {
+	struct sockaddr_in sin_local;
+	struct sockaddr_in sin_remote;
+
+	struct bsc_fd bfd;
+	/* linked list of to-be-transmitted msgb's */
+	struct llist_head tx_queue;
+};
+
+struct rtp_socket {
+	struct llist_head list;
+
+	struct rtp_sub_socket rtp;
+	struct rtp_sub_socket rtcp;
+
+	/* what should we do on receive? */
+	enum rtp_rx_action rx_action;
+	union {
+		struct {
+			struct rtp_socket *other_sock;
+		} proxy;
+		struct {
+			struct gsm_network *net;
+			u_int32_t callref;
+		} receive;
+	};
+	enum rtp_tx_action tx_action;
+	struct {
+		u_int16_t sequence;
+		u_int32_t timestamp;
+		u_int32_t ssrc;
+		struct timeval last_tv;
+	} transmit;
+};
+
+struct rtp_socket *rtp_socket_create(void);
+int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip);
+int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port);
+int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other);
+int rtp_socket_upstream(struct rtp_socket *this, struct gsm_network *net, u_int32_t callref);
+int rtp_socket_free(struct rtp_socket *rs);
+int rtp_send_frame(struct rtp_socket *rs, struct gsm_data_frame *frame);
+
+#endif /* _RTP_PROXY_H */
diff --git a/openbsc/include/openbsc/sgsn.h b/openbsc/include/openbsc/sgsn.h
new file mode 100644
index 0000000..2dc53c1
--- /dev/null
+++ b/openbsc/include/openbsc/sgsn.h
@@ -0,0 +1,30 @@
+#ifndef _SGSN_H
+#define _SGSN_H
+
+#include <sys/types.h>
+
+#include <osmocore/msgb.h>
+
+#include <openbsc/gprs_ns.h>
+
+struct sgsn_config {
+	/* parsed from config file */
+	u_int32_t nsip_listen_ip;
+	u_int16_t nsip_listen_port;
+
+	/* misc */
+	struct gprs_ns_inst *nsi;
+};
+
+
+/* sgsn_vty.c */
+
+int sgsn_vty_init(void);
+int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg);
+
+/* sgsn.c */
+
+/* Main input function for Gb proxy */
+int sgsn_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci);
+
+#endif
diff --git a/openbsc/include/openbsc/signal.h b/openbsc/include/openbsc/signal.h
new file mode 100644
index 0000000..6e0f5bac
--- /dev/null
+++ b/openbsc/include/openbsc/signal.h
@@ -0,0 +1,159 @@
+/* Generic signalling/notification infrastructure */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef OPENBSC_SIGNAL_H
+#define OPENBSC_SIGNAL_H
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <openbsc/gsm_data.h>
+
+#include <osmocore/signal.h>
+
+/*
+ * Signalling subsystems
+ */
+enum signal_subsystems {
+	SS_PAGING,
+	SS_SMS,
+	SS_ABISIP,
+	SS_NM,
+	SS_LCHAN,
+	SS_SUBSCR,
+	SS_SCALL,
+	SS_GLOBAL,
+	SS_CHALLOC,
+	SS_NS,
+};
+
+/* SS_PAGING signals */
+enum signal_paging {
+	S_PAGING_SUCCEEDED,
+	S_PAGING_EXPIRED,
+};
+
+/* SS_SMS signals */
+enum signal_sms {
+	S_SMS_SUBMITTED,	/* A SMS has been successfully submitted to us */
+	S_SMS_DELIVERED,	/* A SMS has been successfully delivered to a MS */
+	S_SMS_SMMA,		/* A MS tells us it has more space available */
+	S_SMS_MEM_EXCEEDED,	/* A MS tells us it has no more space available */
+};
+
+/* SS_ABISIP signals */
+enum signal_abisip {
+	S_ABISIP_CRCX_ACK,
+	S_ABISIP_MDCX_ACK,
+	S_ABISIP_DLCX_IND,
+};
+
+/* SS_NM signals */
+enum signal_nm {
+	S_NM_SW_ACTIV_REP,	/* GSM 12.21 software activated report */
+	S_NM_FAIL_REP,		/* GSM 12.21 failure event report */
+	S_NM_NACK,		/* GSM 12.21 various NM_MT_*_NACK happened */
+	S_NM_IPACC_NACK,	/* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_NACK happened */
+	S_NM_IPACC_ACK,		/* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_ACK happened */
+	S_NM_IPACC_RESTART_ACK, /* nanoBTS has send a restart ack */
+	S_NM_IPACC_RESTART_NACK,/* nanoBTS has send a restart ack */
+	S_NM_TEST_REP,		/* GSM 12.21 Test Report */
+};
+
+/* SS_LCHAN signals */
+enum signal_lchan {
+	/*
+	 * The lchan got freed with an use_count != 0 and error
+	 * recovery needs to be carried out from within the
+	 * signal handler.
+	 */
+	S_LCHAN_UNEXPECTED_RELEASE,
+	S_LCHAN_ACTIVATE_ACK,		/* 08.58 Channel Activate ACK */
+	S_LCHAN_ACTIVATE_NACK,		/* 08.58 Channel Activate NACK */
+	S_LCHAN_HANDOVER_COMPL,		/* 04.08 Handover Completed */
+	S_LCHAN_HANDOVER_FAIL,		/* 04.08 Handover Failed */
+	S_LCHAN_HANDOVER_DETECT,	/* 08.58 Handover Detect */
+	S_LCHAN_MEAS_REP,		/* 08.58 Measurement Report */
+};
+
+/* SS_CHALLOC signals */
+enum signal_challoc {
+	S_CHALLOC_ALLOC_FAIL,	/* allocation of lchan has failed */
+	S_CHALLOC_FREED,	/* lchan has been successfully freed */
+};
+
+/* SS_SUBSCR signals */
+enum signal_subscr {
+	S_SUBSCR_ATTACHED,
+	S_SUBSCR_DETACHED,
+	S_SUBSCR_IDENTITY,		/* we've received some identity information */
+};
+
+/* SS_SCALL signals */
+enum signal_scall {
+	S_SCALL_SUCCESS,
+	S_SCALL_EXPIRED,
+	S_SCALL_DETACHED,
+};
+
+enum signal_global {
+	S_GLOBAL_SHUTDOWN,
+};
+
+struct gsm_subscriber;
+
+struct paging_signal_data {
+	struct gsm_subscriber *subscr;
+	struct gsm_bts *bts;
+
+	/* NULL in case the paging didn't work */
+	struct gsm_lchan *lchan;
+};
+
+struct scall_signal_data {
+	struct gsm_subscriber *subscr;
+	struct gsm_lchan *lchan;
+	void *data;
+};
+
+struct ipacc_ack_signal_data {
+	struct gsm_bts *bts;
+	u_int8_t msg_type;	
+};
+
+struct challoc_signal_data {
+	struct gsm_bts *bts;
+	struct gsm_lchan *lchan;
+	enum gsm_chan_t type;
+};
+
+enum signal_ns {
+	S_NS_RESET,
+	S_NS_BLOCK,
+	S_NS_UNBLOCK,
+};
+
+struct ns_signal_data {
+	struct gprs_nsvc *nsvc;
+	uint8_t cause;
+};
+
+#endif
diff --git a/openbsc/include/openbsc/silent_call.h b/openbsc/include/openbsc/silent_call.h
new file mode 100644
index 0000000..fefc518
--- /dev/null
+++ b/openbsc/include/openbsc/silent_call.h
@@ -0,0 +1,10 @@
+#ifndef _SILENT_CALL_H
+#define _SILENT_CALL_H
+
+extern int gsm_silent_call_start(struct gsm_subscriber *subscr,
+                                 void *data, int type);
+extern int gsm_silent_call_stop(struct gsm_subscriber *subscr);
+extern int silent_call_rx(struct msgb *msg);
+extern int silent_call_reroute(struct msgb *msg);
+
+#endif /* _SILENT_CALL_H */
diff --git a/openbsc/include/openbsc/subchan_demux.h b/openbsc/include/openbsc/subchan_demux.h
new file mode 100644
index 0000000..02fa023
--- /dev/null
+++ b/openbsc/include/openbsc/subchan_demux.h
@@ -0,0 +1,102 @@
+#ifndef _SUBCH_DEMUX_H
+#define _SUBCH_DEMUX_H
+/* A E1 sub-channel (de)multiplexer with TRAU frame sync */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+#include <osmocore/linuxlist.h>
+
+#define NR_SUBCH	4
+#define TRAU_FRAME_SIZE	40
+#define TRAU_FRAME_BITS	(TRAU_FRAME_SIZE*8)
+
+/***********************************************************************/
+/* DEMULTIPLEXER */
+/***********************************************************************/
+
+struct demux_subch {
+	u_int8_t out_bitbuf[TRAU_FRAME_BITS];
+	u_int16_t out_idx; /* next bit to be written in out_bitbuf */
+	/* number of consecutive zeros that we have received (for sync) */
+	unsigned int consecutive_zeros;
+	/* are we in TRAU frame sync or not? */
+	unsigned int in_sync;
+};
+
+struct subch_demux {
+	/* bitmask of currently active subchannels */
+	u_int8_t chan_activ;
+	/* one demux_subch struct for every subchannel */
+	struct demux_subch subch[NR_SUBCH];
+	/* callback to be called once we have received a complete
+	 * frame on a given subchannel */
+	int (*out_cb)(struct subch_demux *dmx, int ch, u_int8_t *data, int len,
+		      void *);
+	/* user-provided data, transparently passed to out_cb() */
+	void *data;
+};
+
+/* initialize one demultiplexer instance */
+int subch_demux_init(struct subch_demux *dmx);
+
+/* feed 'len' number of muxed bytes into the demultiplexer */
+int subch_demux_in(struct subch_demux *dmx, u_int8_t *data, int len);
+
+/* activate decoding/processing for one subchannel */
+int subch_demux_activate(struct subch_demux *dmx, int subch);
+
+/* deactivate decoding/processing for one subchannel */
+int subch_demux_deactivate(struct subch_demux *dmx, int subch);
+
+/***********************************************************************/
+/* MULTIPLEXER */
+/***********************************************************************/
+
+/* one element in the tx_queue of a muxer sub-channel */
+struct subch_txq_entry {
+	struct llist_head list;
+
+	unsigned int bit_len;	/* total number of bits in 'bits' */
+	unsigned int next_bit;	/* next bit to be transmitted */
+
+	u_int8_t bits[0];	/* one bit per byte */
+};
+
+struct mux_subch {
+	struct llist_head tx_queue;
+};
+
+/* structure representing one instance of the subchannel muxer */
+struct subch_mux {
+	struct mux_subch subch[NR_SUBCH];
+};
+
+/* initialize a subchannel muxer instance */
+int subchan_mux_init(struct subch_mux *mx);
+
+/* request the output of 'len' multiplexed bytes */
+int subchan_mux_out(struct subch_mux *mx, u_int8_t *data, int len);
+
+/* enqueue some data into one sub-channel of the muxer */
+int subchan_mux_enqueue(struct subch_mux *mx, int s_nr, const u_int8_t *data,
+			int len);
+
+#endif /* _SUBCH_DEMUX_H */
diff --git a/openbsc/include/openbsc/system_information.h b/openbsc/include/openbsc/system_information.h
new file mode 100644
index 0000000..982a9ac
--- /dev/null
+++ b/openbsc/include/openbsc/system_information.h
@@ -0,0 +1,6 @@
+#ifndef _SYSTEM_INFO_H
+#define _SYSTEM_INFO_H
+
+int gsm_generate_si(u_int8_t *output, struct gsm_bts *bts, int type);
+
+#endif
diff --git a/openbsc/include/openbsc/telnet_interface.h b/openbsc/include/openbsc/telnet_interface.h
new file mode 100644
index 0000000..b8c36b6
--- /dev/null
+++ b/openbsc/include/openbsc/telnet_interface.h
@@ -0,0 +1,43 @@
+/* minimalistic telnet/network interface it might turn into a wire interface */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef TELNET_INTERFACE_H
+#define TELNET_INTERFACE_H
+
+#include "gsm_data.h"
+#include <openbsc/debug.h>
+#include <osmocore/select.h>
+
+#include <vty/vty.h>
+
+struct telnet_connection {
+	struct llist_head entry;
+	struct gsm_network *network;
+	struct bsc_fd fd;
+	struct vty *vty;
+	struct log_target *dbg;
+};
+
+
+void telnet_init(struct gsm_network *network, int port);
+
+int bsc_vty_init(struct gsm_network *net);
+
+#endif
diff --git a/openbsc/include/openbsc/transaction.h b/openbsc/include/openbsc/transaction.h
new file mode 100644
index 0000000..90a008b
--- /dev/null
+++ b/openbsc/include/openbsc/transaction.h
@@ -0,0 +1,76 @@
+#ifndef _TRANSACT_H
+#define _TRANSACT_H
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_04_11.h>
+
+/* One transaction */
+struct gsm_trans {
+	/* Entry in list of all transactions */
+	struct llist_head entry;
+
+	/* The protocol within which we live */
+	u_int8_t protocol;
+
+	/* The current transaction ID */
+	u_int8_t transaction_id;
+	
+	/* To whom we belong, unique identifier of remote MM entity */
+	struct gsm_subscriber *subscr;
+
+	/* The associated connection we are using to transmit messages */
+	struct gsm_subscriber_connection *conn;
+
+	/* reference from MNCC or other application */
+	u_int32_t callref;
+
+	/* if traffic channel receive was requested */
+	int tch_recv;
+
+	union {
+		struct {
+
+			/* current call state */
+			int state;
+
+			/* current timer and message queue */
+			int Tcurrent;		/* current CC timer */
+			int T308_second;	/* used to send release again */
+			struct timer_list timer;
+			struct gsm_mncc msg;	/* stores setup/disconnect/release message */
+		} cc;
+		struct {
+			u_int8_t link_id;	/* RSL Link ID to be used for this trans */
+			int is_mt;	/* is this a MO (0) or MT (1) transfer */
+			enum gsm411_cp_state cp_state;
+			struct timer_list cp_timer;
+
+			enum gsm411_rp_state rp_state;
+
+			struct gsm_sms *sms;
+		} sms;
+	};
+};
+
+
+
+struct gsm_trans *trans_find_by_id(struct gsm_subscriber *subscr,
+				   u_int8_t proto, u_int8_t trans_id);
+struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
+					u_int32_t callref);
+
+struct gsm_trans *trans_alloc(struct gsm_subscriber *subscr,
+			      u_int8_t protocol, u_int8_t trans_id,
+			      u_int32_t callref);
+void trans_free(struct gsm_trans *trans);
+
+int trans_assign_trans_id(struct gsm_subscriber *subscr,
+			  u_int8_t protocol, u_int8_t ti_flag);
+
+/* update all transactions to use a different LCHAN, e.g.
+ * after handover has succeeded */
+int trans_lchan_change(struct gsm_subscriber_connection *conn_old,
+		       struct gsm_subscriber_connection *conn_new);
+#endif
diff --git a/openbsc/include/openbsc/trau_frame.h b/openbsc/include/openbsc/trau_frame.h
new file mode 100644
index 0000000..5923d4a
--- /dev/null
+++ b/openbsc/include/openbsc/trau_frame.h
@@ -0,0 +1,65 @@
+#ifndef _TRAU_FRAME_H
+#define _TRAU_FRAME_H
+/* TRAU frame handling according to GSM TS 08.60 */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+
+/* 21 for FR/EFR, 25 for AMR, 15 for OM, 15 for data, 13 for E-data, 21 idle */
+#define MAX_C_BITS	25
+/* 260 for FR/EFR, 256 for AMR, 264 for OM, 288 for E-data */
+#define MAX_D_BITS	288
+/* for all speech frames */
+#define MAX_T_BITS	4
+/* for OM */
+#define MAX_S_BITS	6
+/* for E-data */
+#define MAX_M_BITS	2
+
+struct decoded_trau_frame {
+	u_int8_t c_bits[MAX_C_BITS];
+	u_int8_t d_bits[MAX_D_BITS];
+	u_int8_t t_bits[MAX_T_BITS];
+	u_int8_t s_bits[MAX_S_BITS];
+	u_int8_t m_bits[MAX_M_BITS];
+};
+
+#define TRAU_FT_FR_UP		0x02	/* 0 0 0 1 0 - 3.5.1.1.1 */
+#define TRAU_FT_FR_DOWN		0x1c	/* 1 1 1 0 0 - 3.5.1.1.1 */
+#define TRAU_FT_EFR		0x1a	/* 1 1 0 1 0 - 3.5.1.1.1 */
+#define TRAU_FT_AMR		0x06	/* 0 0 1 1 0 - 3.5.1.2 */
+#define TRAU_FT_OM_UP		0x07	/* 0 0 1 0 1 - 3.5.2 */
+#define TRAU_FT_OM_DOWN		0x1b	/* 1 1 0 1 1 - 3.5.2 */
+#define TRAU_FT_DATA_UP		0x08	/* 0 1 0 0 0 - 3.5.3 */
+#define TRAU_FT_DATA_DOWN	0x16	/* 1 0 1 1 0 - 3.5.3 */
+#define TRAU_FT_D145_SYNC	0x14	/* 1 0 1 0 0 - 3.5.3 */
+#define TRAU_FT_EDATA		0x1f	/* 1 1 1 1 1 - 3.5.4 */
+#define TRAU_FT_IDLE_UP		0x10	/* 1 0 0 0 0 - 3.5.5 */
+#define TRAU_FT_IDLE_DOWN	0x0e	/* 0 1 1 1 0 - 3.5.5 */
+
+
+int decode_trau_frame(struct decoded_trau_frame *fr, const u_int8_t *trau_bits);
+int encode_trau_frame(u_int8_t *trau_bits, const struct decoded_trau_frame *fr);
+int trau_frame_up2down(struct decoded_trau_frame *fr);
+u_int8_t *trau_idle_frame(void);
+
+
+#endif /* _TRAU_FRAME_H */
diff --git a/openbsc/include/openbsc/trau_mux.h b/openbsc/include/openbsc/trau_mux.h
new file mode 100644
index 0000000..8deb708
--- /dev/null
+++ b/openbsc/include/openbsc/trau_mux.h
@@ -0,0 +1,49 @@
+/* Simple TRAU frame reflector to route voice calls */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/* The "TRAU mux map" defines which particular 16kbit sub-slot (in which E1
+ * timeslot on which E1 interface) should be directly muxed to which other 
+ * sub-slot.  Entries in the mux map are always bi-directional. 
+ *
+ * The idea of all this is to directly switch voice channels in the BSC
+ * from one phone to another.  We do this right now since we don't support
+ * any external interface for voice channels, and in the future as an
+ * optimization to routing them externally.
+ */
+
+/* map a TRAU mux map entry */
+int trau_mux_map(const struct gsm_e1_subslot *src,
+		 const struct gsm_e1_subslot *dst);
+int trau_mux_map_lchan(const struct gsm_lchan *src,	
+			const struct gsm_lchan *dst);
+
+/* unmap a TRAU mux map entry */
+int trau_mux_unmap(const struct gsm_e1_subslot *ss, u_int32_t callref);
+
+/* we get called by subchan_demux */
+int trau_mux_input(struct gsm_e1_subslot *src_e1_ss,
+		   const u_int8_t *trau_bits, int num_bits);
+
+/* add a trau receiver */
+int trau_recv_lchan(struct gsm_lchan *lchan, u_int32_t callref);
+
+/* send trau from application */
+int trau_send_frame(struct gsm_lchan *lchan, struct gsm_data_frame *frame);
diff --git a/openbsc/include/openbsc/ussd.h b/openbsc/include/openbsc/ussd.h
new file mode 100644
index 0000000..63ea31c
--- /dev/null
+++ b/openbsc/include/openbsc/ussd.h
@@ -0,0 +1,10 @@
+#ifndef _USSD_H
+#define _USSD_H
+
+/* Handler function for mobile-originated USSD messages */
+
+#include <osmocore/msgb.h>
+
+int handle_rcv_ussd(struct msgb *msg);
+
+#endif
diff --git a/openbsc/include/openbsc/vty.h b/openbsc/include/openbsc/vty.h
new file mode 100644
index 0000000..f1b1148
--- /dev/null
+++ b/openbsc/include/openbsc/vty.h
@@ -0,0 +1,10 @@
+#ifndef OPENBSC_VTY_H
+#define OPENBSC_VTY_H
+
+struct gsm_network;
+struct vty;
+
+void openbsc_vty_add_cmds(void);
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *);
+
+#endif
diff --git a/openbsc/include/sccp/Makefile.am b/openbsc/include/sccp/Makefile.am
new file mode 100644
index 0000000..6c8a517
--- /dev/null
+++ b/openbsc/include/sccp/Makefile.am
@@ -0,0 +1,2 @@
+sccp_HEADERS = sccp_types.h sccp.h
+sccpdir = $(includedir)/sccp
diff --git a/openbsc/include/sccp/sccp.h b/openbsc/include/sccp/sccp.h
new file mode 100644
index 0000000..604a2ac
--- /dev/null
+++ b/openbsc/include/sccp/sccp.h
@@ -0,0 +1,172 @@
+/*
+ * SCCP management code
+ *
+ * (C) 2009, 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef SCCP_H
+#define SCCP_H
+
+#include <stdlib.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "sccp_types.h"
+
+struct msgb;
+struct sccp_system;
+
+enum {
+	SCCP_CONNECTION_STATE_NONE,
+	SCCP_CONNECTION_STATE_REQUEST,
+	SCCP_CONNECTION_STATE_CONFIRM,
+	SCCP_CONNECTION_STATE_ESTABLISHED,
+	SCCP_CONNECTION_STATE_RELEASE,
+	SCCP_CONNECTION_STATE_RELEASE_COMPLETE,
+	SCCP_CONNECTION_STATE_REFUSED,
+	SCCP_CONNECTION_STATE_SETUP_ERROR,
+};
+
+struct sockaddr_sccp {
+	sa_family_t	sccp_family;		/* AF_SCCP in the future??? */
+	u_int8_t	sccp_ssn;		/* subssystem number for routing */
+
+	/* TODO fill in address indicator... if that is ever needed */
+
+	/* not sure about these */
+	/* u_int8_t    sccp_class; */
+};
+
+/*
+ * parsed structure of an address
+ */
+struct sccp_address {
+	struct sccp_called_party_address    address;
+	u_int8_t			    ssn;
+	u_int8_t			    poi[2];
+};
+
+struct sccp_optional_data {
+	u_int8_t			    data_len;
+	u_int8_t			    data_start;
+};
+
+struct sccp_connection {
+	/* public */
+	void *data_ctx;
+	void (*data_cb)(struct sccp_connection *conn, struct msgb *msg, unsigned int len);
+
+	void *state_ctx;
+	void (*state_cb)(struct sccp_connection *, int old_state);
+
+	struct sccp_source_reference source_local_reference;
+	struct sccp_source_reference destination_local_reference;
+
+	int connection_state;
+
+	/* private */
+	/* list of active connections */
+	struct llist_head list;
+	struct sccp_system *system;
+	int incoming;
+};
+
+/**
+ * system functionality to implement on top of any other transport layer:
+ *   call sccp_system_incoming for incoming data (from the network)
+ *   sccp will call outgoing whenever outgoing data exists
+ */
+int sccp_system_init(void (*outgoing)(struct msgb *data, void *ctx), void *context);
+int sccp_system_incoming(struct msgb *data);
+
+/**
+ * Send data on an existing connection
+ */
+int sccp_connection_write(struct sccp_connection *connection, struct msgb *data);
+int sccp_connection_send_it(struct sccp_connection *connection);
+int sccp_connection_close(struct sccp_connection *connection, int cause);
+int sccp_connection_free(struct sccp_connection *connection);
+
+/**
+ * internal.. 
+ */
+int sccp_connection_force_free(struct sccp_connection *conn);
+
+/**
+ * Create a new socket. Set your callbacks and then call bind to open
+ * the connection.
+ */
+struct sccp_connection *sccp_connection_socket(void);
+
+/**
+ * Open the connection and send additional data
+ */
+int sccp_connection_connect(struct sccp_connection *conn,
+			    const struct sockaddr_sccp *sccp_called,
+			    struct msgb *data);
+
+/**
+ * mostly for testing purposes only. Set the accept callback.
+ * TODO: add true routing information... in analogy to socket, bind, accept
+ */
+int sccp_connection_set_incoming(const struct sockaddr_sccp *sock,
+				 int (*accept_cb)(struct sccp_connection *connection, void *data),
+				 void *user_data);
+
+/**
+ * Send data in terms of unit data. A fixed address indicator will be used.
+ */
+int sccp_write(struct msgb *data,
+	       const struct sockaddr_sccp *sock_sender,
+	       const struct sockaddr_sccp *sock_target, int class);
+int sccp_set_read(const struct sockaddr_sccp *sock,
+		  int (*read_cb)(struct msgb *msgb, unsigned int, void *user_data),
+		  void *user_data);
+
+/* generic sock addresses */
+extern const struct sockaddr_sccp sccp_ssn_bssap;
+
+/* helpers */
+u_int32_t sccp_src_ref_to_int(struct sccp_source_reference *ref);
+struct sccp_source_reference sccp_src_ref_from_int(u_int32_t);
+
+/**
+ * Below this are helper functions and structs for parsing SCCP messages
+ */
+struct sccp_parse_result {
+	struct sccp_address called;
+	struct sccp_address calling;
+
+	/* point to the msg packet */
+	struct sccp_source_reference *source_local_reference;
+	struct sccp_source_reference *destination_local_reference;
+
+	/* data pointer */
+	int data_len;
+};
+
+/*
+ * helper functions for the nat code
+ */
+int sccp_determine_msg_type(struct msgb *msg);
+int sccp_parse_header(struct msgb *msg, struct sccp_parse_result *result);
+
+#endif
diff --git a/openbsc/include/sccp/sccp_types.h b/openbsc/include/sccp/sccp_types.h
new file mode 100644
index 0000000..22bd70f
--- /dev/null
+++ b/openbsc/include/sccp/sccp_types.h
@@ -0,0 +1,420 @@
+/*
+ * ITU Q.713 defined types for SCCP
+ *
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef SCCP_TYPES_H
+#define SCCP_TYPES_H
+
+#include <endian.h>
+
+/* Table 1/Q.713 - SCCP message types */
+enum sccp_message_types {
+	SCCP_MSG_TYPE_CR	= 1,
+	SCCP_MSG_TYPE_CC	= 2,
+	SCCP_MSG_TYPE_CREF	= 3,
+	SCCP_MSG_TYPE_RLSD	= 4,
+	SCCP_MSG_TYPE_RLC	= 5,
+	SCCP_MSG_TYPE_DT1	= 6,
+	SCCP_MSG_TYPE_DT2	= 7,
+	SCCP_MSG_TYPE_AK	= 8,
+	SCCP_MSG_TYPE_UDT	= 9,
+	SCCP_MSG_TYPE_UDTS	= 10,
+	SCCP_MSG_TYPE_ED	= 11,
+	SCCP_MSG_TYPE_EA	= 12,
+	SCCP_MSG_TYPE_RSR	= 13,
+	SCCP_MSG_TYPE_RSC	= 14,
+	SCCP_MSG_TYPE_ERR	= 15,
+	SCCP_MSG_TYPE_IT	= 16,
+	SCCP_MSG_TYPE_XUDT	= 17,
+	SCCP_MSG_TYPE_XUDTS	= 18,
+	SCCP_MSG_TYPE_LUDT	= 19,
+	SCCP_MSG_TYPE_LUDTS	= 20
+};
+
+/* Table 2/Q.713 - SCCP parameter name codes */
+enum sccp_parameter_name_codes {
+	SCCP_PNC_END_OF_OPTIONAL		= 0,
+	SCCP_PNC_DESTINATION_LOCAL_REFERENCE	= 1,
+	SCCP_PNC_SOURCE_LOCAL_REFERENCE		= 2,
+	SCCP_PNC_CALLED_PARTY_ADDRESS		= 3,
+	SCCP_PNC_CALLING_PARTY_ADDRESS		= 4,
+	SCCP_PNC_PROTOCOL_CLASS			= 5,
+	SCCP_PNC_SEGMENTING			= 6,
+	SCCP_PNC_RECEIVE_SEQ_NUMBER		= 7,
+	SCCP_PNC_SEQUENCING			= 8,
+	SCCP_PNC_CREDIT				= 9,
+	SCCP_PNC_RELEASE_CAUSE			= 10,
+	SCCP_PNC_RETURN_CAUSE			= 11,
+	SCCP_PNC_RESET_CAUSE			= 12,
+	SCCP_PNC_ERROR_CAUSE			= 13,
+	SCCP_PNC_REFUSAL_CAUSE			= 14,
+	SCCP_PNC_DATA				= 15,
+	SCCP_PNC_SEGMENTATION			= 16,
+	SCCP_PNC_HOP_COUNTER			= 17,
+	SCCP_PNC_IMPORTANCE			= 18,
+	SCCP_PNC_LONG_DATA			= 19,
+};
+
+/* Figure 3/Q.713 Called/calling party address */
+enum {
+	SCCP_TITLE_IND_NONE			= 0,
+	SCCP_TITLE_IND_NATURE_ONLY		= 1,
+	SCCP_TITLE_IND_TRANSLATION_ONLY		= 2,
+	SCCP_TITLE_IND_TRANS_NUM_ENC		= 3,
+	SCCP_TITLE_IND_TRANS_NUM_ENC_NATURE	= 4,
+};
+
+enum {
+	SCCP_CALL_ROUTE_ON_SSN			= 1,
+	SCCP_CALL_ROUTE_ON_GT			= 0,
+};
+
+struct sccp_called_party_address {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	u_int8_t	point_code_indicator : 1,
+			ssn_indicator	     : 1,
+			global_title_indicator : 4,
+			routing_indicator    : 1,
+			reserved	     : 1;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	u_int8_t	reserved	     : 1,
+			routing_indicator    : 1,
+			global_title_indicator : 4,
+			ssn_indicator	     : 1,
+			point_code_indicator : 1;
+#endif
+	u_int8_t	data[0];
+} __attribute__((packed));
+
+/* indicator indicates presence in the above order */
+
+/* Figure 6/Q.713 */
+struct sccp_signalling_point_code {
+	u_int8_t	lsb;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	u_int8_t	msb : 6,
+			reserved : 2;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	u_int8_t	reserved : 2,
+			msb : 6;
+#endif
+} __attribute__((packed));
+
+/* SSN == subsystem number */
+enum sccp_subsystem_number {
+	SCCP_SSN_NOT_KNOWN_OR_USED	    = 0,
+	SCCP_SSN_MANAGEMENT		    = 1,
+	SCCP_SSN_RESERVED_ITU		    = 2,
+	SCCP_SSN_ISDN_USER_PART		    = 3,
+	SCCP_SSN_OMAP			    = 4, /* operation, maint and administration part */
+	SCCP_SSN_MAP			    = 5, /* mobile application part */
+	SCCP_SSN_HLR			    = 6,
+	SCCP_SSN_VLR			    = 7,
+	SCCP_SSN_MSC			    = 8,
+	SCCP_SSN_EIC			    = 9, /* equipent identifier centre */
+	SCCP_SSN_AUC			    = 10, /* authentication centre */
+	SCCP_SSN_ISDN_SUPPL_SERVICES	    = 11,
+	SCCP_SSN_RESERVED_INTL		    = 12,
+	SCCP_SSN_ISDN_EDGE_TO_EDGE	    = 13,
+	SCCP_SSN_TC_TEST_RESPONDER	    = 14,
+
+	/* From GSM 03.03 8.2 */
+	SCCP_SSN_BSSAP			    = 254,
+	SCCP_SSN_BSSOM			    = 253,
+};
+
+/* Q.713, 3.4.2.3 */
+enum {
+	SCCP_NAI_UNKNOWN		    = 0,
+	SCCP_NAI_SUBSCRIBER_NUMBER	    = 1,
+	SCCP_NAI_RESERVED_NATIONAL	    = 2,
+	SCCP_NAI_NATIONAL_SIGNIFICANT	    = 3,
+	SCCP_NAI_INTERNATIONAL		    = 4,
+};
+
+struct sccp_global_title {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	u_int8_t	nature_of_addr_ind : 7,
+			odd_even : 1;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	u_int8_t	odd_even : 1,
+			nature_of_addr_ind : 7;
+#endif
+	u_int8_t	data[0];
+} __attribute__((packed));
+
+/* Q.713, 3.3 */
+struct sccp_source_reference {
+	u_int8_t    octet1;
+	u_int8_t    octet2;
+	u_int8_t    octet3;
+} __attribute__((packed));
+
+/* Q.714, 3.6 */
+enum sccp_protocol_class {
+	SCCP_PROTOCOL_CLASS_0		    = 0,
+	SCCP_PROTOCOL_CLASS_1		    = 1,
+	SCCP_PROTOCOL_CLASS_2		    = 2,
+	SCCP_PROTOCOL_CLASS_3		    = 3,
+};
+
+/* bits 5-8 when class0, class1 is used */
+enum sccp_protocol_options {
+	SCCP_PROTOCOL_NO_SPECIAL	    = 0,
+	SCCP_PROTOCOL_RETURN_MESSAGE	    = 8,
+};
+
+enum sccp_release_cause {
+	SCCP_RELEASE_CAUSE_END_USER_ORIGINATED	    = 0,
+	SCCP_RELEASE_CAUSE_END_USER_CONGESTION	    = 1,
+	SCCP_RELEASE_CAUSE_END_USER_FAILURE	    = 2,
+	SCCP_RELEASE_CAUSE_SCCP_USER_ORIGINATED	    = 3,
+	SCCP_RELEASE_CAUSE_REMOTE_PROCEDURE_ERROR   = 4,
+	SCCP_RELEASE_CAUSE_INCONSISTENT_CONN_DATA   = 5,
+	SCCP_RELEASE_CAUSE_ACCESS_FAILURE	    = 6,
+	SCCP_RELEASE_CAUSE_ACCESS_CONGESTION	    = 7,
+	SCCP_RELEASE_CAUSE_SUBSYSTEM_FAILURE	    = 8,
+	SCCP_RELEASE_CAUSE_SUBSYSTEM_CONGESTION	    = 9,
+	SCCP_RELEASE_CAUSE_MTP_FAILURE		    = 10,
+	SCCP_RELEASE_CAUSE_NETWORK_CONGESTION	    = 11,
+	SCCP_RELEASE_CAUSE_EXPIRATION_RESET	    = 12,
+	SCCP_RELEASE_CAUSE_EXPIRATION_INACTIVE	    = 13,
+	SCCP_RELEASE_CAUSE_RESERVED		    = 14,
+	SCCP_RELEASE_CAUSE_UNQUALIFIED		    = 15,
+	SCCP_RELEASE_CAUSE_SCCP_FAILURE		    = 16,
+};
+
+enum sccp_return_cause {
+	SCCP_RETURN_CAUSE_NO_TRANSLATION_NATURE	    = 0,
+	SCCP_RETURN_CAUSE_NO_TRANSLATION	    = 1,
+	SCCP_RETURN_CAUSE_SUBSYSTEM_CONGESTION	    = 2,
+	SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE	    = 3,
+	SCCP_RETURN_CAUSE_UNEQUIPPED_USER	    = 4,
+	SCCP_RETURN_CAUSE_MTP_FAILURE		    = 5,
+	SCCP_RETURN_CAUSE_NETWORK_CONGESTION	    = 6,
+	SCCP_RETURN_CAUSE_UNQUALIFIED		    = 7,
+	SCCP_RETURN_CAUSE_ERROR_IN_MSG_TRANSPORT    = 8,
+	SCCP_RETURN_CAUSE_ERROR_IN_LOCAL_PROCESSING = 9,
+	SCCP_RETURN_CAUSE_DEST_CANNOT_PERFORM_REASSEMBLY = 10,
+	SCCP_RETURN_CAUSE_SCCP_FAILURE		    = 11,
+	SCCP_RETURN_CAUSE_HOP_COUNTER_VIOLATION	    = 12,
+	SCCP_RETURN_CAUSE_SEGMENTATION_NOT_SUPPORTED= 13,
+	SCCP_RETURN_CAUSE_SEGMENTATION_FAOLURE	    = 14
+};
+
+enum sccp_reset_cause {
+	SCCP_RESET_CAUSE_END_USER_ORIGINATED	    = 0,
+	SCCP_RESET_CAUSE_SCCP_USER_ORIGINATED	    = 1,
+	SCCP_RESET_CAUSE_MSG_OUT_OF_ORDER_PS	    = 2,
+	SCCP_RESET_CAUSE_MSG_OUT_OF_ORDER_PR	    = 3,
+	SCCP_RESET_CAUSE_RPC_OUT_OF_WINDOW	    = 4,
+	SCCP_RESET_CAUSE_RPC_INCORRECT_PS	    = 5,
+	SCCP_RESET_CAUSE_RPC_GENERAL		    = 6,
+	SCCP_RESET_CAUSE_REMOTE_END_USER_OPERATIONAL= 7,
+	SCCP_RESET_CAUSE_NETWORK_OPERATIONAL	    = 8,
+	SCCP_RESET_CAUSE_ACCESS_OPERATIONAL	    = 9,
+	SCCP_RESET_CAUSE_NETWORK_CONGESTION	    = 10,
+	SCCP_RESET_CAUSE_RESERVED		    = 11,
+};
+
+enum sccp_error_cause {
+	SCCP_ERROR_LRN_MISMATCH_UNASSIGNED	    = 0, /* local reference number */
+	SCCP_ERROR_LRN_MISMATCH_INCONSISTENT	    = 1,
+	SCCP_ERROR_POINT_CODE_MISMATCH		    = 2,
+	SCCP_ERROR_SERVICE_CLASS_MISMATCH	    = 3,
+	SCCP_ERROR_UNQUALIFIED			    = 4,
+};
+
+enum sccp_refusal_cause {
+	SCCP_REFUSAL_END_USER_ORIGINATED	    = 0,
+	SCCP_REFUSAL_END_USER_CONGESTION	    = 1,
+	SCCP_REFUSAL_END_USER_FAILURE		    = 2,
+	SCCP_REFUSAL_SCCP_USER_ORIGINATED	    = 3,
+	SCCP_REFUSAL_DESTINATION_ADDRESS_UKNOWN	    = 4,
+	SCCP_REFUSAL_DESTINATION_INACCESSIBLE	    = 5,
+	SCCP_REFUSAL_NET_QOS_NON_TRANSIENT	    = 6,
+	SCCP_REFUSAL_NET_QOS_TRANSIENT		    = 7,
+	SCCP_REFUSAL_ACCESS_FAILURE		    = 8,
+	SCCP_REFUSAL_ACCESS_CONGESTION		    = 9,
+	SCCP_REFUSAL_SUBSYSTEM_FAILURE		    = 10,
+	SCCP_REFUSAL_SUBSYTEM_CONGESTION	    = 11,
+	SCCP_REFUSAL_EXPIRATION			    = 12,
+	SCCP_REFUSAL_INCOMPATIBLE_USER_DATA	    = 13,
+	SCCP_REFUSAL_RESERVED			    = 14,
+	SCCP_REFUSAL_UNQUALIFIED		    = 15,
+	SCCP_REFUSAL_HOP_COUNTER_VIOLATION	    = 16,
+	SCCP_REFUSAL_SCCP_FAILURE		    = 17,
+	SCCP_REFUSAL_UNEQUIPPED_USER		    = 18,
+};
+
+/*
+ * messages... as of Q.713 Chapter 4
+ */
+struct sccp_connection_request {
+	/* mandantory */
+	u_int8_t			type;
+	struct sccp_source_reference	source_local_reference;
+	u_int8_t			proto_class;
+
+
+	/* variable */
+	u_int8_t			variable_called;
+#if VARIABLE
+	called_party_address
+#endif
+
+	/* optional */
+	u_int8_t			optional_start;
+
+#if OPTIONAL
+	credit 3
+	callingparty var 4-n
+	data            3-130
+	hop_counter     3
+	importance      3
+	end_of_optional 1
+#endif
+
+	u_int8_t			data[0];
+} __attribute__((packed));
+
+struct sccp_connection_confirm {
+	/* mandantory */
+	u_int8_t			type;
+	struct sccp_source_reference	destination_local_reference;
+	struct sccp_source_reference	source_local_reference;
+	u_int8_t			proto_class;
+
+	/* optional */
+	u_int8_t			optional_start;
+
+	/* optional */
+#if OPTIONAL
+	credit 3
+	called party 4
+	data            3-130
+	importance      3
+	end_of_optional 1
+#endif
+
+	u_int8_t			data[0];
+} __attribute__((packed));
+
+struct sccp_connection_refused {
+	/* mandantory */
+	u_int8_t			type;
+	struct sccp_source_reference	destination_local_reference;
+	u_int8_t			cause;
+
+	/* optional */
+	u_int8_t			optional_start;
+
+	/* optional */
+#if OPTIONAL
+	called party 4
+	data            3-130
+	importance      3
+	end_of_optional 1
+#endif
+
+	u_int8_t			data[0];
+} __attribute__((packed));
+
+struct sccp_connection_released {
+	/* mandantory */
+	u_int8_t			type;
+	struct sccp_source_reference	destination_local_reference;
+	struct sccp_source_reference	source_local_reference;
+	u_int8_t			release_cause;
+
+
+	/* optional */
+	u_int8_t			optional_start;
+
+#if OPTIONAL
+	data            3-130
+	importance      3
+	end_of_optional 1
+#endif
+	u_int8_t			data[0];
+} __attribute__((packed));
+
+struct sccp_connection_release_complete {
+	u_int8_t			type;
+	struct sccp_source_reference	destination_local_reference;
+	struct sccp_source_reference	source_local_reference;
+} __attribute__((packed));
+
+struct sccp_data_form1 {
+	/* mandantory */
+	u_int8_t			type;
+	struct sccp_source_reference	destination_local_reference;
+	u_int8_t			segmenting;
+
+	/* variable */
+	u_int8_t			variable_start;
+
+#if VARIABLE
+	data 2-256;
+#endif
+
+	u_int8_t			data[0];
+} __attribute__((packed));
+
+
+struct sccp_data_unitdata {
+	/* mandantory */
+	u_int8_t			type;
+	u_int8_t			proto_class;
+
+
+	/* variable */
+	u_int8_t			variable_called;
+	u_int8_t			variable_calling;
+	u_int8_t			variable_data;
+
+#if VARIABLE
+	called party address
+	calling party address
+#endif
+
+	u_int8_t			data[0];
+} __attribute__((packed));
+
+struct sccp_data_it {
+	/* mandantory */
+	u_int8_t			type;
+	struct sccp_source_reference	destination_local_reference;
+	struct sccp_source_reference	source_local_reference;
+	u_int8_t			proto_class;
+
+	u_int8_t			sequencing[2];
+	u_int8_t			credit;
+} __attribute__((packed));
+
+struct sccp_proto_err {
+	u_int8_t			type;
+	struct sccp_source_reference	destination_local_reference;
+	u_int8_t			error_cause;
+};
+
+#endif
diff --git a/openbsc/include/vty/Makefile.am b/openbsc/include/vty/Makefile.am
new file mode 100644
index 0000000..1674766
--- /dev/null
+++ b/openbsc/include/vty/Makefile.am
@@ -0,0 +1 @@
+noinst_HEADERS = buffer.h command.h  vector.h vty.h
diff --git a/openbsc/include/vty/buffer.h b/openbsc/include/vty/buffer.h
new file mode 100644
index 0000000..c9467a9
--- /dev/null
+++ b/openbsc/include/vty/buffer.h
@@ -0,0 +1,102 @@
+/*
+ * Buffering to output and input.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_BUFFER_H
+#define _ZEBRA_BUFFER_H
+
+#include <sys/types.h>
+
+/* Create a new buffer.  Memory will be allocated in chunks of the given
+   size.  If the argument is 0, the library will supply a reasonable
+   default size suitable for buffering socket I/O. */
+struct buffer *buffer_new(void *ctx, size_t);
+
+/* Free all data in the buffer. */
+void buffer_reset(struct buffer *);
+
+/* This function first calls buffer_reset to release all buffered data.
+   Then it frees the struct buffer itself. */
+void buffer_free(struct buffer *);
+
+/* Add the given data to the end of the buffer. */
+extern void buffer_put(struct buffer *, const void *, size_t);
+/* Add a single character to the end of the buffer. */
+extern void buffer_putc(struct buffer *, u_char);
+/* Add a NUL-terminated string to the end of the buffer. */
+extern void buffer_putstr(struct buffer *, const char *);
+
+/* Combine all accumulated (and unflushed) data inside the buffer into a
+   single NUL-terminated string allocated using XMALLOC(MTYPE_TMP).  Note
+   that this function does not alter the state of the buffer, so the data
+   is still inside waiting to be flushed. */
+char *buffer_getstr(struct buffer *);
+
+/* Returns 1 if there is no pending data in the buffer.  Otherwise returns 0. */
+int buffer_empty(struct buffer *);
+
+typedef enum {
+	/* An I/O error occurred.  The buffer should be destroyed and the
+	   file descriptor should be closed. */
+	BUFFER_ERROR = -1,
+
+	/* The data was written successfully, and the buffer is now empty
+	   (there is no pending data waiting to be flushed). */
+	BUFFER_EMPTY = 0,
+
+	/* There is pending data in the buffer waiting to be flushed.  Please
+	   try flushing the buffer when select indicates that the file descriptor
+	   is writeable. */
+	BUFFER_PENDING = 1
+} buffer_status_t;
+
+/* Try to write this data to the file descriptor.  Any data that cannot
+   be written immediately is added to the buffer queue. */
+extern buffer_status_t buffer_write(struct buffer *, int fd,
+				    const void *, size_t);
+
+/* This function attempts to flush some (but perhaps not all) of
+   the queued data to the given file descriptor. */
+extern buffer_status_t buffer_flush_available(struct buffer *, int fd);
+
+/* The following 2 functions (buffer_flush_all and buffer_flush_window)
+   are for use in lib/vty.c only.  They should not be used elsewhere. */
+
+/* Call buffer_flush_available repeatedly until either all data has been
+   flushed, or an I/O error has been encountered, or the operation would
+   block. */
+extern buffer_status_t buffer_flush_all(struct buffer *, int fd);
+
+/* Attempt to write enough data to the given fd to fill a window of the
+   given width and height (and remove the data written from the buffer).
+
+   If !no_more, then a message saying " --More-- " is appended.
+   If erase is true, then first overwrite the previous " --More-- " message
+   with spaces.
+
+   Any write error (including EAGAIN or EINTR) will cause this function
+   to return -1 (because the logic for handling the erase and more features
+   is too complicated to retry the write later).
+*/
+extern buffer_status_t buffer_flush_window(struct buffer *, int fd, int width,
+					   int height, int erase, int no_more);
+
+#endif				/* _ZEBRA_BUFFER_H */
diff --git a/openbsc/include/vty/command.h b/openbsc/include/vty/command.h
new file mode 100644
index 0000000..5f7c819
--- /dev/null
+++ b/openbsc/include/vty/command.h
@@ -0,0 +1,384 @@
+/*
+ * Zebra configuration command interface routine
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_COMMAND_H
+#define _ZEBRA_COMMAND_H
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "vector.h"
+#include "vty.h"
+
+/* Host configuration variable */
+struct host {
+	/* Host name of this router. */
+	char *name;
+
+	/* Password for vty interface. */
+	char *password;
+	char *password_encrypt;
+
+	/* Enable password */
+	char *enable;
+	char *enable_encrypt;
+
+	/* System wide terminal lines. */
+	int lines;
+
+	/* Log filename. */
+	char *logfile;
+
+	/* config file name of this host */
+	char *config;
+
+	/* Flags for services */
+	int advanced;
+	int encrypt;
+
+	/* Banner configuration. */
+	const char *motd;
+	char *motdfile;
+};
+
+/* There are some command levels which called from command node. */
+enum node_type {
+	AUTH_NODE,		/* Authentication mode of vty interface. */
+	VIEW_NODE,		/* View node. Default mode of vty interface. */
+	AUTH_ENABLE_NODE,	/* Authentication mode for change enable. */
+	ENABLE_NODE,		/* Enable node. */
+	CONFIG_NODE,		/* Config node. Default mode of config file. */
+	SERVICE_NODE,		/* Service node. */
+	DEBUG_NODE,		/* Debug node. */
+#if 0
+	AAA_NODE,		/* AAA node. */
+	KEYCHAIN_NODE,		/* Key-chain node. */
+	KEYCHAIN_KEY_NODE,	/* Key-chain key node. */
+	INTERFACE_NODE,		/* Interface mode node. */
+	ZEBRA_NODE,		/* zebra connection node. */
+	TABLE_NODE,		/* rtm_table selection node. */
+	RIP_NODE,		/* RIP protocol mode node. */
+	RIPNG_NODE,		/* RIPng protocol mode node. */
+	BGP_NODE,		/* BGP protocol mode which includes BGP4+ */
+	BGP_VPNV4_NODE,		/* BGP MPLS-VPN PE exchange. */
+	BGP_IPV4_NODE,		/* BGP IPv4 unicast address family.  */
+	BGP_IPV4M_NODE,		/* BGP IPv4 multicast address family.  */
+	BGP_IPV6_NODE,		/* BGP IPv6 address family */
+	OSPF_NODE,		/* OSPF protocol mode */
+	OSPF6_NODE,		/* OSPF protocol for IPv6 mode */
+	ISIS_NODE,		/* ISIS protocol mode */
+	MASC_NODE,		/* MASC for multicast.  */
+	IRDP_NODE,		/* ICMP Router Discovery Protocol mode. */
+	IP_NODE,		/* Static ip route node. */
+	ACCESS_NODE,		/* Access list node. */
+	PREFIX_NODE,		/* Prefix list node. */
+	ACCESS_IPV6_NODE,	/* Access list node. */
+	PREFIX_IPV6_NODE,	/* Prefix list node. */
+	AS_LIST_NODE,		/* AS list node. */
+	COMMUNITY_LIST_NODE,	/* Community list node. */
+	RMAP_NODE,		/* Route map node. */
+	SMUX_NODE,		/* SNMP configuration node. */
+	DUMP_NODE,		/* Packet dump node. */
+	FORWARDING_NODE,	/* IP forwarding node. */
+#endif
+	VTY_NODE,		/* Vty node. */
+
+	GSMNET_NODE,
+	BTS_NODE,
+	TRX_NODE,
+	TS_NODE,
+	SUBSCR_NODE,
+	MGCP_NODE,
+	GBPROXY_NODE,
+	SGSN_NODE,
+};
+
+/* Node which has some commands and prompt string and configuration
+   function pointer . */
+struct cmd_node {
+	/* Node index. */
+	enum node_type node;
+
+	/* Prompt character at vty interface. */
+	const char *prompt;
+
+	/* Is this node's configuration goes to vtysh ? */
+	int vtysh;
+
+	/* Node's configuration write function */
+	int (*func) (struct vty *);
+
+	/* Vector of this node's command list. */
+	vector cmd_vector;
+};
+
+enum {
+	CMD_ATTR_DEPRECATED = 1,
+	CMD_ATTR_HIDDEN,
+};
+
+/* Structure of command element. */
+struct cmd_element {
+	const char *string;	/* Command specification by string. */
+	int (*func) (struct cmd_element *, struct vty *, int, const char *[]);
+	const char *doc;	/* Documentation of this command. */
+	int daemon;		/* Daemon to which this command belong. */
+	vector strvec;		/* Pointing out each description vector. */
+	unsigned int cmdsize;	/* Command index count. */
+	char *config;		/* Configuration string */
+	vector subconfig;	/* Sub configuration string */
+	u_char attr;		/* Command attributes */
+};
+
+/* Command description structure. */
+struct desc {
+	const char *cmd;	/* Command string. */
+	const char *str;	/* Command's description. */
+};
+
+/* Return value of the commands. */
+#define CMD_SUCCESS              0
+#define CMD_WARNING              1
+#define CMD_ERR_NO_MATCH         2
+#define CMD_ERR_AMBIGUOUS        3
+#define CMD_ERR_INCOMPLETE       4
+#define CMD_ERR_EXEED_ARGC_MAX   5
+#define CMD_ERR_NOTHING_TODO     6
+#define CMD_COMPLETE_FULL_MATCH  7
+#define CMD_COMPLETE_MATCH       8
+#define CMD_COMPLETE_LIST_MATCH  9
+#define CMD_SUCCESS_DAEMON      10
+
+/* Argc max counts. */
+#define CMD_ARGC_MAX   25
+
+/* Turn off these macros when uisng cpp with extract.pl */
+#ifndef VTYSH_EXTRACT_PL
+
+/* helper defines for end-user DEFUN* macros */
+#define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
+  static struct cmd_element cmdname = \
+  { \
+    .string = cmdstr, \
+    .func = funcname, \
+    .doc = helpstr, \
+    .attr = attrs, \
+    .daemon = dnum, \
+  };
+
+/* global (non static) cmd_element */
+#define gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
+  struct cmd_element cmdname = \
+  { \
+    .string = cmdstr, \
+    .func = funcname, \
+    .doc = helpstr, \
+    .attr = attrs, \
+    .daemon = dnum, \
+  };
+
+#define DEFUN_CMD_FUNC_DECL(funcname) \
+  static int funcname (struct cmd_element *, struct vty *, int, const char *[]); \
+
+#define DEFUN_CMD_FUNC_TEXT(funcname) \
+  static int funcname \
+    (struct cmd_element *self, struct vty *vty, int argc, const char *argv[])
+
+/* DEFUN for vty command interafce. Little bit hacky ;-). */
+#define DEFUN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+/* global (non static) cmd_element */
+#define gDEFUN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)
+
+#define DEFUN_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) \
+
+/* DEFUN_NOSH for commands that vtysh should ignore */
+#define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN(funcname, cmdname, cmdstr, helpstr)
+
+/* DEFSH for vtysh. */
+#define DEFSH(daemon, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon) \
+
+/* DEFUN + DEFSH */
+#define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+/* DEFUN + DEFSH with attributes */
+#define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUNSH_ATTR (daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)
+
+#define DEFUNSH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUNSH_ATTR (daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED)
+
+/* ALIAS macro which define existing command's alias. */
+#define ALIAS(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)
+
+/* global (non static) cmd_element */
+#define gALIAS(funcname, cmdname, cmdstr, helpstr) \
+  gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)
+
+#define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0)
+
+#define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0)
+
+#define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0)
+
+#define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon)
+
+#define ALIAS_SH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, daemon)
+
+#define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, daemon)
+
+#endif				/* VTYSH_EXTRACT_PL */
+
+/* Some macroes */
+#define CMD_OPTION(S)   ((S[0]) == '[')
+#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ((S[0]) == '<'))
+#define CMD_VARARG(S)   ((S[0]) == '.')
+#define CMD_RANGE(S)	((S[0] == '<'))
+
+#define CMD_IPV4(S)	   ((strcmp ((S), "A.B.C.D") == 0))
+#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0))
+#define CMD_IPV6(S)        ((strcmp ((S), "X:X::X:X") == 0))
+#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0))
+
+/* Common descriptions. */
+#define SHOW_STR "Show running system information\n"
+#define IP_STR "IP information\n"
+#define IPV6_STR "IPv6 information\n"
+#define NO_STR "Negate a command or set its defaults\n"
+#define CLEAR_STR "Reset functions\n"
+#define RIP_STR "RIP information\n"
+#define BGP_STR "BGP information\n"
+#define OSPF_STR "OSPF information\n"
+#define NEIGHBOR_STR "Specify neighbor router\n"
+#define DEBUG_STR "Debugging functions (see also 'undebug')\n"
+#define UNDEBUG_STR "Disable debugging functions (see also 'debug')\n"
+#define ROUTER_STR "Enable a routing process\n"
+#define AS_STR "AS number\n"
+#define MBGP_STR "MBGP information\n"
+#define MATCH_STR "Match values from routing table\n"
+#define SET_STR "Set values in destination routing protocol\n"
+#define OUT_STR "Filter outgoing routing updates\n"
+#define IN_STR  "Filter incoming routing updates\n"
+#define V4NOTATION_STR "specify by IPv4 address notation(e.g. 0.0.0.0)\n"
+#define OSPF6_NUMBER_STR "Specify by number\n"
+#define INTERFACE_STR "Interface infomation\n"
+#define IFNAME_STR "Interface name(e.g. ep0)\n"
+#define IP6_STR "IPv6 Information\n"
+#define OSPF6_STR "Open Shortest Path First (OSPF) for IPv6\n"
+#define OSPF6_ROUTER_STR "Enable a routing process\n"
+#define OSPF6_INSTANCE_STR "<1-65535> Instance ID\n"
+#define SECONDS_STR "<1-65535> Seconds\n"
+#define ROUTE_STR "Routing Table\n"
+#define PREFIX_LIST_STR "Build a prefix list\n"
+#define OSPF6_DUMP_TYPE_LIST \
+"(neighbor|interface|area|lsa|zebra|config|dbex|spf|route|lsdb|redistribute|hook|asbr|prefix|abr)"
+#define ISIS_STR "IS-IS information\n"
+#define AREA_TAG_STR "[area tag]\n"
+
+#define CONF_BACKUP_EXT ".sav"
+
+/* IPv4 only machine should not accept IPv6 address for peer's IP
+   address.  So we replace VTY command string like below. */
+#ifdef HAVE_IPV6
+#define NEIGHBOR_CMD       "neighbor (A.B.C.D|X:X::X:X) "
+#define NO_NEIGHBOR_CMD    "no neighbor (A.B.C.D|X:X::X:X) "
+#define NEIGHBOR_ADDR_STR  "Neighbor address\nIPv6 address\n"
+#define NEIGHBOR_CMD2      "neighbor (A.B.C.D|X:X::X:X|WORD) "
+#define NO_NEIGHBOR_CMD2   "no neighbor (A.B.C.D|X:X::X:X|WORD) "
+#define NEIGHBOR_ADDR_STR2 "Neighbor address\nNeighbor IPv6 address\nNeighbor tag\n"
+#else
+#define NEIGHBOR_CMD       "neighbor A.B.C.D "
+#define NO_NEIGHBOR_CMD    "no neighbor A.B.C.D "
+#define NEIGHBOR_ADDR_STR  "Neighbor address\n"
+#define NEIGHBOR_CMD2      "neighbor (A.B.C.D|WORD) "
+#define NO_NEIGHBOR_CMD2   "no neighbor (A.B.C.D|WORD) "
+#define NEIGHBOR_ADDR_STR2 "Neighbor address\nNeighbor tag\n"
+#endif				/* HAVE_IPV6 */
+
+/* Prototypes. */
+void install_node(struct cmd_node *, int (*)(struct vty *));
+void install_default(enum node_type);
+void install_element(enum node_type, struct cmd_element *);
+void sort_node();
+
+/* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated
+   string with a space between each element (allocated using
+   XMALLOC(MTYPE_TMP)).  Returns NULL if shift >= argc. */
+char *argv_concat(const char **argv, int argc, int shift);
+
+vector cmd_make_strvec(const char *);
+void cmd_free_strvec(vector);
+vector cmd_describe_command();
+char **cmd_complete_command();
+const char *cmd_prompt(enum node_type);
+int config_from_file(struct vty *, FILE *);
+enum node_type node_parent(enum node_type);
+int cmd_execute_command(vector, struct vty *, struct cmd_element **, int);
+int cmd_execute_command_strict(vector, struct vty *, struct cmd_element **);
+void config_replace_string(struct cmd_element *, char *, ...);
+void cmd_init(int);
+
+/* Export typical functions. */
+extern struct cmd_element config_end_cmd;
+extern struct cmd_element config_exit_cmd;
+extern struct cmd_element config_quit_cmd;
+extern struct cmd_element config_help_cmd;
+extern struct cmd_element config_list_cmd;
+char *host_config_file();
+void host_config_set(const char *);
+
+void print_version(const char *);
+
+extern void *tall_vty_cmd_ctx;
+
+#endif				/* _ZEBRA_COMMAND_H */
diff --git a/openbsc/include/vty/vector.h b/openbsc/include/vty/vector.h
new file mode 100644
index 0000000..22a184d
--- /dev/null
+++ b/openbsc/include/vty/vector.h
@@ -0,0 +1,64 @@
+/*
+ * Generic vector interface header.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VECTOR_H
+#define _ZEBRA_VECTOR_H
+
+/* struct for vector */
+struct _vector {
+	unsigned int active;	/* number of active slots */
+	unsigned int alloced;	/* number of allocated slot */
+	void **index;		/* index to data */
+};
+typedef struct _vector *vector;
+
+#define VECTOR_MIN_SIZE 1
+
+/* (Sometimes) usefull macros.  This macro convert index expression to
+ array expression. */
+/* Reference slot at given index, caller must ensure slot is active */
+#define vector_slot(V,I)  ((V)->index[(I)])
+/* Number of active slots.
+ * Note that this differs from vector_count() as it the count returned
+ * will include any empty slots
+ */
+#define vector_active(V) ((V)->active)
+
+/* Prototypes. */
+vector vector_init(unsigned int size);
+void vector_ensure(vector v, unsigned int num);
+int vector_empty_slot(vector v);
+int vector_set(vector v, void *val);
+int vector_set_index(vector v, unsigned int i, void *val);
+void vector_unset(vector v, unsigned int i);
+unsigned int vector_count(vector v);
+void vector_only_wrapper_free(vector v);
+void vector_only_index_free(void *index);
+void vector_free(vector v);
+vector vector_copy(vector v);
+
+void *vector_lookup(vector, unsigned int);
+void *vector_lookup_ensure(vector, unsigned int);
+
+extern void *tall_vty_vec_ctx;
+
+#endif				/* _ZEBRA_VECTOR_H */
diff --git a/openbsc/include/vty/vty.h b/openbsc/include/vty/vty.h
new file mode 100644
index 0000000..0441fc5
--- /dev/null
+++ b/openbsc/include/vty/vty.h
@@ -0,0 +1,151 @@
+#ifndef _VTY_H
+#define _VTY_H
+
+#include <stdio.h>
+#include <stdarg.h>
+
+/* GCC have printf type attribute check.  */
+#ifdef __GNUC__
+#define VTY_PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b)))
+#else
+#define VTY_PRINTF_ATTRIBUTE(a,b)
+#endif				/* __GNUC__ */
+
+/* Does the I/O error indicate that the operation should be retried later? */
+#define ERRNO_IO_RETRY(EN) \
+	(((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+/* Vty read buffer size. */
+#define VTY_READ_BUFSIZ 512
+
+#define VTY_BUFSIZ 512
+#define VTY_MAXHIST 20
+
+/* Vty events */
+enum event {
+	VTY_SERV,
+	VTY_READ,
+	VTY_WRITE,
+	VTY_CLOSED,
+	VTY_TIMEOUT_RESET,
+#ifdef VTYSH
+	VTYSH_SERV,
+	VTYSH_READ,
+	VTYSH_WRITE
+#endif				/* VTYSH */
+};
+
+struct vty {
+	FILE *file;
+
+	/* private data, specified by creator */
+	void *priv;
+
+	/* File descripter of this vty. */
+	int fd;
+
+	/* Is this vty connect to file or not */
+	enum { VTY_TERM, VTY_FILE, VTY_SHELL, VTY_SHELL_SERV } type;
+
+	/* Node status of this vty */
+	int node;
+
+	/* Failure count */
+	int fail;
+
+	/* Output buffer. */
+	struct buffer *obuf;
+
+	/* Command input buffer */
+	char *buf;
+
+	/* Command cursor point */
+	int cp;
+
+	/* Command length */
+	int length;
+
+	/* Command max length. */
+	int max;
+
+	/* Histry of command */
+	char *hist[VTY_MAXHIST];
+
+	/* History lookup current point */
+	int hp;
+
+	/* History insert end point */
+	int hindex;
+
+	/* For current referencing point of interface, route-map,
+	   access-list etc... */
+	void *index;
+
+	/* For multiple level index treatment such as key chain and key. */
+	void *index_sub;
+
+	/* For escape character. */
+	unsigned char escape;
+
+	/* Current vty status. */
+	enum { VTY_NORMAL, VTY_CLOSE, VTY_MORE, VTY_MORELINE } status;
+
+	/* IAC handling: was the last character received the IAC
+	 * (interpret-as-command) escape character (and therefore the next
+	 * character will be the command code)?  Refer to Telnet RFC 854. */
+	unsigned char iac;
+
+	/* IAC SB (option subnegotiation) handling */
+	unsigned char iac_sb_in_progress;
+	/* At the moment, we care only about the NAWS (window size) negotiation,
+	 * and that requires just a 5-character buffer (RFC 1073):
+	 * <NAWS char> <16-bit width> <16-bit height> */
+#define TELNET_NAWS_SB_LEN 5
+	unsigned char sb_buf[TELNET_NAWS_SB_LEN];
+	/* How many subnegotiation characters have we received?  We just drop
+	 * those that do not fit in the buffer. */
+	size_t sb_len;
+
+	/* Window width/height. */
+	int width;
+	int height;
+
+	/* Configure lines. */
+	int lines;
+
+	int monitor;
+
+	/* In configure mode. */
+	int config;
+};
+
+/* Small macro to determine newline is newline only or linefeed needed. */
+#define VTY_NEWLINE  ((vty->type == VTY_TERM) ? "\r\n" : "\n")
+
+static inline char *vty_newline(struct vty *vty)
+{
+	return VTY_NEWLINE;
+}
+
+/* Prototypes. */
+void vty_init (void);
+int vty_read_config_file(const char *file_name);
+void vty_init_vtysh (void);
+void vty_reset (void);
+struct vty *vty_new (void);
+struct vty *vty_create (int vty_sock, void *priv);
+int vty_out (struct vty *, const char *, ...) VTY_PRINTF_ATTRIBUTE(2, 3);
+int vty_out_newline(struct vty *);
+int vty_read(struct vty *vty);
+//void vty_time_print (struct vty *, int);
+void vty_close (struct vty *);
+char *vty_get_cwd (void);
+void vty_log (const char *level, const char *proto, const char *fmt, va_list);
+int vty_config_lock (struct vty *);
+int vty_config_unlock (struct vty *);
+int vty_shell (struct vty *);
+int vty_shell_serv (struct vty *);
+void vty_hello (struct vty *);
+
+void *tall_vty_ctx;
+#endif
diff --git a/openbsc/libsccp.pc.in b/openbsc/libsccp.pc.in
new file mode 100644
index 0000000..eda8d49
--- /dev/null
+++ b/openbsc/libsccp.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: OpenBSC SCCP Lib
+Description: OpenBSC SCCP Lib
+Version: @VERSION@
+Libs: -L${libdir} -lsccp
+Cflags: -I${includedir}/
diff --git a/openbsc/openbsc.pc.in b/openbsc/openbsc.pc.in
new file mode 100644
index 0000000..aba07e2
--- /dev/null
+++ b/openbsc/openbsc.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/
+
+Name: OpenBSC
+Description: OpenBSC base station controller
+Requires:
+Version: @VERSION@
+Libs: -L${libdir} -lopenbsc
+Cflags: -I${includedir}
diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am
new file mode 100644
index 0000000..9dc0257
--- /dev/null
+++ b/openbsc/src/Makefile.am
@@ -0,0 +1,44 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS)
+
+# build current directory before building gprs
+SUBDIRS = . ipaccess gprs
+
+sbin_PROGRAMS = bsc_hack bs11_config isdnsync bsc_mgcp
+noinst_LIBRARIES = libbsc.a libmsc.a libvty.a libsccp.a
+noinst_HEADERS = vty/cardshell.h
+
+bscdir = $(libdir)
+bsc_LIBRARIES = libsccp.a
+
+libbsc_a_SOURCES = abis_rsl.c abis_nm.c gsm_data.c gsm_04_08_utils.c \
+		chan_alloc.c debug.c socket.c \
+		gsm_subscriber_base.c subchan_demux.c bsc_rll.c transaction.c \
+		trau_frame.c trau_mux.c paging.c e1_config.c e1_input.c \
+		input/misdn.c input/ipaccess.c \
+		talloc_ctx.c system_information.c rest_octets.c \
+		rtp_proxy.c bts_siemens_bs11.c bts_ipaccess_nanobts.c \
+		bts_unknown.c bsc_version.c bsc_api.c
+
+libmsc_a_SOURCES = gsm_subscriber.c db.c \
+		mncc.c gsm_04_08.c gsm_04_11.c transaction.c \
+		token_auth.c rrlp.c gsm_04_80.c ussd.c silent_call.c \
+		handover_logic.c handover_decision.c meas_rep.c
+
+libvty_a_SOURCES = vty/buffer.c vty/command.c vty/vector.c vty/vty.c \
+		telnet_interface.c vty_interface_cmds.c
+
+libsccp_a_SOURCES = sccp/sccp.c
+
+bsc_hack_SOURCES = bsc_hack.c bsc_init.c vty_interface.c vty_interface_layer3.c
+bsc_hack_LDADD = libmsc.a libbsc.a libmsc.a libvty.a -ldl -ldbi $(LIBCRYPT)
+
+bs11_config_SOURCES = bs11_config.c abis_nm.c gsm_data.c debug.c \
+		      rs232.c bts_siemens_bs11.c
+
+isdnsync_SOURCES = isdnsync.c
+
+bsc_mgcp_SOURCES = mgcp/mgcp_main.c mgcp/mgcp_protocol.c mgcp/mgcp_network.c mgcp/mgcp_vty.c \
+		   debug.c
+bsc_mgcp_LDADD = libvty.a
diff --git a/openbsc/src/abis_nm.c b/openbsc/src/abis_nm.c
new file mode 100644
index 0000000..c78ee56
--- /dev/null
+++ b/openbsc/src/abis_nm.c
@@ -0,0 +1,3006 @@
+/* GSM Network Management (OML) messages on the A-bis interface
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <time.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/misdn.h>
+#include <openbsc/signal.h>
+
+#define OM_ALLOC_SIZE		1024
+#define OM_HEADROOM_SIZE	128
+#define IPACC_SEGMENT_SIZE	245
+
+/* unidirectional messages from BTS to BSC */
+static const enum abis_nm_msgtype reports[] = {
+	NM_MT_SW_ACTIVATED_REP,
+	NM_MT_TEST_REP,
+	NM_MT_STATECHG_EVENT_REP,
+	NM_MT_FAILURE_EVENT_REP,
+};
+
+/* messages without ACK/NACK */
+static const enum abis_nm_msgtype no_ack_nack[] = {
+	NM_MT_MEAS_RES_REQ,
+	NM_MT_STOP_MEAS,
+	NM_MT_START_MEAS,
+};
+
+/* Messages related to software load */
+static const enum abis_nm_msgtype sw_load_msgs[] = {
+	NM_MT_LOAD_INIT_ACK,
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_SEG_ACK,
+	NM_MT_LOAD_ABORT,
+	NM_MT_LOAD_END_ACK,
+	NM_MT_LOAD_END_NACK,
+	//NM_MT_SW_ACT_REQ,
+	NM_MT_ACTIVATE_SW_ACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_SW_ACTIVATED_REP,
+};
+
+static const enum abis_nm_msgtype nacks[] = {
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_END_NACK,
+	NM_MT_SW_ACT_REQ_NACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_ESTABLISH_TEI_NACK,
+	NM_MT_CONN_TERR_SIGN_NACK,
+	NM_MT_DISC_TERR_SIGN_NACK,
+	NM_MT_CONN_TERR_TRAF_NACK,
+	NM_MT_DISC_TERR_TRAF_NACK,
+	NM_MT_CONN_MDROP_LINK_NACK,
+	NM_MT_DISC_MDROP_LINK_NACK,
+	NM_MT_SET_BTS_ATTR_NACK,
+	NM_MT_SET_RADIO_ATTR_NACK,
+	NM_MT_SET_CHAN_ATTR_NACK,
+	NM_MT_PERF_TEST_NACK,
+	NM_MT_SEND_TEST_REP_NACK,
+	NM_MT_STOP_TEST_NACK,
+	NM_MT_STOP_EVENT_REP_NACK,
+	NM_MT_REST_EVENT_REP_NACK,
+	NM_MT_CHG_ADM_STATE_NACK,
+	NM_MT_CHG_ADM_STATE_REQ_NACK,
+	NM_MT_REP_OUTST_ALARMS_NACK,
+	NM_MT_CHANGEOVER_NACK,
+	NM_MT_OPSTART_NACK,
+	NM_MT_REINIT_NACK,
+	NM_MT_SET_SITE_OUT_NACK,
+	NM_MT_CHG_HW_CONF_NACK,
+	NM_MT_GET_ATTR_NACK,
+	NM_MT_SET_ALARM_THRES_NACK,
+	NM_MT_BS11_BEGIN_DB_TX_NACK,
+	NM_MT_BS11_END_DB_TX_NACK,
+	NM_MT_BS11_CREATE_OBJ_NACK,
+	NM_MT_BS11_DELETE_OBJ_NACK,
+};
+
+static const struct value_string nack_names[] = {
+	{ NM_MT_LOAD_INIT_NACK,		"SOFTWARE LOAD INIT" },
+	{ NM_MT_LOAD_END_NACK,		"SOFTWARE LOAD END" },
+	{ NM_MT_SW_ACT_REQ_NACK,	"SOFTWARE ACTIVATE REQUEST" },
+	{ NM_MT_ACTIVATE_SW_NACK,	"ACTIVATE SOFTWARE" },
+	{ NM_MT_ESTABLISH_TEI_NACK,	"ESTABLISH TEI" },
+	{ NM_MT_CONN_TERR_SIGN_NACK,	"CONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_DISC_TERR_SIGN_NACK,	"DISCONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_CONN_TERR_TRAF_NACK,	"CONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_DISC_TERR_TRAF_NACK,	"DISCONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_CONN_MDROP_LINK_NACK,	"CONNECT MULTI-DROP LINK" },
+	{ NM_MT_DISC_MDROP_LINK_NACK,	"DISCONNECT MULTI-DROP LINK" },
+	{ NM_MT_SET_BTS_ATTR_NACK,	"SET BTS ATTRIBUTE" },
+	{ NM_MT_SET_RADIO_ATTR_NACK,	"SET RADIO ATTRIBUTE" },
+	{ NM_MT_SET_CHAN_ATTR_NACK,	"SET CHANNEL ATTRIBUTE" },
+	{ NM_MT_PERF_TEST_NACK,		"PERFORM TEST" },
+	{ NM_MT_SEND_TEST_REP_NACK,	"SEND TEST REPORT" },
+	{ NM_MT_STOP_TEST_NACK,		"STOP TEST" },
+	{ NM_MT_STOP_EVENT_REP_NACK,	"STOP EVENT REPORT" },
+	{ NM_MT_REST_EVENT_REP_NACK,	"RESET EVENT REPORT" },
+	{ NM_MT_CHG_ADM_STATE_NACK,	"CHANGE ADMINISTRATIVE STATE" },
+	{ NM_MT_CHG_ADM_STATE_REQ_NACK,
+				"CHANGE ADMINISTRATIVE STATE REQUEST" },
+	{ NM_MT_REP_OUTST_ALARMS_NACK,	"REPORT OUTSTANDING ALARMS" },
+	{ NM_MT_CHANGEOVER_NACK,	"CHANGEOVER" },
+	{ NM_MT_OPSTART_NACK,		"OPSTART" },
+	{ NM_MT_REINIT_NACK,		"REINIT" },
+	{ NM_MT_SET_SITE_OUT_NACK,	"SET SITE OUTPUT" },
+	{ NM_MT_CHG_HW_CONF_NACK,	"CHANGE HARDWARE CONFIGURATION" },
+	{ NM_MT_GET_ATTR_NACK,		"GET ATTRIBUTE" },
+	{ NM_MT_SET_ALARM_THRES_NACK,	"SET ALARM THRESHOLD" },
+	{ NM_MT_BS11_BEGIN_DB_TX_NACK,	"BS11 BEGIN DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_END_DB_TX_NACK,	"BS11 END DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_CREATE_OBJ_NACK,	"BS11 CREATE OBJECT" },
+	{ NM_MT_BS11_DELETE_OBJ_NACK,	"BS11 DELETE OBJECT" },
+	{ 0,				NULL }
+};
+
+/* Chapter 9.4.36 */
+static const struct value_string nack_cause_names[] = {
+	/* General Nack Causes */
+	{ NM_NACK_INCORR_STRUCT,	"Incorrect message structure" },
+	{ NM_NACK_MSGTYPE_INVAL,	"Invalid message type value" },
+	{ NM_NACK_OBJCLASS_INVAL,	"Invalid Object class value" },
+	{ NM_NACK_OBJCLASS_NOTSUPP,	"Object class not supported" },
+	{ NM_NACK_BTSNR_UNKN,		"BTS no. unknown" },
+	{ NM_NACK_TRXNR_UNKN,		"Baseband Transceiver no. unknown" },
+	{ NM_NACK_OBJINST_UNKN,		"Object Instance unknown" },
+	{ NM_NACK_ATTRID_INVAL,		"Invalid attribute identifier value" },
+	{ NM_NACK_ATTRID_NOTSUPP,	"Attribute identifier not supported" },
+	{ NM_NACK_PARAM_RANGE,		"Parameter value outside permitted range" },
+	{ NM_NACK_ATTRLIST_INCONSISTENT,"Inconsistency in attribute list" },
+	{ NM_NACK_SPEC_IMPL_NOTSUPP,	"Specified implementation not supported" },
+	{ NM_NACK_CANT_PERFORM,		"Message cannot be performed" },
+	/* Specific Nack Causes */
+	{ NM_NACK_RES_NOTIMPL,		"Resource not implemented" },
+	{ NM_NACK_RES_NOTAVAIL,		"Resource not available" },
+	{ NM_NACK_FREQ_NOTAVAIL,	"Frequency not available" },
+	{ NM_NACK_TEST_NOTSUPP,		"Test not supported" },
+	{ NM_NACK_CAPACITY_RESTR,	"Capacity restrictions" },
+	{ NM_NACK_PHYSCFG_NOTPERFORM,	"Physical configuration cannot be performed" },
+	{ NM_NACK_TEST_NOTINIT,		"Test not initiated" },
+	{ NM_NACK_PHYSCFG_NOTRESTORE,	"Physical configuration cannot be restored" },
+	{ NM_NACK_TEST_NOSUCH,		"No such test" },
+	{ NM_NACK_TEST_NOSTOP,		"Test cannot be stopped" },
+	{ NM_NACK_MSGINCONSIST_PHYSCFG,	"Message inconsistent with physical configuration" },
+	{ NM_NACK_FILE_INCOMPLETE,	"Complete file notreceived" },
+	{ NM_NACK_FILE_NOTAVAIL,	"File not available at destination" },
+	{ NM_NACK_FILE_NOTACTIVATE,	"File cannot be activate" },
+	{ NM_NACK_REQ_NOT_GRANT,	"Request not granted" },
+	{ NM_NACK_WAIT,			"Wait" },
+	{ NM_NACK_NOTH_REPORT_EXIST,	"Nothing reportable existing" },
+	{ NM_NACK_MEAS_NOTSUPP,		"Measurement not supported" },
+	{ NM_NACK_MEAS_NOTSTART,	"Measurement not started" },
+	{ 0,				NULL }
+};
+
+static const char *nack_cause_name(u_int8_t cause)
+{
+	return get_value_string(nack_cause_names, cause);
+}
+
+/* Chapter 9.4.16: Event Type */
+static const struct value_string event_type_names[] = {
+	{ NM_EVT_COMM_FAIL,		"communication failure" },
+	{ NM_EVT_QOS_FAIL,		"quality of service failure" },
+	{ NM_EVT_PROC_FAIL,		"processing failure" },
+	{ NM_EVT_EQUIP_FAIL,		"equipment failure" },
+	{ NM_EVT_ENV_FAIL,		"environment failure" },
+	{ 0,				NULL }
+};
+
+static const char *event_type_name(u_int8_t cause)
+{
+	return get_value_string(event_type_names, cause);
+}
+
+/* Chapter 9.4.63: Perceived Severity */
+static const struct value_string severity_names[] = {
+	{ NM_SEVER_CEASED,		"failure ceased" },
+	{ NM_SEVER_CRITICAL,		"critical failure" },
+	{ NM_SEVER_MAJOR,		"major failure" },
+	{ NM_SEVER_MINOR,		"minor failure" },
+	{ NM_SEVER_WARNING,		"warning level failure" },
+	{ NM_SEVER_INDETERMINATE,	"indeterminate failure" },
+	{ 0,				NULL }
+};
+
+static const char *severity_name(u_int8_t cause)
+{
+	return get_value_string(severity_names, cause);
+}
+
+/* Attributes that the BSC can set, not only get, according to Section 9.4 */
+static const enum abis_nm_attr nm_att_settable[] = {
+	NM_ATT_ADD_INFO,
+	NM_ATT_ADD_TEXT,
+	NM_ATT_DEST,
+	NM_ATT_EVENT_TYPE,
+	NM_ATT_FILE_DATA,
+	NM_ATT_GET_ARI,
+	NM_ATT_HW_CONF_CHG,
+	NM_ATT_LIST_REQ_ATTR,
+	NM_ATT_MDROP_LINK,
+	NM_ATT_MDROP_NEXT,
+	NM_ATT_NACK_CAUSES,
+	NM_ATT_OUTST_ALARM,
+	NM_ATT_PHYS_CONF,
+	NM_ATT_PROB_CAUSE,
+	NM_ATT_RAD_SUBC,
+	NM_ATT_SOURCE,
+	NM_ATT_SPEC_PROB,
+	NM_ATT_START_TIME,
+	NM_ATT_TEST_DUR,
+	NM_ATT_TEST_NO,
+	NM_ATT_TEST_REPORT,
+	NM_ATT_WINDOW_SIZE,
+	NM_ATT_SEVERITY,
+	NM_ATT_MEAS_RES,
+	NM_ATT_MEAS_TYPE,
+};
+
+const struct tlv_definition nm_att_tlvdef = {
+	.def = {
+		[NM_ATT_ABIS_CHANNEL] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_ADD_INFO] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADD_TEXT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADM_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_ARFCN_LIST]=		{ TLV_TYPE_TL16V },
+		[NM_ATT_AUTON_REPORT] =		{ TLV_TYPE_TV },
+		[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_BCCH_ARFCN] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_BSIC] =			{ TLV_TYPE_TV },
+		[NM_ATT_BTS_AIR_TIMER] =	{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_I_P] =		{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_T] =		{ TLV_TYPE_TV },
+		[NM_ATT_CHAN_COMB] =		{ TLV_TYPE_TV },
+		[NM_ATT_CONN_FAIL_CRIT] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_DEST] =			{ TLV_TYPE_TL16V },
+		[NM_ATT_EVENT_TYPE] =		{ TLV_TYPE_TV },
+		[NM_ATT_FILE_DATA] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_GSM_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_HSN] =			{ TLV_TYPE_TV },
+		[NM_ATT_HW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_DESC] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_INTAVE_PARAM] =		{ TLV_TYPE_TV },
+		[NM_ATT_INTERF_BOUND] =		{ TLV_TYPE_FIXED, 6 },
+		[NM_ATT_LIST_REQ_ATTR] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_MAIO] =			{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_THRESH] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MANUF_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MAX_TA] =		{ TLV_TYPE_TV },
+		[NM_ATT_MDROP_LINK] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_MDROP_NEXT] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_NACK_CAUSES] =		{ TLV_TYPE_TV },
+		[NM_ATT_NY1] =			{ TLV_TYPE_TV },
+		[NM_ATT_OPER_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_OVERL_PERIOD] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_PHYS_CONF] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_POWER_CLASS] =		{ TLV_TYPE_TV },
+		[NM_ATT_POWER_THRESH] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_PROB_CAUSE] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_RACH_B_THRESH] =	{ TLV_TYPE_TV },
+		[NM_ATT_LDAVG_SLOTS] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_RAD_SUBC] =		{ TLV_TYPE_TV },
+		[NM_ATT_RF_MAXPOWR_R] =		{ TLV_TYPE_TV },
+		[NM_ATT_SITE_INPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SITE_OUTPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SOURCE] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SPEC_PROB] =		{ TLV_TYPE_TV },
+		[NM_ATT_START_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_T200] =			{ TLV_TYPE_FIXED, 7 },
+		[NM_ATT_TEI] =			{ TLV_TYPE_TV },
+		[NM_ATT_TEST_DUR] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_TEST_NO] =		{ TLV_TYPE_TV },
+		[NM_ATT_TEST_REPORT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_VSWR_THRESH] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_WINDOW_SIZE] = 		{ TLV_TYPE_TV },
+		[NM_ATT_TSC] =			{ TLV_TYPE_TV },
+		[NM_ATT_SW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SEVERITY] = 		{ TLV_TYPE_TV },
+		[NM_ATT_GET_ARI] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_CONF_CHG] = 		{ TLV_TYPE_TL16V },
+		[NM_ATT_OUTST_ALARM] =		{ TLV_TYPE_TV },
+		[NM_ATT_MEAS_RES] =		{ TLV_TYPE_TL16V },
+	},
+};
+
+static const enum abis_nm_chan_comb chcomb4pchan[] = {
+	[GSM_PCHAN_CCCH]	= NM_CHANC_mainBCCH,
+	[GSM_PCHAN_CCCH_SDCCH4]	= NM_CHANC_BCCHComb,
+	[GSM_PCHAN_TCH_F]	= NM_CHANC_TCHFull,
+	[GSM_PCHAN_TCH_H]	= NM_CHANC_TCHHalf,
+	[GSM_PCHAN_SDCCH8_SACCH8C] = NM_CHANC_SDCCH,
+	[GSM_PCHAN_PDCH]	= NM_CHANC_IPAC_PDCH,
+	[GSM_PCHAN_TCH_F_PDCH]	= NM_CHANC_IPAC_TCHFull_PDCH,
+	/* FIXME: bounds check */
+};
+
+int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan)
+{
+	if (pchan < ARRAY_SIZE(chcomb4pchan))
+		return chcomb4pchan[pchan];
+
+	return -EINVAL;
+}
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const u_int8_t *buf, int len)
+{
+	if (!bts->model)
+		return -EIO;
+	return tlv_parse(tp, &bts->model->nm_att_tlvdef, buf, len, 0, 0);
+}
+
+static int is_in_arr(enum abis_nm_msgtype mt, const enum abis_nm_msgtype *arr, int size)
+{
+	int i;
+
+	for (i = 0; i < size; i++) {
+		if (arr[i] == mt)
+			return 1;
+	}
+
+	return 0;
+}
+
+#if 0
+/* is this msgtype the usual ACK/NACK type ? */
+static int is_ack_nack(enum abis_nm_msgtype mt)
+{
+	return !is_in_arr(mt, no_ack_nack, ARRAY_SIZE(no_ack_nack));
+}
+#endif
+
+/* is this msgtype a report ? */
+static int is_report(enum abis_nm_msgtype mt)
+{
+	return is_in_arr(mt, reports, ARRAY_SIZE(reports));
+}
+
+#define MT_ACK(x)	(x+1)
+#define MT_NACK(x)	(x+2)
+
+static void fill_om_hdr(struct abis_om_hdr *oh, u_int8_t len)
+{
+	oh->mdisc = ABIS_OM_MDISC_FOM;
+	oh->placement = ABIS_OM_PLACEMENT_ONLY;
+	oh->sequence = 0;
+	oh->length = len;
+}
+
+static void fill_om_fom_hdr(struct abis_om_hdr *oh, u_int8_t len,
+			    u_int8_t msg_type, u_int8_t obj_class,
+			    u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr)
+{
+	struct abis_om_fom_hdr *foh =
+			(struct abis_om_fom_hdr *) oh->data;
+
+	fill_om_hdr(oh, len+sizeof(*foh));
+	foh->msg_type = msg_type;
+	foh->obj_class = obj_class;
+	foh->obj_inst.bts_nr = bts_nr;
+	foh->obj_inst.trx_nr = trx_nr;
+	foh->obj_inst.ts_nr = ts_nr;
+}
+
+static struct msgb *nm_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+				   "OML");
+}
+
+/* Send a OML NM Message from BSC to BTS */
+int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+	msg->trx = bts->c0;
+
+	return _abis_nm_sendmsg(msg);
+}
+
+static int abis_nm_rcvmsg_sw(struct msgb *mb);
+
+static struct value_string obj_class_names[] = {
+	{ NM_OC_SITE_MANAGER,	"SITE MANAGER" },
+	{ NM_OC_BTS,		"BTS" },
+	{ NM_OC_RADIO_CARRIER,	"RADIO CARRIER" },
+	{ NM_OC_BASEB_TRANSC,	"BASEBAND TRANSCEIVER" },
+	{ NM_OC_CHANNEL,	"CHANNEL" },
+	{ NM_OC_BS11_ADJC,	"ADJC" },
+	{ NM_OC_BS11_HANDOVER,	"HANDOVER" },
+	{ NM_OC_BS11_PWR_CTRL,	"POWER CONTROL" },
+	{ NM_OC_BS11_BTSE,	"BTSE" },
+	{ NM_OC_BS11_RACK,	"RACK" },
+	{ NM_OC_BS11_TEST,	"TEST" },
+	{ NM_OC_BS11_ENVABTSE,	"ENVABTSE" },
+	{ NM_OC_BS11_BPORT,	"BPORT" },
+	{ NM_OC_GPRS_NSE,	"GPRS NSE" },
+	{ NM_OC_GPRS_CELL,	"GPRS CELL" },
+	{ NM_OC_GPRS_NSVC,	"GPRS NSVC" },
+	{ NM_OC_BS11,		"SIEMENSHW" },
+	{ 0,			NULL }
+};
+
+static const char *obj_class_name(u_int8_t oc)
+{
+	return get_value_string(obj_class_names, oc);
+}
+
+const char *nm_opstate_name(u_int8_t os)
+{
+	switch (os) {
+	case NM_OPSTATE_DISABLED:
+		return "Disabled";
+	case NM_OPSTATE_ENABLED:
+		return "Enabled";
+	case NM_OPSTATE_NULL:
+		return "NULL";
+	default:
+		return "RFU";
+	}
+}
+
+/* Chapter 9.4.7 */
+static const struct value_string avail_names[] = {
+	{ 0, 	"In test" },
+	{ 1,	"Failed" },
+	{ 2,	"Power off" },
+	{ 3,	"Off line" },
+	/* Not used */
+	{ 5,	"Dependency" },
+	{ 6,	"Degraded" },
+	{ 7,	"Not installed" },
+	{ 0xff, "OK" },
+	{ 0,	NULL }
+};
+
+const char *nm_avail_name(u_int8_t avail)
+{
+	return get_value_string(avail_names, avail);
+}
+
+static struct value_string test_names[] = {
+	/* FIXME: standard test names */
+	{ NM_IPACC_TESTNO_CHAN_USAGE, "Channel Usage" },
+	{ NM_IPACC_TESTNO_BCCH_CHAN_USAGE, "BCCH Channel Usage" },
+	{ NM_IPACC_TESTNO_FREQ_SYNC, "Frequency Synchronization" },
+	{ NM_IPACC_TESTNO_BCCH_INFO, "BCCH Info" },
+	{ NM_IPACC_TESTNO_TX_BEACON, "Transmit Beacon" },
+	{ NM_IPACC_TESTNO_SYSINFO_MONITOR, "System Info Monitor" },
+	{ NM_IPACC_TESTNO_BCCCH_MONITOR, "BCCH Monitor" },
+	{ 0, NULL }
+};
+
+const char *nm_adm_name(u_int8_t adm)
+{
+	switch (adm) {
+	case 1:
+		return "Locked";
+	case 2:
+		return "Unlocked";
+	case 3:
+		return "Shutdown";
+	default:
+		return "<not used>";
+	}
+}
+
+int nm_is_running(struct gsm_nm_state *s) {
+	return (s->operational == NM_OPSTATE_ENABLED) && (
+		(s->availability == NM_AVSTATE_OK) ||
+		(s->availability == 0xff)
+	);
+}
+
+static void debugp_foh(struct abis_om_fom_hdr *foh)
+{
+	DEBUGP(DNM, "OC=%s(%02x) INST=(%02x,%02x,%02x) ",
+		obj_class_name(foh->obj_class), foh->obj_class,
+		foh->obj_inst.bts_nr, foh->obj_inst.trx_nr,
+		foh->obj_inst.ts_nr);
+}
+
+/* obtain the gsm_nm_state data structure for a given object instance */
+static struct gsm_nm_state *
+objclass2nmstate(struct gsm_bts *bts, u_int8_t obj_class,
+		 struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_nm_state *nm_state = NULL;
+
+	switch (obj_class) {
+	case NM_OC_BTS:
+		nm_state = &bts->nm_state;
+		break;
+	case NM_OC_RADIO_CARRIER:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		nm_state = &trx->nm_state;
+		break;
+	case NM_OC_BASEB_TRANSC:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		nm_state = &trx->bb_transc.nm_state;
+		break;
+	case NM_OC_CHANNEL:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		if (obj_inst->ts_nr >= TRX_NR_TS)
+			return NULL;
+		nm_state = &trx->ts[obj_inst->ts_nr].nm_state;
+		break;
+	case NM_OC_SITE_MANAGER:
+		nm_state = &bts->site_mgr.nm_state;
+		break;
+	case NM_OC_BS11:
+		switch (obj_inst->bts_nr) {
+		case BS11_OBJ_CCLK:
+			nm_state = &bts->bs11.cclk.nm_state;
+			break;
+		case BS11_OBJ_BBSIG:
+			if (obj_inst->ts_nr > bts->num_trx)
+				return NULL;
+			trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+			nm_state = &trx->bs11.bbsig.nm_state;
+			break;
+		case BS11_OBJ_PA:
+			if (obj_inst->ts_nr > bts->num_trx)
+				return NULL;
+			trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+			nm_state = &trx->bs11.pa.nm_state;
+			break;
+		default:
+			return NULL;
+		}
+	case NM_OC_BS11_RACK:
+		nm_state = &bts->bs11.rack.nm_state;
+		break;
+	case NM_OC_BS11_ENVABTSE:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse))
+			return NULL;
+		nm_state = &bts->bs11.envabtse[obj_inst->trx_nr].nm_state;
+		break;
+	case NM_OC_GPRS_NSE:
+		nm_state = &bts->gprs.nse.nm_state;
+		break;
+	case NM_OC_GPRS_CELL:
+		nm_state = &bts->gprs.cell.nm_state;
+		break;
+	case NM_OC_GPRS_NSVC:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+			return NULL;
+		nm_state = &bts->gprs.nsvc[obj_inst->trx_nr].nm_state;
+		break;
+	}
+	return nm_state;
+}
+
+/* obtain the in-memory data structure of a given object instance */
+static void *
+objclass2obj(struct gsm_bts *bts, u_int8_t obj_class,
+	     struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_bts_trx *trx;
+	void *obj = NULL;
+
+	switch (obj_class) {
+	case NM_OC_BTS:
+		obj = bts;
+		break;
+	case NM_OC_RADIO_CARRIER:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		obj = trx;
+		break;
+	case NM_OC_BASEB_TRANSC:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		obj = &trx->bb_transc;
+		break;
+	case NM_OC_CHANNEL:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		if (obj_inst->ts_nr >= TRX_NR_TS)
+			return NULL;
+		obj = &trx->ts[obj_inst->ts_nr];
+		break;
+	case NM_OC_SITE_MANAGER:
+		obj = &bts->site_mgr;
+		break;
+	case NM_OC_GPRS_NSE:
+		obj = &bts->gprs.nse;
+		break;
+	case NM_OC_GPRS_CELL:
+		obj = &bts->gprs.cell;
+		break;
+	case NM_OC_GPRS_NSVC:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+			return NULL;
+		obj = &bts->gprs.nsvc[obj_inst->trx_nr];
+		break;
+	}
+	return obj;
+}
+
+/* Update the administrative state of a given object in our in-memory data
+ * structures and send an event to the higher layer */
+static int update_admstate(struct gsm_bts *bts, u_int8_t obj_class,
+			   struct abis_om_obj_inst *obj_inst, u_int8_t adm_state)
+{
+	struct gsm_nm_state *nm_state, new_state;
+	void *obj;
+	int rc;
+
+	obj = objclass2obj(bts, obj_class, obj_inst);
+	if (!obj)
+		return -EINVAL;
+	nm_state = objclass2nmstate(bts, obj_class, obj_inst);
+	if (!nm_state)
+		return -1;
+
+	new_state = *nm_state;
+	new_state.administrative = adm_state;
+
+	rc = nm_state_event(EVT_STATECHG_ADM, obj_class, obj, nm_state, &new_state);
+
+	nm_state->administrative = adm_state;
+
+	return rc;
+}
+
+static int abis_nm_rx_statechg_rep(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct gsm_bts *bts = mb->trx->bts;
+	struct tlv_parsed tp;
+	struct gsm_nm_state *nm_state, new_state;
+	int rc;
+
+	DEBUGPC(DNM, "STATE CHG: ");
+
+	memset(&new_state, 0, sizeof(new_state));
+
+	nm_state = objclass2nmstate(bts, foh->obj_class, &foh->obj_inst);
+	if (!nm_state) {
+		DEBUGPC(DNM, "unknown object class\n");
+		return -EINVAL;
+	}
+
+	new_state = *nm_state;
+	
+	abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+	if (TLVP_PRESENT(&tp, NM_ATT_OPER_STATE)) {
+		new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE);
+		DEBUGPC(DNM, "OP_STATE=%s ", nm_opstate_name(new_state.operational));
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) {
+		if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0)
+			new_state.availability = 0xff;
+		else
+			new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS);
+		DEBUGPC(DNM, "AVAIL=%s(%02x) ", nm_avail_name(new_state.availability),
+			new_state.availability);
+	} else
+		new_state.availability = 0xff;
+	if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) {
+		new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+		DEBUGPC(DNM, "ADM=%2s ", nm_adm_name(new_state.administrative));
+	}
+	DEBUGPC(DNM, "\n");
+
+	if ((new_state.administrative != 0 && nm_state->administrative == 0) ||
+	    new_state.operational != nm_state->operational ||
+	    new_state.availability != nm_state->availability) {
+		/* Update the operational state of a given object in our in-memory data
+ 		* structures and send an event to the higher layer */
+		void *obj = objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+		rc = nm_state_event(EVT_STATECHG_OPER, foh->obj_class, obj, nm_state, &new_state);
+		nm_state->operational = new_state.operational;
+		nm_state->availability = new_state.availability;
+		if (nm_state->administrative == 0)
+			nm_state->administrative = new_state.administrative;
+	}
+#if 0
+	if (op_state == 1) {
+		/* try to enable objects that are disabled */
+		abis_nm_opstart(bts, foh->obj_class,
+				foh->obj_inst.bts_nr,
+				foh->obj_inst.trx_nr,
+				foh->obj_inst.ts_nr);
+	}
+#endif
+	return 0;
+}
+
+static int rx_fail_evt_rep(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+
+	DEBUGPC(DNM, "Failure Event Report ");
+	
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+	if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE))
+		DEBUGPC(DNM, "Type=%s ", event_type_name(*TLVP_VAL(&tp, NM_ATT_EVENT_TYPE)));
+	if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY))
+		DEBUGPC(DNM, "Severity=%s ", severity_name(*TLVP_VAL(&tp, NM_ATT_SEVERITY)));
+
+	DEBUGPC(DNM, "\n");
+
+	return 0;
+}
+
+static int abis_nm_rcvmsg_report(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	u_int8_t mt = foh->msg_type;
+
+	debugp_foh(foh);
+
+	//nmh->cfg->report_cb(mb, foh);
+
+	switch (mt) {
+	case NM_MT_STATECHG_EVENT_REP:
+		return abis_nm_rx_statechg_rep(mb);
+		break;
+	case NM_MT_SW_ACTIVATED_REP:
+		DEBUGPC(DNM, "Software Activated Report\n");
+		dispatch_signal(SS_NM, S_NM_SW_ACTIV_REP, mb);
+		break;
+	case NM_MT_FAILURE_EVENT_REP:
+		rx_fail_evt_rep(mb);
+		dispatch_signal(SS_NM, S_NM_FAIL_REP, mb);
+		break;
+	case NM_MT_TEST_REP:
+		DEBUGPC(DNM, "Test Report\n");
+		dispatch_signal(SS_NM, S_NM_TEST_REP, mb);
+		break;
+	default:
+		DEBUGPC(DNM, "reporting NM MT 0x%02x\n", mt);
+		break;
+		
+	};
+
+	return 0;
+}
+
+/* Activate the specified software into the BTS */
+static int ipacc_sw_activate(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1,
+			     u_int8_t i2, const u_int8_t *sw_desc, u_int8_t swdesc_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = swdesc_len;
+	u_int8_t *trailer;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, obj_class, i0, i1, i2);
+
+	trailer = msgb_put(msg, swdesc_len);
+	memcpy(trailer, sw_desc, swdesc_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+static int abis_nm_parse_sw_descr(const u_int8_t *sw_descr, int sw_descr_len)
+{
+	static const struct tlv_definition sw_descr_def = {
+		.def = {
+			[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V, },
+			[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V, },
+		},
+	};
+
+	u_int8_t tag;
+	u_int16_t tag_len;
+	const u_int8_t *val;
+	int ofs = 0, len;
+
+	/* Classic TLV parsing doesn't work well with SW_DESCR because of it's
+	 * nested nature and the fact you have to assume it contains only two sub
+	 * tags NM_ATT_FILE_VERSION & NM_ATT_FILE_ID to parse it */
+
+	if (sw_descr[0] != NM_ATT_SW_DESCR) {
+		DEBUGP(DNM, "SW_DESCR attribute identifier not found!\n");
+		return -1;
+	}
+	ofs += 1;
+
+	len = tlv_parse_one(&tag, &tag_len, &val,
+		&sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs);
+	if (len < 0 || (tag != NM_ATT_FILE_ID)) {
+		DEBUGP(DNM, "FILE_ID attribute identifier not found!\n");
+		return -2;
+	}
+	ofs += len;
+
+	len = tlv_parse_one(&tag, &tag_len, &val,
+		&sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs);
+	if (len < 0 || (tag != NM_ATT_FILE_VERSION)) {
+		DEBUGP(DNM, "FILE_VERSION attribute identifier not found!\n");
+		return -3;
+	}
+	ofs += len;
+
+	return ofs;
+}
+
+static int abis_nm_rx_sw_act_req(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+	const u_int8_t *sw_config;
+	int ret, sw_config_len, sw_descr_len;
+
+	debugp_foh(foh);
+
+	DEBUGPC(DNM, "SW Activate Request: ");
+
+	DEBUGP(DNM, "Software Activate Request, ACKing and Activating\n");
+
+	ret = abis_nm_sw_act_req_ack(mb->trx->bts, foh->obj_class,
+				      foh->obj_inst.bts_nr,
+				      foh->obj_inst.trx_nr,
+				      foh->obj_inst.ts_nr, 0,
+				      foh->data, oh->length-sizeof(*foh));
+
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+	sw_config = TLVP_VAL(&tp, NM_ATT_SW_CONFIG);
+	sw_config_len = TLVP_LEN(&tp, NM_ATT_SW_CONFIG);
+	if (!TLVP_PRESENT(&tp, NM_ATT_SW_CONFIG)) {
+		DEBUGP(DNM, "SW config not found! Can't continue.\n");
+		return -EINVAL;
+	} else {
+		DEBUGP(DNM, "Found SW config: %s\n", hexdump(sw_config, sw_config_len));
+	}
+
+		/* Use the first SW_DESCR present in SW config */
+	sw_descr_len = abis_nm_parse_sw_descr(sw_config, sw_config_len);
+	if (sw_descr_len < 0)
+		return -EINVAL;
+
+	return ipacc_sw_activate(mb->trx->bts, foh->obj_class,
+				 foh->obj_inst.bts_nr,
+				 foh->obj_inst.trx_nr,
+				 foh->obj_inst.ts_nr,
+				 sw_config, sw_descr_len);
+}
+
+/* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */
+static int abis_nm_rx_chg_adm_state_ack(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+	u_int8_t adm_state;
+
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+	if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE))
+		return -EINVAL;
+
+	adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+
+	return update_admstate(mb->trx->bts, foh->obj_class, &foh->obj_inst, adm_state);
+}
+
+static int abis_nm_rx_lmt_event(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+
+	DEBUGP(DNM, "LMT Event ");
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) >= 1) {
+		u_int8_t onoff = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_LOGON_SESSION);
+		DEBUGPC(DNM, "LOG%s ", onoff ? "ON" : "OFF");
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) >= 1) {
+		u_int8_t level = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV);
+		DEBUGPC(DNM, "Level=%u ", level);
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_NAME) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_NAME) >= 1) {
+		char *name = (char *) TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_NAME);
+		DEBUGPC(DNM, "Username=%s ", name);
+	}
+	DEBUGPC(DNM, "\n");
+	/* FIXME: parse LMT LOGON TIME */
+	return 0;
+}
+
+/* Receive a OML NM Message from BTS */
+static int abis_nm_rcvmsg_fom(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	u_int8_t mt = foh->msg_type;
+
+	/* check for unsolicited message */
+	if (is_report(mt))
+		return abis_nm_rcvmsg_report(mb);
+
+	if (is_in_arr(mt, sw_load_msgs, ARRAY_SIZE(sw_load_msgs)))
+		return abis_nm_rcvmsg_sw(mb);
+
+	if (is_in_arr(mt, nacks, ARRAY_SIZE(nacks))) {
+		struct tlv_parsed tp;
+
+		debugp_foh(foh);
+
+		DEBUGPC(DNM, "%s NACK ", get_value_string(nack_names, mt));
+
+		abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			DEBUGPC(DNM, "CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			DEBUGPC(DNM, "\n");
+
+		dispatch_signal(SS_NM, S_NM_NACK, (void*) &mt);
+		return 0;
+	}
+#if 0
+	/* check if last message is to be acked */
+	if (is_ack_nack(nmh->last_msgtype)) {
+		if (mt == MT_ACK(nmh->last_msgtype)) {
+			DEBUGP(DNM, "received ACK (0x%x)\n", foh->msg_type);
+			/* we got our ACK, continue sending the next msg */
+		} else if (mt == MT_NACK(nmh->last_msgtype)) {
+			/* we got a NACK, signal this to the caller */
+			DEBUGP(DNM, "received NACK (0x%x)\n", foh->msg_type);
+			/* FIXME: somehow signal this to the caller */
+		} else {
+			/* really strange things happen */
+			return -EINVAL;
+		}
+	}
+#endif
+
+	switch (mt) {
+	case NM_MT_CHG_ADM_STATE_ACK:
+		return abis_nm_rx_chg_adm_state_ack(mb);
+		break;
+	case NM_MT_SW_ACT_REQ:
+		return abis_nm_rx_sw_act_req(mb);
+		break;
+	case NM_MT_BS11_LMT_SESSION:
+		return abis_nm_rx_lmt_event(mb);
+		break;
+	case NM_MT_CONN_MDROP_LINK_ACK:
+		DEBUGP(DNM, "CONN MDROP LINK ACK\n");
+		break;
+	case NM_MT_IPACC_RESTART_ACK:
+		dispatch_signal(SS_NM, S_NM_IPACC_RESTART_ACK, NULL);
+		break;
+	case NM_MT_IPACC_RESTART_NACK:
+		dispatch_signal(SS_NM, S_NM_IPACC_RESTART_NACK, NULL);
+		break;
+	}
+
+	return 0;
+}
+
+static int abis_nm_rx_ipacc(struct msgb *mb);
+
+static int abis_nm_rcvmsg_manuf(struct msgb *mb)
+{
+	int rc;
+	int bts_type = mb->trx->bts->type;
+
+	switch (bts_type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		rc = abis_nm_rx_ipacc(mb);
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this "
+		     "BTS type (%u)\n", bts_type);
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+/* High-Level API */
+/* Entry-point where L2 OML from BTS enters the NM code */
+int abis_nm_rcvmsg(struct msgb *msg)
+{
+	struct abis_om_hdr *oh = msgb_l2(msg);
+	int rc = 0;
+
+	/* Various consistency checks */
+	if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+			oh->placement);
+		return -EINVAL;
+	}
+	if (oh->sequence != 0) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+			oh->sequence);
+		return -EINVAL;
+	}
+#if 0
+	unsigned int l2_len = msg->tail - (u_int8_t *)msgb_l2(msg);
+	unsigned int hlen = sizeof(*oh) + sizeof(struct abis_om_fom_hdr);
+	if (oh->length + hlen > l2_len) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML truncated message (%u > %u)\n",
+			oh->length + sizeof(*oh), l2_len);
+		return -EINVAL;
+	}
+	if (oh->length + hlen < l2_len)
+		LOGP(DNM, LOGL_ERROR, "ABIS OML message with extra trailer?!? (oh->len=%d, sizeof_oh=%d l2_len=%d\n", oh->length, sizeof(*oh), l2_len);
+#endif
+	msg->l3h = (unsigned char *)oh + sizeof(*oh);
+
+	switch (oh->mdisc) {
+	case ABIS_OM_MDISC_FOM:
+		rc = abis_nm_rcvmsg_fom(msg);
+		break;
+	case ABIS_OM_MDISC_MANUF:
+		rc = abis_nm_rcvmsg_manuf(msg);
+		break;
+	case ABIS_OM_MDISC_MMI:
+	case ABIS_OM_MDISC_TRAU:
+		LOGP(DNM, LOGL_ERROR, "unimplemented ABIS OML message discriminator 0x%x\n",
+			oh->mdisc);
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "unknown ABIS OML message discriminator 0x%x\n",
+			oh->mdisc);
+		return -EINVAL;
+	}
+
+	msgb_free(msg);
+	return rc;
+}
+
+#if 0
+/* initialized all resources */
+struct abis_nm_h *abis_nm_init(struct abis_nm_cfg *cfg)
+{
+	struct abis_nm_h *nmh;
+
+	nmh = malloc(sizeof(*nmh));
+	if (!nmh)
+		return NULL;
+
+	nmh->cfg = cfg;
+
+	return nmh;
+}
+
+/* free all resources */
+void abis_nm_fini(struct abis_nm_h *nmh)
+{
+	free(nmh);
+}
+#endif
+
+/* Here we are trying to define a high-level API that can be used by
+ * the actual BSC implementation.  However, the architecture is currently
+ * still under design.  Ideally the calls to this API would be synchronous,
+ * while the underlying stack behind the APi runs in a traditional select
+ * based state machine.
+ */
+
+/* 6.2 Software Load: */
+enum sw_state {
+	SW_STATE_NONE,
+	SW_STATE_WAIT_INITACK,
+	SW_STATE_WAIT_SEGACK,
+	SW_STATE_WAIT_ENDACK,
+	SW_STATE_WAIT_ACTACK,
+	SW_STATE_ERROR,
+};
+
+struct abis_nm_sw {
+	struct gsm_bts *bts;
+	gsm_cbfn *cbfn;
+	void *cb_data;
+	int forced;
+
+	/* this will become part of the SW LOAD INITIATE */
+	u_int8_t obj_class;
+	u_int8_t obj_instance[3];
+
+	u_int8_t file_id[255];
+	u_int8_t file_id_len;
+
+	u_int8_t file_version[255];
+	u_int8_t file_version_len;
+
+	u_int8_t window_size;
+	u_int8_t seg_in_window;
+
+	int fd;
+	FILE *stream;
+	enum sw_state state;
+	int last_seg;
+};
+
+static struct abis_nm_sw g_sw;
+
+static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg)
+{
+	if (sw->bts->type == GSM_BTS_TYPE_NANOBTS) {
+		msgb_v_put(msg, NM_ATT_SW_DESCR);
+		msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+		msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+			       sw->file_version);
+	} else if (sw->bts->type == GSM_BTS_TYPE_BS11) {
+		msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+		msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+			     sw->file_version);
+	} else {
+		LOGP(DNM, LOGL_ERROR, "Please implement this for the BTS.\n");
+	}
+}
+
+/* 6.2.1 / 8.3.1: Load Data Initiate */
+static int sw_load_init(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 3*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_INIT, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	sw_add_file_id_and_ver(sw, msg);
+	msgb_tv_put(msg, NM_ATT_WINDOW_SIZE, sw->window_size);
+	
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+static int is_last_line(FILE *stream)
+{
+	char next_seg_buf[256];
+	long pos;
+
+	/* check if we're sending the last line */
+	pos = ftell(stream);
+	if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) {
+		fseek(stream, pos, SEEK_SET);
+		return 1;
+	}
+
+	fseek(stream, pos, SEEK_SET);
+	return 0;
+}
+
+/* 6.2.2 / 8.3.2 Load Data Segment */
+static int sw_load_segment(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	char seg_buf[256];
+	char *line_buf = seg_buf+2;
+	unsigned char *tlv;
+	u_int8_t len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		if (fgets(line_buf, sizeof(seg_buf)-2, sw->stream) == NULL) {
+			perror("fgets reading segment");
+			return -EINVAL;
+		}
+		seg_buf[0] = 0x00;
+
+		/* check if we're sending the last line */
+		sw->last_seg = is_last_line(sw->stream);
+		if (sw->last_seg)
+			seg_buf[1] = 0;
+		else
+			seg_buf[1] = 1 + sw->seg_in_window++;
+
+		len = strlen(line_buf) + 2;
+		tlv = msgb_put(msg, TLV_GROSS_LEN(len));
+		tlv_put(tlv, NM_ATT_BS11_FILE_DATA, len, (u_int8_t *)seg_buf);
+		/* BS11 wants CR + LF in excess of the TLV length !?! */
+		tlv[1] -= 2;
+
+		/* we only now know the exact length for the OM hdr */
+		len = strlen(line_buf)+2;
+		break;
+	case GSM_BTS_TYPE_NANOBTS: {
+		static_assert(sizeof(seg_buf) >= IPACC_SEGMENT_SIZE, buffer_big_enough);
+		len = read(sw->fd, &seg_buf, IPACC_SEGMENT_SIZE);
+		if (len < 0) {
+			perror("read failed");
+			return -EINVAL;
+		}
+
+		if (len != IPACC_SEGMENT_SIZE)
+			sw->last_seg = 1;
+
+		++sw->seg_in_window;
+		msgb_tl16v_put(msg, NM_ATT_IPACC_FILE_DATA, len, (const u_int8_t *) seg_buf);
+		len += 3;
+		break;
+	}
+	default:
+		LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n");
+		/* FIXME: Other BTS types */
+		return -1;
+	}
+
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_SEG, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+/* 6.2.4 / 8.3.4 Load Data End */
+static int sw_load_end(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 2*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_END, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	sw_add_file_id_and_ver(sw, msg);
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+/* Activate the specified software into the BTS */
+static int sw_activate(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 2*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	/* FIXME: this is BS11 specific format */
+	msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+	msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+		     sw->file_version);
+
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+struct sdp_firmware {
+	char magic[4];
+	char more_magic[4];
+	unsigned int header_length;
+	unsigned int file_length;
+} __attribute__ ((packed));
+
+static int parse_sdp_header(struct abis_nm_sw *sw)
+{
+	struct sdp_firmware firmware_header;
+	int rc;
+	struct stat stat;
+
+	rc = read(sw->fd, &firmware_header, sizeof(firmware_header));
+	if (rc != sizeof(firmware_header)) {
+		LOGP(DNM, LOGL_ERROR, "Could not read SDP file header.\n");
+		return -1;
+	}
+
+	if (strncmp(firmware_header.magic, " SDP", 4) != 0) {
+		LOGP(DNM, LOGL_ERROR, "The magic number1 is wrong.\n");
+		return -1;
+	}
+
+	if (firmware_header.more_magic[0] != 0x10 ||
+	    firmware_header.more_magic[1] != 0x02 ||
+	    firmware_header.more_magic[2] != 0x00 ||
+	    firmware_header.more_magic[3] != 0x00) {
+		LOGP(DNM, LOGL_ERROR, "The more magic number is wrong.\n");
+		return -1;
+	}
+
+
+	if (fstat(sw->fd, &stat) == -1) {
+		LOGP(DNM, LOGL_ERROR, "Could not stat the file.\n");
+		return -1;
+	}
+
+	if (ntohl(firmware_header.file_length) != stat.st_size) {
+		LOGP(DNM, LOGL_ERROR, "The filesizes do not match.\n");
+		return -1;
+	}
+
+	/* go back to the start as we checked the whole filesize.. */
+	lseek(sw->fd, 0l, SEEK_SET);
+	LOGP(DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood.\n"
+			       "There might be checksums in the file that are not\n"
+			       "verified and incomplete firmware might be flashed.\n"
+			       "There is absolutely no WARRANTY that flashing will\n"
+			       "work.\n");
+	return 0;
+}
+
+static int sw_open_file(struct abis_nm_sw *sw, const char *fname)
+{
+	char file_id[12+1];
+	char file_version[80+1];
+	int rc;
+
+	sw->fd = open(fname, O_RDONLY);
+	if (sw->fd < 0)
+		return sw->fd;
+
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		sw->stream = fdopen(sw->fd, "r");
+		if (!sw->stream) {
+			perror("fdopen");
+			return -1;
+		}
+		/* read first line and parse file ID and VERSION */
+		rc = fscanf(sw->stream, "@(#)%12s:%80s\r\n",
+			    file_id, file_version);
+		if (rc != 2) {
+			perror("parsing header line of software file");
+			return -1;
+		}
+		strcpy((char *)sw->file_id, file_id);
+		sw->file_id_len = strlen(file_id);
+		strcpy((char *)sw->file_version, file_version);
+		sw->file_version_len = strlen(file_version);
+		/* rewind to start of file */
+		rewind(sw->stream);
+		break;	
+	case GSM_BTS_TYPE_NANOBTS:
+		/* TODO: extract that from the filename or content */
+		rc = parse_sdp_header(sw);
+		if (rc < 0) {
+			fprintf(stderr, "Could not parse the ipaccess SDP header\n");
+			return -1;
+		}
+
+		strcpy((char *)sw->file_id, "id");
+		sw->file_id_len = 3;
+		strcpy((char *)sw->file_version, "version");
+		sw->file_version_len = 8;
+		break;
+	default:
+		/* We don't know how to treat them yet */
+		close(sw->fd);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+	
+static void sw_close_file(struct abis_nm_sw *sw)
+{
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		fclose(sw->stream);
+		break;
+	default:
+		close(sw->fd);
+		break;
+	}
+}
+
+/* Fill the window */
+static int sw_fill_window(struct abis_nm_sw *sw)
+{
+	int rc;
+
+	while (sw->seg_in_window < sw->window_size) {
+		rc = sw_load_segment(sw);
+		if (rc < 0)
+			return rc;
+		if (sw->last_seg)
+			break;
+	}
+	return 0;
+}
+
+/* callback function from abis_nm_rcvmsg() handler */
+static int abis_nm_rcvmsg_sw(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	int rc = -1;
+	struct abis_nm_sw *sw = &g_sw;
+	enum sw_state old_state = sw->state;
+	
+	//DEBUGP(DNM, "state %u, NM MT 0x%02x\n", sw->state, foh->msg_type);
+
+	switch (sw->state) {
+	case SW_STATE_WAIT_INITACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_INIT_ACK:
+			/* fill window with segments */
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_INIT_ACK, mb,
+					 sw->cb_data, NULL);
+			rc = sw_fill_window(sw);
+			sw->state = SW_STATE_WAIT_SEGACK;
+			break;
+		case NM_MT_LOAD_INIT_NACK:
+			if (sw->forced) {
+				DEBUGP(DNM, "FORCED: Ignoring Software Load "
+					"Init NACK\n");
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_INIT_ACK, mb,
+						 sw->cb_data, NULL);
+				rc = sw_fill_window(sw);
+				sw->state = SW_STATE_WAIT_SEGACK;
+			} else {
+				DEBUGP(DNM, "Software Load Init NACK\n");
+				/* FIXME: cause */
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_INIT_NACK, mb,
+						 sw->cb_data, NULL);
+				sw->state = SW_STATE_ERROR;
+			}
+			break;
+		}
+		break;
+	case SW_STATE_WAIT_SEGACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_SEG_ACK:
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_SEG_ACK, mb,
+					 sw->cb_data, NULL);
+			sw->seg_in_window = 0;
+			if (!sw->last_seg) {
+				/* fill window with more segments */
+				rc = sw_fill_window(sw);
+				sw->state = SW_STATE_WAIT_SEGACK;
+			} else {
+				/* end the transfer */
+				sw->state = SW_STATE_WAIT_ENDACK;
+				rc = sw_load_end(sw);
+			}
+			break;
+		case NM_MT_LOAD_ABORT:
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_ABORT, mb,
+					 sw->cb_data, NULL);
+			break;
+		}
+		break;
+	case SW_STATE_WAIT_ENDACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_END_ACK:
+			sw_close_file(sw);
+			DEBUGP(DNM, "Software Load End (BTS %u)\n",
+				sw->bts->nr);
+			sw->state = SW_STATE_NONE;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_END_ACK, mb,
+					 sw->cb_data, NULL);
+			rc = 0;
+			break;
+		case NM_MT_LOAD_END_NACK:
+			if (sw->forced) {
+				DEBUGP(DNM, "FORCED: Ignoring Software Load"
+					"End NACK\n");
+				sw->state = SW_STATE_NONE;
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_END_ACK, mb,
+						 sw->cb_data, NULL);
+			} else {
+				DEBUGP(DNM, "Software Load End NACK\n");
+				/* FIXME: cause */
+				sw->state = SW_STATE_ERROR;
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_END_NACK, mb,
+						 sw->cb_data, NULL);
+			}
+			break;
+		}
+	case SW_STATE_WAIT_ACTACK:
+		switch (foh->msg_type) {
+		case NM_MT_ACTIVATE_SW_ACK:
+			/* we're done */
+			DEBUGP(DNM, "Activate Software DONE!\n");
+			sw->state = SW_STATE_NONE;
+			rc = 0;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_ACTIVATE_SW_ACK, mb,
+					 sw->cb_data, NULL);
+			break;
+		case NM_MT_ACTIVATE_SW_NACK:
+			DEBUGP(DNM, "Activate Software NACK\n");
+			/* FIXME: cause */
+			sw->state = SW_STATE_ERROR;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_ACTIVATE_SW_NACK, mb,
+					 sw->cb_data, NULL);
+			break;
+		}
+	case SW_STATE_NONE:
+		switch (foh->msg_type) {
+		case NM_MT_ACTIVATE_SW_ACK:
+			rc = 0;
+			break;
+		}
+		break;
+	case SW_STATE_ERROR:
+		break;
+	}
+
+	if (rc)
+		DEBUGP(DNM, "unexpected NM MT 0x%02x in state %u -> %u\n",
+			foh->msg_type, old_state, sw->state);
+
+	return rc;
+}
+
+/* Load the specified software into the BTS */
+int abis_nm_software_load(struct gsm_bts *bts, const char *fname,
+			  u_int8_t win_size, int forced,
+			  gsm_cbfn *cbfn, void *cb_data)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	int rc;
+
+	DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n",
+		bts->nr, fname);
+
+	if (sw->state != SW_STATE_NONE)
+		return -EBUSY;
+
+	sw->bts = bts;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		sw->obj_class = NM_OC_SITE_MANAGER;
+		sw->obj_instance[0] = 0xff;
+		sw->obj_instance[1] = 0xff;
+		sw->obj_instance[2] = 0xff;
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		sw->obj_class = NM_OC_BASEB_TRANSC;
+		sw->obj_instance[0] = 0x00;
+		sw->obj_instance[1] = 0x00;
+		sw->obj_instance[2] = 0xff;
+		break;
+	case GSM_BTS_TYPE_UNKNOWN:
+	default:
+		LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n");
+		return -1;
+		break;
+	}
+	sw->window_size = win_size;
+	sw->state = SW_STATE_WAIT_INITACK;
+	sw->cbfn = cbfn;
+	sw->cb_data = cb_data;
+	sw->forced = forced;
+
+	rc = sw_open_file(sw, fname);
+	if (rc < 0) {
+		sw->state = SW_STATE_NONE;
+		return rc;
+	}
+
+	return sw_load_init(sw);
+}
+
+int abis_nm_software_load_status(struct gsm_bts *bts)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	struct stat st;
+	int rc, percent;
+
+	rc = fstat(sw->fd, &st);
+	if (rc < 0) {
+		perror("ERROR during stat");
+		return rc;
+	}
+
+	if (sw->stream)
+		percent = (ftell(sw->stream) * 100) / st.st_size;
+	else
+		percent = (lseek(sw->fd, 0, SEEK_CUR) * 100) / st.st_size;
+	return percent;
+}
+
+/* Activate the specified software into the BTS */
+int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
+			      gsm_cbfn *cbfn, void *cb_data)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	int rc;
+
+	DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n",
+		bts->nr, fname);
+
+	if (sw->state != SW_STATE_NONE)
+		return -EBUSY;
+
+	sw->bts = bts;
+	sw->obj_class = NM_OC_SITE_MANAGER;
+	sw->obj_instance[0] = 0xff;
+	sw->obj_instance[1] = 0xff;
+	sw->obj_instance[2] = 0xff;
+	sw->state = SW_STATE_WAIT_ACTACK;
+	sw->cbfn = cbfn;
+	sw->cb_data = cb_data;
+
+	/* Open the file in order to fill some sw struct members */
+	rc = sw_open_file(sw, fname);
+	if (rc < 0) {
+		sw->state = SW_STATE_NONE;
+		return rc;
+	}
+	sw_close_file(sw);
+
+	return sw_activate(sw);
+}
+
+static void fill_nm_channel(struct abis_nm_channel *ch, u_int8_t bts_port,
+		       u_int8_t ts_nr, u_int8_t subslot_nr)
+{
+	ch->attrib = NM_ATT_ABIS_CHANNEL;
+	ch->bts_port = bts_port;
+	ch->timeslot = ts_nr;
+	ch->subslot = subslot_nr;	
+}
+
+int abis_nm_establish_tei(struct gsm_bts *bts, u_int8_t trx_nr,
+			  u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot,
+			  u_int8_t tei)
+{
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	u_int8_t len = sizeof(*ch) + 2;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ESTABLISH_TEI, NM_OC_RADIO_CARRIER,
+			bts->bts_nr, trx_nr, 0xff);
+	
+	msgb_tv_put(msg, NM_ATT_TEI, tei);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* connect signalling of one (BTS,TRX) to a particular timeslot on the E1 */
+int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx,
+			   u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot)
+{
+	struct gsm_bts *bts = trx->bts;
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_SIGN,
+			NM_OC_RADIO_CARRIER, bts->bts_nr, trx->nr, 0xff);
+	
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+#if 0
+int abis_nm_disc_terr_sign(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
+			   struct abis_nm_abis_channel *chan)
+{
+}
+#endif
+
+int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
+			   u_int8_t e1_port, u_int8_t e1_timeslot,
+			   u_int8_t e1_subslot)
+{
+	struct gsm_bts *bts = ts->trx->bts;
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_TRAF,
+			NM_OC_CHANNEL, bts->bts_nr, ts->trx->nr, ts->nr);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	DEBUGP(DNM, "CONNECT TERR TRAF Um=%s E1=(%u,%u,%u)\n",
+		gsm_ts_name(ts),
+		e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+#if 0
+int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
+			   struct abis_nm_abis_channel *chan,
+			   u_int8_t subchan)
+{
+}
+#endif
+
+/* Chapter 8.6.1 */
+int abis_nm_set_bts_attr(struct gsm_bts *bts, u_int8_t *attr, int attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t *cur;
+
+	DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_SET_BTS_ATTR, NM_OC_BTS, bts->bts_nr, 0xff, 0xff);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.6.2 */
+int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, u_int8_t *attr, int attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t *cur;
+
+	DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER,
+			trx->bts->bts_nr, trx->nr, 0xff);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+static int verify_chan_comb(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb)
+{
+	int i;
+
+	/* As it turns out, the BS-11 has some very peculiar restrictions
+	 * on the channel combinations it allows */
+	switch (ts->trx->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		switch (chan_comb) {
+		case NM_CHANC_TCHHalf:
+		case NM_CHANC_TCHHalf2:
+			/* not supported */
+			return -EINVAL;
+		case NM_CHANC_SDCCH:
+			/* only one SDCCH/8 per TRX */
+			for (i = 0; i < TRX_NR_TS; i++) {
+				if (i == ts->nr)
+					continue;
+				if (ts->trx->ts[i].nm_chan_comb ==
+				    NM_CHANC_SDCCH)
+					return -EINVAL;
+			}
+			/* not allowed for TS0 of BCCH-TRX */
+			if (ts->trx == ts->trx->bts->c0 &&
+			    ts->nr == 0)
+					return -EINVAL;
+			/* not on the same TRX that has a BCCH+SDCCH4
+			 * combination */
+			if (ts->trx == ts->trx->bts->c0 &&
+			    (ts->trx->ts[0].nm_chan_comb == 5 ||
+			     ts->trx->ts[0].nm_chan_comb == 8))
+					return -EINVAL;
+			break;
+		case NM_CHANC_mainBCCH:
+		case NM_CHANC_BCCHComb:
+			/* allowed only for TS0 of C0 */
+			if (ts->trx != ts->trx->bts->c0 ||
+			    ts->nr != 0)
+				return -EINVAL;
+			break;
+		case NM_CHANC_BCCH:
+			/* allowed only for TS 2/4/6 of C0 */
+			if (ts->trx != ts->trx->bts->c0)
+				return -EINVAL;
+			if (ts->nr != 2 && ts->nr != 4 &&
+			    ts->nr != 6)
+				return -EINVAL;
+			break;
+		case 8: /* this is not like 08.58, but in fact
+			 * FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */
+			/* FIXME: only one CBCH allowed per cell */
+			break;
+		}
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		switch (ts->nr) {
+		case 0:
+			if (ts->trx->nr == 0) {
+				/* only on TRX0 */
+				switch (chan_comb) {
+				case NM_CHANC_BCCH:
+				case NM_CHANC_mainBCCH:
+				case NM_CHANC_BCCHComb:
+					return 0;
+					break;
+				default:
+					return -EINVAL;
+				}
+			} else {
+				switch (chan_comb) {
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+					return 0;
+				default:
+					return -EINVAL;
+				}
+			}
+			break;
+		case 1:
+			if (ts->trx->nr == 0) {
+				switch (chan_comb) {
+				case NM_CHANC_SDCCH_CBCH:
+					if (ts->trx->ts[0].nm_chan_comb ==
+					    NM_CHANC_mainBCCH)
+						return 0;
+					return -EINVAL;
+				case NM_CHANC_SDCCH:
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_PDCH:
+					return 0;
+				}
+			} else {
+				switch (chan_comb) {
+				case NM_CHANC_SDCCH:
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+					return 0;
+				default:
+					return -EINVAL;
+				}
+			}
+			break;
+		case 2:
+		case 3:
+		case 4:
+		case 5:
+		case 6:
+		case 7:
+			switch (chan_comb) {
+			case NM_CHANC_TCHFull:
+			case NM_CHANC_TCHHalf:
+			case NM_CHANC_IPAC_TCHFull_TCHHalf:
+				return 0;
+			case NM_CHANC_IPAC_PDCH:
+			case NM_CHANC_IPAC_TCHFull_PDCH:
+				if (ts->trx->nr == 0)
+					return 0;
+				else
+					return -EINVAL;
+			}
+			break;
+		}
+		return -EINVAL;
+	default:
+		/* unknown BTS type */
+		return 0;
+	}
+	return 0;
+}
+
+/* Chapter 8.6.3 */
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb)
+{
+	struct gsm_bts *bts = ts->trx->bts;
+	struct abis_om_hdr *oh;
+	u_int16_t arfcn = htons(ts->trx->arfcn);
+	u_int8_t zero = 0x00;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 2 + 2;
+
+	if (bts->type == GSM_BTS_TYPE_BS11)
+		len += 4 + 2 + 2 + 3;
+
+	DEBUGP(DNM, "Set Chan Attr %s\n", gsm_ts_name(ts));
+	if (verify_chan_comb(ts, chan_comb) < 0) {
+		msgb_free(msg);
+		DEBUGP(DNM, "Invalid Channel Combination!!!\n");
+		return -EINVAL;
+	}
+	ts->nm_chan_comb = chan_comb;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR,
+			NM_OC_CHANNEL, bts->bts_nr,
+			ts->trx->nr, ts->nr);
+	/* FIXME: don't send ARFCN list, hopping sequence, mAIO, ...*/
+	if (bts->type == GSM_BTS_TYPE_BS11)
+		msgb_tlv16_put(msg, NM_ATT_ARFCN_LIST, 1, &arfcn);
+	msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb);
+	if (bts->type == GSM_BTS_TYPE_BS11) {
+		msgb_tv_put(msg, NM_ATT_HSN, 0x00);
+		msgb_tv_put(msg, NM_ATT_MAIO, 0x00);
+	}
+	msgb_tv_put(msg, NM_ATT_TSC, bts->tsc);	/* training sequence */
+	if (bts->type == GSM_BTS_TYPE_BS11)
+		msgb_tlv_put(msg, 0x59, 1, &zero);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_sw_act_req_ack(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i1,
+			u_int8_t i2, u_int8_t i3, int nack, u_int8_t *attr, int att_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t msgtype = NM_MT_SW_ACT_REQ_ACK;
+	u_int8_t len = att_len;
+
+	if (nack) {
+		len += 2;
+		msgtype = NM_MT_SW_ACT_REQ_NACK;
+	}
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, att_len, msgtype, obj_class, i1, i2, i3);
+
+	if (attr) {
+		u_int8_t *ptr = msgb_put(msg, att_len);
+		memcpy(ptr, attr, att_len);
+	}
+	if (nack)
+		msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_raw_msg(struct gsm_bts *bts, int len, u_int8_t *rawmsg)
+{
+	struct msgb *msg = nm_msgb_alloc();
+	struct abis_om_hdr *oh;
+	u_int8_t *data;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh));
+	fill_om_hdr(oh, len);
+	data = msgb_put(msg, len);
+	memcpy(data, rawmsg, len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Siemens specific commands */
+static int __simple_cmd(struct gsm_bts *bts, u_int8_t msg_type)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, msg_type, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.9.2 */
+int abis_nm_opstart(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, u_int8_t i2)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_OPSTART, obj_class, i0, i1, i2);
+
+	debugp_foh((struct abis_om_fom_hdr *) oh->data);
+	DEBUGPC(DNM, "Sending OPSTART\n");
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.8.5 */
+int abis_nm_chg_adm_state(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0,
+			  u_int8_t i1, u_int8_t i2, enum abis_nm_adm_state adm_state)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2, NM_MT_CHG_ADM_STATE, obj_class, i0, i1, i2);
+	msgb_tv_put(msg, NM_ATT_ADM_STATE, adm_state);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_conn_mdrop_link(struct gsm_bts *bts, u_int8_t e1_port0, u_int8_t ts0,
+			    u_int8_t e1_port1, u_int8_t ts1)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t *attr;
+
+	DEBUGP(DNM, "CONNECT MDROP LINK E1=(%u,%u) -> E1=(%u, %u)\n",
+		e1_port0, ts0, e1_port1, ts1);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 6, NM_MT_CONN_MDROP_LINK,
+			NM_OC_SITE_MANAGER, 0x00, 0x00, 0x00);
+
+	attr = msgb_put(msg, 3);
+	attr[0] = NM_ATT_MDROP_LINK;
+	attr[1] = e1_port0;
+	attr[2] = ts0;
+
+	attr = msgb_put(msg, 3);
+	attr[0] = NM_ATT_MDROP_NEXT;
+	attr[1] = e1_port1;
+	attr[2] = ts1;
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.7.1 */
+int abis_nm_perform_test(struct gsm_bts *bts, u_int8_t obj_class,
+			 u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t test_nr, u_int8_t auton_report,
+			 u_int8_t *phys_config, u_int16_t phys_config_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	int len = 4; /* 2 TV attributes */
+
+	DEBUGP(DNM, "PEFORM TEST\n");
+	
+	if (phys_config_len)
+		len += 3 + phys_config_len;
+	
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_PERF_TEST,
+			obj_class, bts_nr, trx_nr, ts_nr);
+	msgb_tv_put(msg, NM_ATT_TEST_NO, test_nr);
+	msgb_tv_put(msg, NM_ATT_AUTON_REPORT, auton_report);
+	if (phys_config_len)
+		msgb_tl16v_put(msg, NM_ATT_PHYS_CONF, phys_config_len,
+				phys_config);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_event_reports(struct gsm_bts *bts, int on)
+{
+	if (on == 0)
+		return __simple_cmd(bts, NM_MT_STOP_EVENT_REP);
+	else
+		return __simple_cmd(bts, NM_MT_REST_EVENT_REP);
+}
+
+/* Siemens (or BS-11) specific commands */
+
+int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect)
+{
+	if (reconnect == 0)
+		return __simple_cmd(bts, NM_MT_BS11_DISCONNECT);
+	else
+		return __simple_cmd(bts, NM_MT_BS11_RECONNECT);
+}
+
+int abis_nm_bs11_restart(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_RESTART);
+}
+
+
+struct bs11_date_time {
+	u_int16_t	year;
+	u_int8_t	month;
+	u_int8_t	day;
+	u_int8_t	hour;
+	u_int8_t	min;
+	u_int8_t	sec;
+} __attribute__((packed));
+
+
+void get_bs11_date_time(struct bs11_date_time *aet)
+{
+	time_t t;
+	struct tm *tm;
+
+	t = time(NULL);
+	tm = localtime(&t);
+	aet->sec = tm->tm_sec;
+	aet->min = tm->tm_min;
+	aet->hour = tm->tm_hour;
+	aet->day = tm->tm_mday;
+	aet->month = tm->tm_mon;
+	aet->year = htons(1900 + tm->tm_year);
+}
+
+int abis_nm_bs11_reset_resource(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_RESET_RESOURCE);
+}
+
+int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin)
+{
+	if (begin)
+		return __simple_cmd(bts, NM_MT_BS11_BEGIN_DB_TX);
+	else
+		return __simple_cmd(bts, NM_MT_BS11_END_DB_TX);
+}
+
+int abis_nm_bs11_create_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, u_int8_t idx,
+				u_int8_t attr_len, const u_int8_t *attr)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t *cur;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_BS11_CREATE_OBJ,
+			NM_OC_BS11, type, 0, idx);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_delete_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ,
+			NM_OC_BS11, type, 0, idx);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t zero = 0x00;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_CREATE_OBJ,
+			NM_OC_BS11_ENVABTSE, 0, idx, 0xff);
+	msgb_tlv_put(msg, 0x99, 1, &zero);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_create_bport(struct gsm_bts *bts, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_CREATE_OBJ, NM_OC_BS11_BPORT,
+			idx, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_delete_bport(struct gsm_bts *bts, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, NM_OC_BS11_BPORT,
+			idx, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+static const u_int8_t sm_attr[] = { NM_ATT_TEI, NM_ATT_ABIS_CHANNEL };
+int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(sm_attr), NM_MT_GET_ATTR, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(sm_attr), sm_attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* like abis_nm_conn_terr_traf + set_tei */
+int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, u_int8_t e1_port,
+			  u_int8_t e1_timeslot, u_int8_t e1_subslot,
+			  u_int8_t tei)
+{
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch)+2, NM_MT_BS11_SET_ATTR,
+			NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+	msgb_tv_put(msg, NM_ATT_TEI, tei);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, u_int8_t level)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR,
+			NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr);
+	msgb_tlv_put(msg, NM_ATT_BS11_TXPWR, 1, &level);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t attr = NM_ATT_BS11_TXPWR;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t attr[] = { NM_ATT_BS11_PLL_MODE };
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_LI, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_cclk(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t attr[] = { NM_ATT_BS11_CCLK_ACCURACY,
+			    NM_ATT_BS11_CCLK_TYPE };
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_CCLK, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr);
+
+	return abis_nm_sendmsg(bts, msg);
+
+}
+
+//static const u_int8_t bs11_logon_c7[] = { 0x07, 0xd9, 0x01, 0x11, 0x0d, 0x10, 0x20 };
+
+int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on)
+{
+	return abis_nm_bs11_logon(bts, 0x02, "FACTORY", on);
+}
+
+int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on)
+{
+	return abis_nm_bs11_logon(bts, 0x03, "FIELD  ", on);
+}
+
+int abis_nm_bs11_logon(struct gsm_bts *bts, u_int8_t level, const char *name, int on)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time bdt;
+
+	get_bs11_date_time(&bdt);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	if (on) {
+		u_int8_t len = 3*2 + sizeof(bdt)
+				+ 1 + strlen(name);
+		fill_om_fom_hdr(oh, len, NM_MT_BS11_LMT_LOGON,
+				NM_OC_BS11_BTSE, 0xff, 0xff, 0xff);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_LOGIN_TIME,
+			     sizeof(bdt), (u_int8_t *) &bdt);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_ACC_LEV,
+			     1, &level);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_NAME,
+			     strlen(name), (u_int8_t *)name);
+	} else {
+		fill_om_fom_hdr(oh, 0, NM_MT_BS11_LMT_LOGOFF,
+				NM_OC_BS11_BTSE, 0xff, 0xff, 0xff);
+	}
+	
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+
+	if (strlen(password) != 10)
+		return -EINVAL;
+
+ 	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+strlen(password), NM_MT_BS11_SET_ATTR,
+			NM_OC_BS11, BS11_OBJ_TRX1, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_BS11_PASSWORD, 10, (const u_int8_t *)password);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* change the BS-11 PLL Mode to either locked (E1 derived) or standalone */
+int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+	u_int8_t tlv_value;
+	
+	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11,
+			BS11_OBJ_LI, 0x00, 0x00);
+
+	if (locked)
+		tlv_value = BS11_LI_PLL_LOCKED;
+	else
+		tlv_value = BS11_LI_PLL_STANDALONE;
+	
+	msgb_tlv_put(msg, NM_ATT_BS11_PLL_MODE, 1, &tlv_value);
+	
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Set the calibration value of the PLL (work value/set value)
+ * It depends on the login which one is changed */
+int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+	u_int8_t tlv_value[2];
+
+	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11,
+			BS11_OBJ_TRX1, 0x00, 0x00);
+
+	tlv_value[0] = value>>8;
+	tlv_value[1] = value&0xff;
+
+	msgb_tlv_put(msg, NM_ATT_BS11_PLL, 2, tlv_value);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_state(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_GET_STATE);
+}
+
+/* BS11 SWL */
+
+void *tall_fle_ctx;
+
+struct abis_nm_bs11_sw {
+	struct gsm_bts *bts;
+	char swl_fname[PATH_MAX];
+	u_int8_t win_size;
+	int forced;
+	struct llist_head file_list;
+	gsm_cbfn *user_cb;	/* specified by the user */
+};
+static struct abis_nm_bs11_sw _g_bs11_sw, *g_bs11_sw = &_g_bs11_sw;
+
+struct file_list_entry {
+	struct llist_head list;
+	char fname[PATH_MAX];
+};
+
+struct file_list_entry *fl_dequeue(struct llist_head *queue)
+{
+	struct llist_head *lh;
+
+	if (llist_empty(queue))
+		return NULL;
+
+	lh = queue->next;
+	llist_del(lh);
+	
+	return llist_entry(lh, struct file_list_entry, list);
+}
+
+static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw)
+{
+	char linebuf[255];
+	struct llist_head *lh, *lh2;
+	FILE *swl;
+	int rc = 0;
+
+	swl = fopen(bs11_sw->swl_fname, "r");
+	if (!swl)
+		return -ENODEV;
+
+	/* zero the stale file list, if any */
+	llist_for_each_safe(lh, lh2, &bs11_sw->file_list) {
+		llist_del(lh);
+		talloc_free(lh);
+	}
+
+	while (fgets(linebuf, sizeof(linebuf), swl)) {
+		char file_id[12+1];
+		char file_version[80+1];
+		struct file_list_entry *fle;
+		static char dir[PATH_MAX];
+
+		if (strlen(linebuf) < 4)
+			continue;
+	
+		rc = sscanf(linebuf+4, "%12s:%80s\r\n", file_id, file_version);
+		if (rc < 0) {
+			perror("ERR parsing SWL file");
+			rc = -EINVAL;
+			goto out;
+		}
+		if (rc < 2)
+			continue;
+
+		fle = talloc_zero(tall_fle_ctx, struct file_list_entry);
+		if (!fle) {
+			rc = -ENOMEM;
+			goto out;
+		}
+
+		/* construct new filename */
+		strncpy(dir, bs11_sw->swl_fname, sizeof(dir));
+		strncat(fle->fname, dirname(dir), sizeof(fle->fname) - 1);
+		strcat(fle->fname, "/");
+		strncat(fle->fname, file_id, sizeof(fle->fname) - 1 -strlen(fle->fname));
+		
+		llist_add_tail(&fle->list, &bs11_sw->file_list);
+	}
+
+out:
+	fclose(swl);
+	return rc;
+}
+
+/* bs11 swload specific callback, passed to abis_nm core swload */
+static int bs11_swload_cbfn(unsigned int hook, unsigned int event,
+			    struct msgb *msg, void *data, void *param)
+{
+	struct abis_nm_bs11_sw *bs11_sw = data;
+	struct file_list_entry *fle;
+	int rc = 0;
+
+	switch (event) {
+	case NM_MT_LOAD_END_ACK:
+		fle = fl_dequeue(&bs11_sw->file_list);
+		if (fle) {
+			/* start download the next file of our file list */
+			rc = abis_nm_software_load(bs11_sw->bts, fle->fname,
+						   bs11_sw->win_size,
+						   bs11_sw->forced,
+						   &bs11_swload_cbfn, bs11_sw);
+			talloc_free(fle);
+		} else {
+			/* activate the SWL */
+			rc = abis_nm_software_activate(bs11_sw->bts,
+							bs11_sw->swl_fname,
+							bs11_swload_cbfn,
+							bs11_sw);
+		}
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+	case NM_MT_LOAD_END_NACK:
+	case NM_MT_LOAD_INIT_ACK:
+	case NM_MT_LOAD_INIT_NACK:
+	case NM_MT_ACTIVATE_SW_NACK:
+	case NM_MT_ACTIVATE_SW_ACK:
+	default:
+		/* fallthrough to the user callback */
+		if (bs11_sw->user_cb)
+			rc = bs11_sw->user_cb(hook, event, msg, NULL, NULL);
+		break;
+	}
+
+	return rc;
+}
+
+/* Siemens provides a SWL file that is a mere listing of all the other
+ * files that are part of a software release.  We need to upload first
+ * the list file, and then each file that is listed in the list file */
+int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname,
+			  u_int8_t win_size, int forced, gsm_cbfn *cbfn)
+{
+	struct abis_nm_bs11_sw *bs11_sw = g_bs11_sw;
+	struct file_list_entry *fle;
+	int rc = 0;
+
+	INIT_LLIST_HEAD(&bs11_sw->file_list);
+	bs11_sw->bts = bts;
+	bs11_sw->win_size = win_size;
+	bs11_sw->user_cb = cbfn;
+	bs11_sw->forced = forced;
+
+	strncpy(bs11_sw->swl_fname, fname, sizeof(bs11_sw->swl_fname));
+	rc = bs11_read_swl_file(bs11_sw);
+	if (rc < 0)
+		return rc;
+
+	/* dequeue next item in file list */
+	fle = fl_dequeue(&bs11_sw->file_list);
+	if (!fle)
+		return -EINVAL;
+
+	/* start download the next file of our file list */
+	rc = abis_nm_software_load(bts, fle->fname, win_size, forced,
+				   bs11_swload_cbfn, bs11_sw);
+	talloc_free(fle);
+	return rc;
+}
+
+#if 0
+static u_int8_t req_attr_btse[] = {
+	NM_ATT_ADM_STATE, NM_ATT_BS11_LMT_LOGON_SESSION,
+	NM_ATT_BS11_LMT_LOGIN_TIME, NM_ATT_BS11_LMT_USER_ACC_LEV,
+	NM_ATT_BS11_LMT_USER_NAME,
+
+	0xaf, NM_ATT_BS11_RX_OFFSET, NM_ATT_BS11_VENDOR_NAME,
+
+	NM_ATT_BS11_SW_LOAD_INTENDED, NM_ATT_BS11_SW_LOAD_SAFETY,
+
+	NM_ATT_BS11_SW_LOAD_STORED };
+
+static u_int8_t req_attr_btsm[] = {
+	NM_ATT_ABIS_CHANNEL, NM_ATT_TEI, NM_ATT_BS11_ABIS_EXT_TIME,
+	NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xce, NM_ATT_FILE_ID,
+	NM_ATT_FILE_VERSION, NM_ATT_OPER_STATE, 0xe8, NM_ATT_BS11_ALL_TEST_CATG,
+	NM_ATT_SW_DESCR, NM_ATT_GET_ARI };
+#endif
+	
+static u_int8_t req_attr[] = {
+	NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xa8, NM_ATT_OPER_STATE,
+	0xd5, 0xa1, NM_ATT_BS11_ESN_FW_CODE_NO, NM_ATT_BS11_ESN_HW_CODE_NO,
+	0x42, NM_ATT_BS11_ESN_PCB_SERIAL, NM_ATT_BS11_PLL };
+
+int abis_nm_bs11_get_serno(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	/* SiemensHW CCTRL object */
+	fill_om_fom_hdr(oh, 2+sizeof(req_attr), NM_MT_GET_ATTR, NM_OC_BS11,
+			0x03, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(req_attr), req_attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_ext_time(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time aet;
+
+	get_bs11_date_time(&aet);
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	/* SiemensHW CCTRL object */
+	fill_om_fom_hdr(oh, 2+sizeof(aet), NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+	msgb_tlv_put(msg, NM_ATT_BS11_ABIS_EXT_TIME, sizeof(aet), (u_int8_t *) &aet);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport, enum abis_bs11_line_cfg line_cfg)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time aet;
+
+	get_bs11_date_time(&aet);
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2, NM_MT_BS11_SET_ATTR, NM_OC_BS11_BPORT,
+			bport, 0xff, 0x02);
+	msgb_tv_put(msg, NM_ATT_BS11_LINE_CFG, line_cfg);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* ip.access nanoBTS specific commands */
+static const char ipaccess_magic[] = "com.ipaccess";
+
+
+static int abis_nm_rx_ipacc(struct msgb *msg)
+{
+	struct abis_om_hdr *oh = msgb_l2(msg);
+	struct abis_om_fom_hdr *foh;
+	u_int8_t idstrlen = oh->data[0];
+	struct tlv_parsed tp;
+	struct ipacc_ack_signal_data signal;
+
+	if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) {
+		LOGP(DNM, LOGL_ERROR, "id string is not com.ipaccess !?!\n");
+		return -EINVAL;
+	}
+
+	foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen);
+	abis_nm_tlv_parse(&tp, msg->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+	debugp_foh(foh);
+
+	DEBUGPC(DNM, "IPACCESS(0x%02x): ", foh->msg_type);
+
+	switch (foh->msg_type) {
+	case NM_MT_IPACC_RSL_CONNECT_ACK:
+		DEBUGPC(DNM, "RSL CONNECT ACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP))
+			DEBUGPC(DNM, "IP=%s ",
+				inet_ntoa(*((struct in_addr *)
+					TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP))));
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP_PORT))
+			DEBUGPC(DNM, "PORT=%u ",
+				ntohs(*((u_int16_t *)
+					TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP_PORT))));
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_STREAM_ID))
+			DEBUGPC(DNM, "STREAM=0x%02x ",
+					*TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID));
+		DEBUGPC(DNM, "\n");
+		break;
+	case NM_MT_IPACC_RSL_CONNECT_NACK:
+		LOGP(DNM, LOGL_ERROR, "RSL CONNECT NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			DEBUGPC(DNM, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			DEBUGPC(DNM, "\n");
+		break;
+	case NM_MT_IPACC_SET_NVATTR_ACK:
+		DEBUGPC(DNM, "SET NVATTR ACK\n");
+		/* FIXME: decode and show the actual attributes */
+		break;
+	case NM_MT_IPACC_SET_NVATTR_NACK:
+		LOGP(DNM, LOGL_ERROR, "SET NVATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	case NM_MT_IPACC_GET_NVATTR_ACK:
+		DEBUGPC(DNM, "GET NVATTR ACK\n");
+		/* FIXME: decode and show the actual attributes */
+		break;
+	case NM_MT_IPACC_GET_NVATTR_NACK:
+		LOGPC(DNM, LOGL_ERROR, "GET NVATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	case NM_MT_IPACC_SET_ATTR_ACK:
+		DEBUGPC(DNM, "SET ATTR ACK\n");
+		break;
+	case NM_MT_IPACC_SET_ATTR_NACK:
+		LOGPC(DNM, LOGL_ERROR, "SET ATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	default:
+		DEBUGPC(DNM, "unknown\n");
+		break;
+	}
+
+	/* signal handling */
+	switch  (foh->msg_type) {
+	case NM_MT_IPACC_RSL_CONNECT_NACK:
+	case NM_MT_IPACC_SET_NVATTR_NACK:
+	case NM_MT_IPACC_GET_NVATTR_NACK:
+		signal.bts = msg->trx->bts;
+		signal.msg_type = foh->msg_type;
+		dispatch_signal(SS_NM, S_NM_IPACC_NACK, &signal);
+		break;
+	case NM_MT_IPACC_SET_NVATTR_ACK:
+		signal.bts = msg->trx->bts;
+		signal.msg_type = foh->msg_type;
+		dispatch_signal(SS_NM, S_NM_IPACC_ACK, &signal);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* send an ip-access manufacturer specific message */
+int abis_nm_ipaccess_msg(struct gsm_bts *bts, u_int8_t msg_type,
+			 u_int8_t obj_class, u_int8_t bts_nr,
+			 u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t *attr, int attr_len)
+{
+	struct msgb *msg = nm_msgb_alloc();
+	struct abis_om_hdr *oh;
+	struct abis_om_fom_hdr *foh;
+	u_int8_t *data;
+
+	/* construct the 12.21 OM header, observe the erroneous length */
+	oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh));
+	fill_om_hdr(oh, sizeof(*foh) + attr_len);
+	oh->mdisc = ABIS_OM_MDISC_MANUF;
+
+	/* add the ip.access magic */
+	data = msgb_put(msg, sizeof(ipaccess_magic)+1);
+	*data++ = sizeof(ipaccess_magic);
+	memcpy(data, ipaccess_magic, sizeof(ipaccess_magic));
+
+	/* fill the 12.21 FOM header */
+	foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh));
+	foh->msg_type = msg_type;
+	foh->obj_class = obj_class;
+	foh->obj_inst.bts_nr = bts_nr;
+	foh->obj_inst.trx_nr = trx_nr;
+	foh->obj_inst.ts_nr = ts_nr;
+
+	if (attr && attr_len) {
+		data = msgb_put(msg, attr_len);
+		memcpy(data, attr, attr_len);
+	}
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* set some attributes in NVRAM */
+int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, u_int8_t *attr,
+				int attr_len)
+{
+	return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_SET_NVATTR,
+				    NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, attr,
+				    attr_len);
+}
+
+int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx,
+				 u_int32_t ip, u_int16_t port, u_int8_t stream)
+{
+	struct in_addr ia;
+	u_int8_t attr[] = { NM_ATT_IPACC_STREAM_ID, 0,
+			    NM_ATT_IPACC_DST_IP_PORT, 0, 0,
+			    NM_ATT_IPACC_DST_IP, 0, 0, 0, 0 };
+
+	int attr_len = sizeof(attr);
+
+	ia.s_addr = htonl(ip);
+	attr[1] = stream;
+	attr[3] = port >> 8;
+	attr[4] = port & 0xff;
+	*(u_int32_t *)(attr+6) = ia.s_addr;
+
+	/* if ip == 0, we use the default IP */
+	if (ip == 0)
+		attr_len -= 5;
+
+	DEBUGP(DNM, "ip.access RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+		inet_ntoa(ia), port, stream);
+
+	return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_RSL_CONNECT,
+				    NM_OC_BASEB_TRANSC, trx->bts->bts_nr,
+				    trx->nr, 0xff, attr, attr_len);
+}
+
+/* restart / reboot an ip.access nanoBTS */
+int abis_nm_ipaccess_restart(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_IPACC_RESTART);
+}
+
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, u_int8_t obj_class,
+				u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+				u_int8_t *attr, u_int8_t attr_len)
+{
+	return abis_nm_ipaccess_msg(bts, NM_MT_IPACC_SET_ATTR,
+				    obj_class, bts_nr, trx_nr, ts_nr,
+				     attr, attr_len);
+}
+
+void abis_nm_ipaccess_cgi(u_int8_t *buf, struct gsm_bts *bts)
+{
+	/* we simply reuse the GSM48 function and overwrite the RAC
+	 * with the Cell ID */
+	gsm48_ra_id_by_bts(buf, bts);
+	*((u_int16_t *)(buf + 5)) = htons(bts->cell_identity);
+}
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked)
+{
+	int new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+
+	trx->nm_state.administrative = new_state;
+	if (!trx->bts || !trx->bts->oml_link)
+		return;
+
+	abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER,
+			      trx->bts->bts_nr, trx->nr, 0xff,
+			      new_state);
+}
+
+static const struct value_string ipacc_testres_names[] = {
+	{ NM_IPACC_TESTRES_SUCCESS,	"SUCCESS" },
+	{ NM_IPACC_TESTRES_TIMEOUT,	"TIMEOUT" },
+	{ NM_IPACC_TESTRES_NO_CHANS,	"NO CHANNELS" },
+	{ NM_IPACC_TESTRES_PARTIAL,	"PARTIAL" },
+	{ NM_IPACC_TESTRES_STOPPED,	"STOPPED" },
+	{ 0,				NULL }
+};
+
+const char *ipacc_testres_name(u_int8_t res)
+{
+	return get_value_string(ipacc_testres_names, res);
+}
+
+void ipac_parse_cgi(struct cell_global_id *cid, const u_int8_t *buf)
+{
+	cid->mcc = (buf[0] & 0xf) * 100;
+	cid->mcc += (buf[0] >> 4) *  10;
+	cid->mcc += (buf[1] & 0xf) *  1;
+
+	if (buf[1] >> 4 == 0xf) {
+		cid->mnc = (buf[2] & 0xf) * 10;
+		cid->mnc += (buf[2] >> 4) *  1;
+	} else {
+		cid->mnc = (buf[2] & 0xf) * 100;
+		cid->mnc += (buf[2] >> 4) *  10;
+		cid->mnc += (buf[1] >> 4) *   1;
+	}
+
+	cid->lac = ntohs(*((u_int16_t *)&buf[3]));
+	cid->ci = ntohs(*((u_int16_t *)&buf[5]));
+}
+
+/* parse BCCH information IEI from wire format to struct ipac_bcch_info */
+int ipac_parse_bcch_info(struct ipac_bcch_info *binf, u_int8_t *buf)
+{
+	u_int8_t *cur = buf;
+	u_int16_t len;
+
+	memset(binf, 0, sizeof(binf));
+
+	if (cur[0] != NM_IPAC_EIE_BCCH_INFO)
+		return -EINVAL;
+	cur++;
+
+	len = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	binf->info_type = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL)
+		binf->freq_qual = *cur >> 2;
+
+	binf->arfcn = *cur++ & 3 << 8;
+	binf->arfcn |= *cur++;
+
+	if (binf->info_type & IPAC_BINF_RXLEV)
+		binf->rx_lev = *cur & 0x3f;
+	cur++;
+
+	if (binf->info_type & IPAC_BINF_RXQUAL)
+		binf->rx_qual = *cur & 0x7;
+	cur++;
+
+	if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL)
+		binf->freq_err = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FRAME_OFFSET)
+		binf->frame_offset = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET)
+		binf->frame_nr_offset = ntohl(*(u_int32_t *)cur);
+	cur += 4;
+
+	if (binf->info_type & IPAC_BINF_BSIC)
+		binf->bsic = *cur & 0x3f;
+	cur++;
+
+	ipac_parse_cgi(&binf->cgi, cur);
+	cur += 7;
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2) {
+		memcpy(binf->ba_list_si2, cur, sizeof(binf->ba_list_si2));
+		cur += sizeof(binf->ba_list_si2);
+	}
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2bis) {
+		memcpy(binf->ba_list_si2bis, cur,
+			sizeof(binf->ba_list_si2bis));
+		cur += sizeof(binf->ba_list_si2bis);
+	}
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2ter) {
+		memcpy(binf->ba_list_si2ter, cur,
+			sizeof(binf->ba_list_si2ter));
+		cur += sizeof(binf->ba_list_si2ter);
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/abis_rsl.c b/openbsc/src/abis_rsl.c
new file mode 100644
index 0000000..53b2982
--- /dev/null
+++ b/openbsc/src/abis_rsl.c
@@ -0,0 +1,1765 @@
+/* GSM Radio Signalling Link messages on the A-bis interface
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/debug.h>
+#include <osmocore/tlv.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/rtp_proxy.h>
+#include <osmocore/rsl.h>
+
+#define RSL_ALLOC_SIZE		1024
+#define RSL_ALLOC_HEADROOM	128
+
+#define MAX(a, b) (a) >= (b) ? (a) : (b)
+
+static u_int8_t mdisc_by_msgtype(u_int8_t msg_type)
+{
+	/* mask off the transparent bit ? */
+	msg_type &= 0xfe;
+
+	if ((msg_type & 0xf0) == 0x00)
+		return ABIS_RSL_MDISC_RLL;
+	if ((msg_type & 0xf0) == 0x10) {
+		if (msg_type >= 0x19 && msg_type <= 0x22)
+			return ABIS_RSL_MDISC_TRX;
+		else
+			return ABIS_RSL_MDISC_COM_CHAN;
+	}
+	if ((msg_type & 0xe0) == 0x20)
+		return ABIS_RSL_MDISC_DED_CHAN;
+	
+	return ABIS_RSL_MDISC_LOC;
+}
+
+static inline void init_dchan_hdr(struct abis_rsl_dchan_hdr *dh,
+				  u_int8_t msg_type)
+{
+	dh->c.msg_discr = mdisc_by_msgtype(msg_type);
+	dh->c.msg_type = msg_type;
+	dh->ie_chan = RSL_IE_CHAN_NR;
+}
+
+/* determine logical channel based on TRX and channel number IE */
+struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, u_int8_t chan_nr)
+{
+	struct gsm_lchan *lchan;
+	u_int8_t ts_nr = chan_nr & 0x07;
+	u_int8_t cbits = chan_nr >> 3;
+	u_int8_t lch_idx;
+	struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+
+	if (cbits == 0x01) {
+		lch_idx = 0;	/* TCH/F */	
+		if (ts->pchan != GSM_PCHAN_TCH_F &&
+		    ts->pchan != GSM_PCHAN_PDCH &&
+		    ts->pchan != GSM_PCHAN_TCH_F_PDCH)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x1e) == 0x02) {
+		lch_idx = cbits & 0x1;	/* TCH/H */
+		if (ts->pchan != GSM_PCHAN_TCH_H)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x1c) == 0x04) {
+		lch_idx = cbits & 0x3;	/* SDCCH/4 */
+		if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x18) == 0x08) {
+		lch_idx = cbits & 0x7;	/* SDCCH/8 */
+		if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
+		lch_idx = 0;
+		if (ts->pchan != GSM_PCHAN_CCCH &&
+		    ts->pchan != GSM_PCHAN_CCCH_SDCCH4)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+		/* FIXME: we should not return first sdcch4 !!! */
+	} else {
+		LOGP(DRSL, LOGL_ERROR, "unknown chan_nr=0x%02x\n", chan_nr);
+		return NULL;
+	}
+
+	lchan = &ts->lchan[lch_idx];
+	log_set_context(BSC_CTX_LCHAN, lchan);
+	log_set_context(BSC_CTX_SUBSCR, lchan->conn.subscr);
+
+	return lchan;
+}
+
+/* See Table 10.5.25 of GSM04.08 */
+u_int8_t lchan2chan_nr(const struct gsm_lchan *lchan)
+{
+	struct gsm_bts_trx_ts *ts = lchan->ts;
+	u_int8_t cbits, chan_nr;
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_PDCH:
+	case GSM_PCHAN_TCH_F_PDCH:
+		cbits = 0x01;
+		break;
+	case GSM_PCHAN_TCH_H:
+		cbits = 0x02;
+		cbits += lchan->nr;
+		break;
+	case GSM_PCHAN_CCCH_SDCCH4:
+		cbits = 0x04;
+		cbits += lchan->nr;
+		break;
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+		cbits = 0x08;
+		cbits += lchan->nr;
+		break;
+	default:
+	case GSM_PCHAN_CCCH:
+		cbits = 0x10;
+		break;
+	}
+
+	chan_nr = (cbits << 3) | (ts->nr & 0x7);
+
+	return chan_nr;
+}
+
+/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */
+u_int64_t str_to_imsi(const char *imsi_str)
+{
+	u_int64_t ret;
+
+	ret = strtoull(imsi_str, NULL, 10);
+
+	return ret;
+}
+
+/* Table 5 Clause 7 TS 05.02 */
+unsigned int n_pag_blocks(int bs_ccch_sdcch_comb, unsigned int bs_ag_blks_res)
+{
+	if (!bs_ccch_sdcch_comb)
+		return 9 - bs_ag_blks_res;
+	else
+		return 3 - bs_ag_blks_res;
+}
+
+/* Chapter 6.5.2 of TS 05.02 */
+unsigned int get_ccch_group(u_int64_t imsi, unsigned int bs_cc_chans,
+			    unsigned int n_pag_blocks)
+{
+	return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) / n_pag_blocks;
+}
+
+/* Chapter 6.5.2 of TS 05.02 */
+unsigned int get_paging_group(u_int64_t imsi, unsigned int bs_cc_chans,
+			      int n_pag_blocks)
+{
+	return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) % n_pag_blocks;
+}
+
+static struct msgb *rsl_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(RSL_ALLOC_SIZE, RSL_ALLOC_HEADROOM,
+				   "RSL");
+}
+
+#define MACBLOCK_SIZE	23
+static void pad_macblock(u_int8_t *out, const u_int8_t *in, int len)
+{
+	memcpy(out, in, len);
+
+	if (len < MACBLOCK_SIZE)
+		memset(out+len, 0x2b, MACBLOCK_SIZE-len);
+}
+
+/* Chapter 9.3.7: Encryption Information */
+static int build_encr_info(u_int8_t *out, struct gsm_lchan *lchan)
+{
+	*out++ = lchan->encr.alg_id & 0xff;
+	if (lchan->encr.key_len)
+		memcpy(out, lchan->encr.key, lchan->encr.key_len);
+	return lchan->encr.key_len + 1;
+}
+
+static void print_rsl_cause(int lvl, const u_int8_t *cause_v, u_int8_t cause_len)
+{
+	int i;
+
+	LOGPC(DRSL, lvl, "CAUSE=0x%02x(%s) ",
+		cause_v[0], rsl_err_name(cause_v[0]));
+	for (i = 1; i < cause_len-1; i++)
+		LOGPC(DRSL, lvl, "%02x ", cause_v[i]);
+}
+
+/* Send a BCCH_INFO message as per Chapter 8.5.1 */
+int rsl_bcch_info(struct gsm_bts_trx *trx, u_int8_t type,
+		  const u_int8_t *data, int len)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof*dh);
+	init_dchan_hdr(dh, RSL_MT_BCCH_INFO);
+	dh->chan_nr = RSL_CHAN_BCCH;
+
+	msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+	msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data);
+
+	msg->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_sacch_filling(struct gsm_bts_trx *trx, u_int8_t type,
+		      const u_int8_t *data, int len)
+{
+	struct abis_rsl_common_hdr *ch;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+	ch->msg_discr = ABIS_RSL_MDISC_TRX;
+	ch->msg_type = RSL_MT_SACCH_FILL;
+
+	msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+	msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
+
+	msg->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+
+	db = abs(db);
+	if (db > 30)
+		return -EINVAL;
+
+	msg = rsl_msgb_alloc();
+
+	lchan->bs_power = db/2;
+	if (fpc)
+		lchan->bs_power |= 0x10;
+	
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_BS_POWER_CONTROL);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	int ctl_lvl;
+
+	ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm);
+	if (ctl_lvl < 0)
+		return ctl_lvl;
+
+	msg = rsl_msgb_alloc();
+
+	lchan->ms_power = ctl_lvl;
+
+	if (fpc)
+		lchan->ms_power |= 0x20;
+	
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm,
+				   struct gsm_lchan *lchan)
+{
+	memset(cm, 0, sizeof(cm));
+
+	/* FIXME: what to do with data calls ? */
+	cm->dtx_dtu = 0x00;
+
+	/* set TCH Speech/Data */
+	cm->spd_ind = lchan->rsl_cmode;
+
+	if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN &&
+	    lchan->tch_mode != GSM48_CMODE_SIGN)
+		LOGP(DRSL, LOGL_ERROR, "unsupported: rsl_mode == signalling, "
+			"but tch_mode != signalling\n");
+
+	switch (lchan->type) {
+	case GSM_LCHAN_SDCCH:
+		cm->chan_rt = RSL_CMOD_CRT_SDCCH;
+		break;
+	case GSM_LCHAN_TCH_F:
+		cm->chan_rt = RSL_CMOD_CRT_TCH_Bm;
+		break;
+	case GSM_LCHAN_TCH_H:
+		cm->chan_rt = RSL_CMOD_CRT_TCH_Lm;
+		break;
+	case GSM_LCHAN_NONE:
+	case GSM_LCHAN_UNKNOWN:
+	default:
+		return -EINVAL;
+	}
+
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SIGN:
+		cm->chan_rate = 0;
+		break;
+	case GSM48_CMODE_SPEECH_V1:
+		cm->chan_rate = RSL_CMOD_SP_GSM1;
+		break;
+	case GSM48_CMODE_SPEECH_EFR:
+		cm->chan_rate = RSL_CMOD_SP_GSM2;
+		break;
+	case GSM48_CMODE_SPEECH_AMR:
+		cm->chan_rate = RSL_CMOD_SP_GSM3;
+		break;
+	case GSM48_CMODE_DATA_14k5:
+		cm->chan_rate = RSL_CMOD_SP_NT_14k5;
+		break;
+	case GSM48_CMODE_DATA_12k0:
+		cm->chan_rate = RSL_CMOD_SP_NT_12k0;
+		break;
+	case GSM48_CMODE_DATA_6k0:
+		cm->chan_rate = RSL_CMOD_SP_NT_6k0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Chapter 8.4.1 */
+#if 0
+int rsl_chan_activate(struct gsm_bts_trx *trx, u_int8_t chan_nr,
+		      u_int8_t act_type,
+		      struct rsl_ie_chan_mode *chan_mode,
+		      struct rsl_ie_chan_ident *chan_ident,
+		      u_int8_t bs_power, u_int8_t ms_power,
+		      u_int8_t ta)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
+	/* For compatibility with Phase 1 */
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(*chan_mode),
+		     (u_int8_t *) chan_mode);
+	msgb_tlv_put(msg, RSL_IE_CHAN_IDENT, 4,
+		     (u_int8_t *) chan_ident);
+#if 0
+	msgb_tlv_put(msg, RSL_IE_ENCR_INFO, 1,
+		     (u_int8_t *) &encr_info);
+#endif
+	msgb_tv_put(msg, RSL_IE_BS_POWER, bs_power);
+	msgb_tv_put(msg, RSL_IE_MS_POWER, ms_power);
+	msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
+
+	msg->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+#endif
+
+int rsl_chan_activate_lchan(struct gsm_lchan *lchan, u_int8_t act_type,
+			    u_int8_t ta, u_int8_t ho_ref)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	u_int16_t arfcn = lchan->ts->trx->arfcn;
+	struct rsl_ie_chan_mode cm;
+	struct rsl_ie_chan_ident ci;
+
+	rc = channel_mode_from_lchan(&cm, lchan);
+	if (rc < 0)
+		return rc;
+
+	memset(&ci, 0, sizeof(ci));
+	ci.chan_desc.iei = 0x64;
+	ci.chan_desc.chan_nr = chan_nr;
+	ci.chan_desc.oct3 = (lchan->ts->trx->bts->tsc << 5) | ((arfcn & 0x3ff) >> 8);
+	ci.chan_desc.oct4 = arfcn & 0xff;
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+		     (u_int8_t *) &cm);
+	/* For compatibility with Phase 1 */
+	msgb_tlv_put(msg, RSL_IE_CHAN_IDENT, 4,
+		     (u_int8_t *) &ci);
+
+	if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		u_int8_t encr_info[MAX_A5_KEY_LEN+2];
+		rc = build_encr_info(encr_info, lchan);
+		if (rc > 0)
+			msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+	}
+
+	switch (act_type) {
+	case RSL_ACT_INTER_ASYNC:
+	case RSL_ACT_INTER_SYNC:
+		msgb_tv_put(msg, RSL_IE_HANDO_REF, ho_ref);
+		break;
+	default:
+		break;
+	}
+
+	msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
+	msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+	msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
+
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+		msgb_tlv_put(msg, RSL_IE_MR_CONFIG, sizeof(lchan->mr_conf),
+			     (u_int8_t *) &lchan->mr_conf);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.9: Modify channel mode on BTS side */
+int rsl_chan_mode_modify_req(struct gsm_lchan *lchan)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	struct rsl_ie_chan_mode cm;
+
+	rc = channel_mode_from_lchan(&cm, lchan);
+	if (rc < 0)
+		return rc;
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_MODE_MODIFY_REQ);
+	dh->chan_nr = chan_nr;
+
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+		     (u_int8_t *) &cm);
+
+	if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		u_int8_t encr_info[MAX_A5_KEY_LEN+2];
+		rc = build_encr_info(encr_info, lchan);
+		if (rc > 0)
+			msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+	}
+
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		msgb_tlv_put(msg, RSL_IE_MR_CONFIG, sizeof(lchan->mr_conf),
+			     (u_int8_t *) &lchan->mr_conf);
+	}
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.6: Send the encryption command with given L3 info */
+int rsl_encryption_cmd(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct gsm_lchan *lchan = msg->lchan;
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	u_int8_t encr_info[MAX_A5_KEY_LEN+2];
+	u_int8_t l3_len = msg->len;
+	int rc;
+
+	/* First push the L3 IE tag and length */
+	msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len);
+
+	/* then the link identifier (SAPI0, main sign link) */
+	msgb_tv_push(msg, RSL_IE_LINK_IDENT, 0);
+
+	/* then encryption information */
+	rc = build_encr_info(encr_info, lchan);
+	if (rc <= 0)
+		return rc;
+	msgb_tlv_push(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+
+	/* and finally the DCHAN header */
+	dh = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_ENCR_CMD);
+	dh->chan_nr = chan_nr;
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.5 / 4.6: Deactivate the SACCH after 04.08 RR CHAN RELEASE */
+int rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_DEACTIVATE_SACCH);
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	msg->lchan = lchan;
+	msg->trx = lchan->ts->trx;
+
+	DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan));
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */
+int rsl_rf_chan_release(struct gsm_lchan *lchan)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_RF_CHAN_REL);
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	msg->lchan = lchan;
+	msg->trx = lchan->ts->trx;
+
+	DEBUGP(DRSL, "%s RF Channel Release CMD\n", gsm_lchan_name(lchan));
+
+	/* BTS will respond by RF CHAN REL ACK */
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_paging_cmd(struct gsm_bts *bts, u_int8_t paging_group, u_int8_t len,
+		   u_int8_t *ms_ident, u_int8_t chan_needed)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_PAGING_CMD);
+	dh->chan_nr = RSL_CHAN_PCH_AGCH;
+
+	msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group);
+	msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2);
+	msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed);
+
+	msg->trx = bts->c0;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_paging_cmd_subscr(struct gsm_bts *bts, u_int8_t chan_need,
+			  struct gsm_subscriber *subscr)
+{
+#if 0
+	u_int8_t mi[128];
+	unsigned int mi_len;
+	u_int8_t paging_group;
+#endif
+
+	return -1;
+}
+
+int imsi_str2bcd(u_int8_t *bcd_out, const char *str_in)
+{
+	int i, len = strlen(str_in);
+
+	for (i = 0; i < len; i++) {
+		int num = str_in[i] - 0x30;
+		if (num < 0 || num > 9)
+			return -1;
+		if (i % 2 == 0)
+			bcd_out[i/2] = num;
+		else
+			bcd_out[i/2] |= (num << 4);
+	}
+
+	return 0;
+}
+
+/* Chapter 8.5.6 */
+int rsl_imm_assign_cmd(struct gsm_bts *bts, u_int8_t len, u_int8_t *val)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	u_int8_t buf[MACBLOCK_SIZE];
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IMMEDIATE_ASSIGN_CMD);
+	dh->chan_nr = RSL_CHAN_PCH_AGCH;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		msgb_tlv_put(msg, RSL_IE_IMM_ASS_INFO, len, val);
+		break;
+	default:
+		/* If phase 2, construct a FULL_IMM_ASS_INFO */
+		pad_macblock(buf, val, len);
+		msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, MACBLOCK_SIZE, buf);
+		break;
+	}
+
+	msg->trx = bts->c0;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Send Siemens specific MS RF Power Capability Indication */
+int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_SIEMENS_MRPCI);
+	dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+	dh->chan_nr = lchan2chan_nr(lchan);
+	msgb_tv_put(msg, RSL_IE_SIEMENS_MRPCI, *(u_int8_t *)mrpci);
+
+	DEBUGP(DRSL, "%s TX Siemens MRPCI 0x%02x\n",
+		gsm_lchan_name(lchan), *(u_int8_t *)mrpci);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+
+/* Send "DATA REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.1 */
+int rsl_data_request(struct msgb *msg, u_int8_t link_id)
+{
+	if (msg->lchan == NULL) {
+		LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n");
+		return -EINVAL;
+	}
+
+	rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, lchan2chan_nr(msg->lchan),
+			link_id, 1);
+
+	msg->trx = msg->lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Send "ESTABLISH REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.1 */
+int rsl_establish_request(struct gsm_lchan *lchan, u_int8_t link_id)
+{
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(RSL_MT_EST_REQ, lchan2chan_nr(lchan),
+			     link_id, 0);
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.3.7 Request the release of multiframe mode of RLL connection.
+   This is what higher layers should call.  The BTS then responds with
+   RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE,
+   which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls
+   lchan_free() */
+int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id)
+{
+
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(RSL_MT_REL_REQ, lchan2chan_nr(lchan),
+			     link_id, 0);
+	msgb_tv_put(msg, RSL_IE_RELEASE_MODE, 0);	/* normal release */
+
+	/* FIXME: start some timer in case we don't receive a REL ACK ? */
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int state)
+{
+	lchan->state = state;
+	return 0;
+}
+
+/* Chapter 8.4.2: Channel Activate Acknowledge */
+static int rsl_rx_chan_act_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+
+	/* BTS has confirmed channel activation, we now need
+	 * to assign the activated channel to the MS */
+	if (rslh->ie_chan != RSL_IE_CHAN_NR)
+		return -EINVAL;
+
+	if (msg->lchan->state != LCHAN_S_ACT_REQ)
+		LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n",
+			gsm_lchan_name(msg->lchan),
+			gsm_lchans_name(msg->lchan->state));
+	rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE);
+
+	dispatch_signal(SS_LCHAN, S_LCHAN_ACTIVATE_ACK, msg->lchan);
+
+	return 0;
+}
+
+/* Chapter 8.4.3: Channel Activate NACK */
+static int rsl_rx_chan_act_nack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	LOGP(DRSL, LOGL_ERROR, "%s CHANNEL ACTIVATE NACK",
+		gsm_lchan_name(msg->lchan));
+
+	/* BTS has rejected channel activation ?!? */
+	if (dh->ie_chan != RSL_IE_CHAN_NR)
+		return -EINVAL;
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) {
+		const u_int8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE);
+		print_rsl_cause(LOGL_ERROR, cause,
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+		if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC)
+			rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
+	} else
+		rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
+
+	LOGPC(DRSL, LOGL_ERROR, "\n");
+
+	dispatch_signal(SS_LCHAN, S_LCHAN_ACTIVATE_NACK, msg->lchan);
+
+	lchan_free(msg->lchan);
+	return 0;
+}
+
+/* Chapter 8.4.4: Connection Failure Indication */
+static int rsl_rx_conn_fail(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	/* FIXME: print which channel */
+	LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL: RELEASING ",
+	     gsm_lchan_name(msg->lchan));
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE),
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+
+	LOGPC(DRSL, LOGL_NOTICE, "\n");
+	/* FIXME: only free it after channel release ACK */
+	counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rf_fail);
+	return rsl_rf_chan_release(msg->lchan);
+}
+
+static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru,
+				const char *prefix)
+{
+	DEBUGPC(DMEAS, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ",
+		prefix, rxlev2dbm(mru->full.rx_lev),
+		prefix, rxlev2dbm(mru->sub.rx_lev));
+	DEBUGPC(DMEAS, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ",
+		prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual);
+}
+
+static void print_meas_rep(struct gsm_meas_rep *mr)
+{
+	int i;
+
+	DEBUGP(DMEAS, "MEASUREMENT RESULT NR=%d ", mr->nr);
+
+	if (mr->flags & MEAS_REP_F_DL_DTX)
+		DEBUGPC(DMEAS, "DTXd ");
+
+	print_meas_rep_uni(&mr->ul, "ul");
+	DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power);
+	if (mr->flags & MEAS_REP_F_MS_TO)
+		DEBUGPC(DMEAS, "MS_TO=%d ", mr->ms_timing_offset);
+
+	if (mr->flags & MEAS_REP_F_MS_L1) {
+		DEBUGPC(DMEAS, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr);
+		DEBUGPC(DMEAS, "L1_FPC=%u ",
+			mr->flags & MEAS_REP_F_FPC ? 1 : 0);
+		DEBUGPC(DMEAS, "L1_TA=%u ", mr->ms_l1.ta);
+	}
+
+	if (mr->flags & MEAS_REP_F_UL_DTX)
+		DEBUGPC(DMEAS, "DTXu ");
+	if (mr->flags & MEAS_REP_F_BA1)
+		DEBUGPC(DMEAS, "BA1 ");
+	if (!(mr->flags & MEAS_REP_F_DL_VALID))
+		DEBUGPC(DMEAS, "NOT VALID ");
+	else
+		print_meas_rep_uni(&mr->dl, "dl");
+
+	DEBUGPC(DMEAS, "NUM_NEIGH=%u\n", mr->num_cell);
+	if (mr->num_cell == 7)
+		return;
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+		DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u => %d dBm\n",
+			mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev));
+	}
+}
+
+static int rsl_rx_meas_res(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+	struct gsm_meas_rep *mr = lchan_next_meas_rep(msg->lchan);
+	u_int8_t len;
+	const u_int8_t *val;
+	int rc;
+
+	/* check if this channel is actually active */
+	/* FIXME: maybe this check should be way more generic/centralized */
+	if (msg->lchan->state != LCHAN_S_ACTIVE) {
+		LOGP(DRSL, LOGL_NOTICE, "%s: MEAS RES for inactive channel\n",
+			gsm_lchan_name(msg->lchan));
+		return 0;
+	}
+
+	memset(mr, 0, sizeof(*mr));
+	mr->lchan = msg->lchan;
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) ||
+	    !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) ||
+	    !TLVP_PRESENT(&tp, RSL_IE_BS_POWER))
+		return -EIO;
+
+	/* Mandatory Parts */
+	mr->nr = *TLVP_VAL(&tp, RSL_IE_MEAS_RES_NR);
+
+	len = TLVP_LEN(&tp, RSL_IE_UPLINK_MEAS);
+	val = TLVP_VAL(&tp, RSL_IE_UPLINK_MEAS);
+	if (len >= 3) {
+		if (val[0] & 0x40)
+			mr->flags |= MEAS_REP_F_DL_DTX;
+		mr->ul.full.rx_lev = val[0] & 0x3f;
+		mr->ul.sub.rx_lev = val[1] & 0x3f;
+		mr->ul.full.rx_qual = val[2]>>3 & 0x7;
+		mr->ul.sub.rx_qual = val[2] & 0x7;
+	}
+
+	mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER);
+
+	/* Optional Parts */
+	if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET))
+		mr->ms_timing_offset =
+			*TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET);
+
+	if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) {
+		val = TLVP_VAL(&tp, RSL_IE_L1_INFO);
+		mr->flags |= MEAS_REP_F_MS_L1;
+		mr->ms_l1.pwr = ms_pwr_dbm(msg->trx->bts->band, val[0] >> 3);
+		if (val[0] & 0x04)
+			mr->flags |= MEAS_REP_F_FPC;
+		mr->ms_l1.ta = val[1];
+	}
+	if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+		msg->l3h = (u_int8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO);
+		rc = gsm48_parse_meas_rep(mr, msg);
+		if (rc < 0)
+			return rc;
+	}
+
+	print_meas_rep(mr);
+
+	dispatch_signal(SS_LCHAN, S_LCHAN_MEAS_REP, mr);
+
+	return 0;
+}
+
+/* Chapter 8.4.7 */
+static int rsl_rx_hando_det(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	DEBUGP(DRSL, "%s HANDOVER DETECT ", gsm_lchan_name(msg->lchan));
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY))
+		DEBUGPC(DRSL, "access delay = %u\n",
+			*TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY));
+	else
+		DEBUGPC(DRSL, "\n");
+
+	dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_DETECT, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_dchan(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+	char *ts_name;
+
+	msg->lchan = lchan_lookup(msg->trx, rslh->chan_nr);
+	ts_name = gsm_lchan_name(msg->lchan);
+
+	switch (rslh->c.msg_type) {
+	case RSL_MT_CHAN_ACTIV_ACK:
+		DEBUGP(DRSL, "%s CHANNEL ACTIVATE ACK\n", ts_name);
+		rc = rsl_rx_chan_act_ack(msg);
+		break;
+	case RSL_MT_CHAN_ACTIV_NACK:
+		rc = rsl_rx_chan_act_nack(msg);
+		break;
+	case RSL_MT_CONN_FAIL:
+		rc = rsl_rx_conn_fail(msg);
+		break;
+	case RSL_MT_MEAS_RES:
+		rc = rsl_rx_meas_res(msg);
+		break;
+	case RSL_MT_HANDO_DET:
+		rc = rsl_rx_hando_det(msg);
+		break;
+	case RSL_MT_RF_CHAN_REL_ACK:
+		DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", ts_name);
+		if (msg->lchan->state != LCHAN_S_REL_REQ)
+			LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n",
+				gsm_lchan_name(msg->lchan),
+				gsm_lchans_name(msg->lchan->state));
+		rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
+		lchan_free(msg->lchan);
+		break;
+	case RSL_MT_MODE_MODIFY_ACK:
+		DEBUGP(DRSL, "%s CHANNEL MODE MODIFY ACK\n", ts_name);
+		break;
+	case RSL_MT_MODE_MODIFY_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s CHANNEL MODE MODIFY NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_PDCH_ACT_ACK:
+		DEBUGPC(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name);
+		msg->lchan->ts->flags |= TS_F_PDCH_MODE;
+		break;
+	case RSL_MT_IPAC_PDCH_ACT_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH ACT NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_PDCH_DEACT_ACK:
+		DEBUGP(DRSL, "%s IPAC PDCH DEACT ACK\n", ts_name);
+		msg->lchan->ts->flags &= ~TS_F_PDCH_MODE;
+		break;
+	case RSL_MT_IPAC_PDCH_DEACT_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH DEACT NACK\n", ts_name);
+		break;
+	case RSL_MT_PHY_CONTEXT_CONF:
+	case RSL_MT_PREPROC_MEAS_RES:
+	case RSL_MT_TALKER_DET:
+	case RSL_MT_LISTENER_DET:
+	case RSL_MT_REMOTE_CODEC_CONF_REP:
+	case RSL_MT_MR_CODEC_MOD_ACK:
+	case RSL_MT_MR_CODEC_MOD_NACK:
+	case RSL_MT_MR_CODEC_MOD_PER:
+		LOGP(DRSL, LOGL_NOTICE, "%s Unimplemented Abis RSL DChan "
+			"msg 0x%02x\n", ts_name, rslh->c.msg_type);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "%s unknown Abis RSL DChan msg 0x%02x\n",
+			ts_name, rslh->c.msg_type);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int rsl_rx_error_rep(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(msg->trx));
+
+	rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_ERROR, TLVP_VAL(&tp, RSL_IE_CAUSE),
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+
+	LOGPC(DRSL, LOGL_ERROR, "\n");
+
+	return 0;
+}
+
+static int abis_rsl_rx_trx(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+
+	switch (rslh->msg_type) {
+	case RSL_MT_ERROR_REPORT:
+		rc = rsl_rx_error_rep(msg);
+		break;
+	case RSL_MT_RF_RES_IND:
+		/* interference on idle channels of TRX */
+		//DEBUGP(DRSL, "%s RF Resource Indication\n", gsm_trx_name(msg->trx));
+		break;
+	case RSL_MT_OVERLOAD:
+		/* indicate CCCH / ACCH / processor overload */
+		LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n",
+		     gsm_trx_name(msg->trx));
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message "
+			"type 0x%02x\n", gsm_trx_name(msg->trx), rslh->msg_type);
+		return -EINVAL;
+	}
+	return rc;
+}
+
+/* If T3101 expires, we never received a response to IMMEDIATE ASSIGN */
+static void t3101_expired(void *data)
+{
+	struct gsm_lchan *lchan = data;
+
+	rsl_rf_chan_release(lchan);
+}
+
+/* MS has requested a channel on the RACH */
+static int rsl_rx_chan_rqd(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->trx->bts;
+	struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg);
+	struct gsm48_req_ref *rqd_ref;
+	struct gsm48_imm_ass ia;
+	enum gsm_chan_t lctype;
+	enum gsm_chreq_reason_t chreq_reason;
+	struct gsm_lchan *lchan;
+	u_int8_t rqd_ta;
+	int ret;
+
+	u_int16_t arfcn;
+	u_int8_t ts_number, subch;
+
+	/* parse request reference to be used in immediate assign */
+	if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE)
+		return -EINVAL;
+
+	rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1];
+
+	/* parse access delay and use as TA */
+	if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY)
+		return -EINVAL;
+	rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2];
+
+	/* determine channel type (SDCCH/TCH_F/TCH_H) based on
+	 * request reference RA */
+	lctype = get_ctype_by_chreq(bts, rqd_ref->ra, bts->network->neci);
+	chreq_reason = get_reason_by_chreq(bts, rqd_ref->ra, bts->network->neci);
+
+	counter_inc(bts->network->stats.chreq.total);
+
+	/* check availability / allocate channel */
+	lchan = lchan_alloc(bts, lctype);
+	if (!lchan) {
+		LOGP(DRSL, LOGL_NOTICE, "BTS %d CHAN RQD: no resources for %s 0x%x\n",
+		     msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra);
+		counter_inc(bts->network->stats.chreq.no_channel);
+		/* FIXME: send some kind of reject ?!? */
+		return -ENOMEM;
+	}
+
+	if (lchan->state != LCHAN_S_NONE)
+		LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel "
+		     "in state %s\n", gsm_lchan_name(lchan),
+		     gsm_lchans_name(lchan->state));
+	rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+	ts_number = lchan->ts->nr;
+	arfcn = lchan->ts->trx->arfcn;
+	subch = lchan->nr;
+	
+	lchan->encr.alg_id = RSL_ENC_ALG_A5(0);	/* no encryption */
+	lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+	lchan->bs_power = 0; /* 0dB reduction, output power = Pn */
+	lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
+	lchan->tch_mode = GSM48_CMODE_SIGN;
+	rsl_chan_activate_lchan(lchan, 0x00, rqd_ta, 0);
+
+	/* create IMMEDIATE ASSIGN 04.08 messge */
+	memset(&ia, 0, sizeof(ia));
+	ia.l2_plen = 0x2d;
+	ia.proto_discr = GSM48_PDISC_RR;
+	ia.msg_type = GSM48_MT_RR_IMM_ASS;
+	ia.page_mode = GSM48_PM_SAME;
+	ia.chan_desc.chan_nr = lchan2chan_nr(lchan);
+	ia.chan_desc.h0.h = 0;
+	ia.chan_desc.h0.arfcn_high = arfcn >> 8;
+	ia.chan_desc.h0.arfcn_low = arfcn & 0xff;
+	ia.chan_desc.h0.tsc = bts->tsc;
+	/* use request reference extracted from CHAN_RQD */
+	memcpy(&ia.req_ref, rqd_ref, sizeof(ia.req_ref));
+	ia.timing_advance = rqd_ta;
+	ia.mob_alloc_len = 0;
+
+	DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s "
+		"r=%s ra=0x%02x\n", gsm_lchan_name(lchan), arfcn, subch,
+		gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason),
+		rqd_ref->ra);
+
+	/* Start timer T3101 to wait for GSM48_MT_RR_PAG_RESP */
+	lchan->T3101.cb = t3101_expired;
+	lchan->T3101.data = lchan;
+	bsc_schedule_timer(&lchan->T3101, bts->network->T3101, 0);
+
+	/* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */
+	ret = rsl_imm_assign_cmd(bts, sizeof(ia), (u_int8_t *) &ia);
+
+	return ret;
+}
+
+/* MS has requested a channel on the RACH */
+static int rsl_rx_ccch_load(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	u_int16_t pg_buf_space;
+	u_int16_t rach_slot_count = -1;
+	u_int16_t rach_busy_count = -1;
+	u_int16_t rach_access_count = -1;
+
+	switch (rslh->data[0]) {
+	case RSL_IE_PAGING_LOAD:
+		pg_buf_space = rslh->data[1] << 8 | rslh->data[2];
+		if (is_ipaccess_bts(msg->trx->bts) && pg_buf_space == 0xffff) {
+			/* paging load below configured threshold, use 50 as default */
+			pg_buf_space = 50;
+		}
+		paging_update_buffer_space(msg->trx->bts, pg_buf_space);
+		break;
+	case RSL_IE_RACH_LOAD:
+		if (msg->data_len >= 7) {
+			rach_slot_count = rslh->data[2] << 8 | rslh->data[3];
+			rach_busy_count = rslh->data[4] << 8 | rslh->data[5];
+			rach_access_count = rslh->data[6] << 8 | rslh->data[7];
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int abis_rsl_rx_cchan(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+
+	msg->lchan = lchan_lookup(msg->trx, rslh->chan_nr);
+
+	switch (rslh->c.msg_type) {
+	case RSL_MT_CHAN_RQD:
+		/* MS has requested a channel on the RACH */
+		rc = rsl_rx_chan_rqd(msg);
+		break;
+	case RSL_MT_CCCH_LOAD_IND:
+		/* current load on the CCCH */
+		rc = rsl_rx_ccch_load(msg);
+		break;
+	case RSL_MT_DELETE_IND:
+		/* CCCH overloaded, IMM_ASSIGN was dropped */
+	case RSL_MT_CBCH_LOAD_IND:
+		/* current load on the CBCH */
+		LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message "
+			"type 0x%02x\n", rslh->c.msg_type);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "Unknown Abis RSL TRX message type "
+			"0x%02x\n", rslh->c.msg_type);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int rsl_rx_rll_err_ind(struct msgb *msg)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	u_int8_t *rlm_cause = rllh->data;
+
+	LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s\n",
+		gsm_lchan_name(msg->lchan),
+		rsl_rlm_cause_name(rlm_cause[1]));
+
+	rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND);
+
+	if (rlm_cause[1] == RLL_CAUSE_T200_EXPIRED) {
+		counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rll_err);
+		return rsl_rf_chan_release(msg->lchan);
+	}
+
+	return 0;
+}
+
+/*	ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST
+	0x02, 0x06,
+	0x01, 0x20,
+	0x02, 0x00,
+	0x0b, 0x00, 0x0f, 0x05, 0x08, ... */
+
+static int abis_rsl_rx_rll(struct msgb *msg)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	int rc = 0;
+	char *ts_name;
+	u_int8_t sapi = rllh->link_id & 7;
+
+	msg->lchan = lchan_lookup(msg->trx, rllh->chan_nr);
+	ts_name = gsm_lchan_name(msg->lchan);
+	DEBUGP(DRLL, "%s SAPI=%u ", ts_name, sapi);
+	
+	switch (rllh->c.msg_type) {
+	case RSL_MT_DATA_IND:
+		DEBUGPC(DRLL, "DATA INDICATION\n");
+		if (msgb_l2len(msg) >
+		    sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+		    rllh->data[0] == RSL_IE_L3_INFO) {
+			msg->l3h = &rllh->data[3];
+			return gsm0408_rcvmsg(msg, rllh->link_id);
+		}
+		break;
+	case RSL_MT_EST_IND:
+		DEBUGPC(DRLL, "ESTABLISH INDICATION\n");
+		/* lchan is established, stop T3101 */
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_MS;
+		bsc_del_timer(&msg->lchan->T3101);
+		if (msgb_l2len(msg) >
+		    sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+		    rllh->data[0] == RSL_IE_L3_INFO) {
+			msg->l3h = &rllh->data[3];
+			return gsm0408_rcvmsg(msg, rllh->link_id);
+		}
+		break;
+	case RSL_MT_EST_CONF:
+		DEBUGPC(DRLL, "ESTABLISH CONFIRM\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_NET;
+		rll_indication(msg->lchan, rllh->link_id,
+				  BSC_RLLR_IND_EST_CONF);
+		break;
+	case RSL_MT_REL_IND:
+		/* BTS informs us of having received  DISC from MS */
+		DEBUGPC(DRLL, "RELEASE INDICATION\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED;
+		rll_indication(msg->lchan, rllh->link_id,
+				  BSC_RLLR_IND_REL_IND);
+		/* we can now releae the channel on the BTS/Abis side */
+		/* FIXME: officially we need to start T3111 and wait for
+		 * some grace period */
+		rsl_rf_chan_release(msg->lchan);
+		break;
+	case RSL_MT_REL_CONF:
+		/* BTS informs us of having received UA from MS,
+		 * in response to DISC that we've sent earlier */
+		DEBUGPC(DRLL, "RELEASE CONFIRMATION\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED;
+		/* we can now releae the channel on the BTS/Abis side */
+		/* FIXME: officially we need to start T3111 and wait for
+		 * some grace period */
+		rsl_rf_chan_release(msg->lchan);
+		break;
+	case RSL_MT_ERROR_IND:
+		rc = rsl_rx_rll_err_ind(msg);
+		break;
+	case RSL_MT_UNIT_DATA_IND:
+		LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message "
+			"type 0x%02x\n", rllh->c.msg_type);
+		break;
+	default:
+		LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message "
+			"type 0x%02x\n", rllh->c.msg_type);
+	}
+	return rc;
+}
+
+static u_int8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan)
+{
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x00;
+		case GSM_LCHAN_TCH_H:
+			return 0x03;
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_EFR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x01;
+		/* there's no half-rate EFR */
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_AMR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x02;
+		case GSM_LCHAN_TCH_H:
+			return 0x05;
+		default:
+			break;
+		}
+	default:
+		break;
+	}
+	LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for "
+		"tch_mode == 0x%02x\n", lchan->tch_mode);
+	return 0;
+}
+
+static u_int8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan)
+{
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_GSM_FULL;
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_GSM_HALF;
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_EFR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_GSM_EFR;
+		/* there's no half-rate EFR */
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_AMR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_AMR_FULL;
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_AMR_HALF;
+		default:
+			break;
+		}
+	default:
+		break;
+	}
+	LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for "
+		"tch_mode == 0x%02x\n & lchan_type == %d",
+		lchan->tch_mode, lchan->type);
+	return 0;
+}
+
+/* ip.access specific RSL extensions */
+static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv)
+{
+	struct in_addr ip;
+	u_int16_t port, conn_id;
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) {
+		ip.s_addr = *((u_int32_t *) TLVP_VAL(tv, RSL_IE_IPAC_LOCAL_IP));
+		DEBUGPC(DRSL, "LOCAL_IP=%s ", inet_ntoa(ip));
+		lchan->abis_ip.bound_ip = ntohl(ip.s_addr);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_PORT)) {
+		port = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_LOCAL_PORT));
+		port = ntohs(port);
+		DEBUGPC(DRSL, "LOCAL_PORT=%u ", port);
+		lchan->abis_ip.bound_port = port;
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_CONN_ID)) {
+		conn_id = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_CONN_ID));
+		conn_id = ntohs(conn_id);
+		DEBUGPC(DRSL, "CON_ID=%u ", conn_id);
+		lchan->abis_ip.conn_id = conn_id;
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_RTP_PAYLOAD2)) {
+		lchan->abis_ip.rtp_payload2 =
+				*TLVP_VAL(tv, RSL_IE_IPAC_RTP_PAYLOAD2);
+		DEBUGPC(DRSL, "RTP_PAYLOAD2=0x%02x ",
+			lchan->abis_ip.rtp_payload2);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) {
+		lchan->abis_ip.speech_mode =
+				*TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE);
+		DEBUGPC(DRSL, "speech_mode=0x%02x ",
+			lchan->abis_ip.speech_mode);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) {
+		ip.s_addr = *((u_int32_t *) TLVP_VAL(tv, RSL_IE_IPAC_REMOTE_IP));
+		DEBUGPC(DRSL, "REMOTE_IP=%s ", inet_ntoa(ip));
+		lchan->abis_ip.connect_ip = ntohl(ip.s_addr);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_PORT)) {
+		port = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_REMOTE_PORT));
+		port = ntohs(port);
+		DEBUGPC(DRSL, "REMOTE_PORT=%u ", port);
+		lchan->abis_ip.connect_port = port;
+	}
+}
+
+int rsl_ipacc_crcx(struct gsm_lchan *lchan)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IPAC_CRCX);
+	dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	/* 0x1- == receive-only, 0x-1 == EFR codec */
+	lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan);
+	lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
+	msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+	msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
+
+	DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n",
+		gsm_lchan_name(lchan), lchan->abis_ip.speech_mode,
+		lchan->abis_ip.rtp_payload);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_ipacc_mdcx(struct gsm_lchan *lchan, u_int32_t ip, u_int16_t port,
+		   u_int8_t rtp_payload2)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	u_int32_t *att_ip;
+	struct in_addr ia;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IPAC_MDCX);
+	dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	/* we need to store these now as MDCX_ACK does not return them :( */
+	lchan->abis_ip.rtp_payload2 = rtp_payload2;
+	lchan->abis_ip.connect_port = port;
+	lchan->abis_ip.connect_ip = ip;
+
+	/* 0x0- == both directions, 0x-1 == EFR codec */
+	lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan);
+	lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d "
+		"CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan),
+		inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2,
+		lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode);
+
+	msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
+	msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP);
+	att_ip = (u_int32_t *) msgb_put(msg, sizeof(ip));
+	*att_ip = ia.s_addr;
+	msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port);
+	msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+	msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
+	if (rtp_payload2)
+		msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2);
+	
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* tell BTS to connect RTP stream to our local RTP socket */
+int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan)
+{
+	struct rtp_socket *rs = lchan->abis_ip.rtp_socket;
+	int rc;
+
+	rc = rsl_ipacc_mdcx(lchan, ntohl(rs->rtp.sin_local.sin_addr.s_addr),
+				ntohs(rs->rtp.sin_local.sin_port),
+			/* FIXME: use RTP payload of bound socket, not BTS*/
+				lchan->abis_ip.rtp_payload2);
+
+	return rc;
+}
+
+int rsl_ipacc_pdch_activate(struct gsm_lchan *lchan, int act)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	u_int8_t msg_type;
+
+	if (act)
+		msg_type = RSL_MT_IPAC_PDCH_ACT;
+	else
+		msg_type = RSL_MT_IPAC_PDCH_DEACT;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, msg_type);
+	dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	DEBUGP(DRSL, "%s IPAC_PDCH_%sACT\n", gsm_lchan_name(lchan),
+		act ? "" : "DE");
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	/* the BTS has acknowledged a local bind, it now tells us the IP
+	* address and port number to which it has bound the given logical
+	* channel */
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	if (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) ||
+	    !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) ||
+	    !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) {
+		LOGP(DRSL, LOGL_NOTICE, "mandatory IE missing");
+		return -EINVAL;
+	}
+
+	ipac_parse_rtp(lchan, &tv);
+
+	/* in case we don't use direct BTS-to-BTS RTP */
+	if (!ipacc_rtp_direct) {
+		int rc;
+		/* the BTS has successfully bound a TCH to a local ip/port,
+		 * which means we can connect our UDP socket to it */
+		if (lchan->abis_ip.rtp_socket) {
+			rtp_socket_free(lchan->abis_ip.rtp_socket);
+			lchan->abis_ip.rtp_socket = NULL;
+		}
+
+		lchan->abis_ip.rtp_socket = rtp_socket_create();
+		if (!lchan->abis_ip.rtp_socket)
+			goto out_err;
+
+		rc = rtp_socket_connect(lchan->abis_ip.rtp_socket,
+				   lchan->abis_ip.bound_ip,
+				   lchan->abis_ip.bound_port);
+		if (rc < 0)
+			goto out_err;
+	}
+
+	dispatch_signal(SS_ABISIP, S_ABISIP_CRCX_ACK, msg->lchan);
+
+	return 0;
+out_err:
+	return -EIO;
+}
+
+static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	/* the BTS has acknowledged a remote connect request and
+	 * it now tells us the IP address and port number to which it has
+	 * connected the given logical channel */
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	ipac_parse_rtp(lchan, &tv);
+	dispatch_signal(SS_ABISIP, S_ABISIP_MDCX_ACK, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc_dlcx_ind(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tv, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_DEBUG, TLVP_VAL(&tv, RSL_IE_CAUSE),
+				TLVP_LEN(&tv, RSL_IE_CAUSE));
+
+	/* the BTS tells us a RTP stream has been disconnected */
+	if (lchan->abis_ip.rtp_socket) {
+		rtp_socket_free(lchan->abis_ip.rtp_socket);
+		lchan->abis_ip.rtp_socket = NULL;
+	}
+
+	dispatch_signal(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc(struct msgb *msg)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	char *ts_name;
+	int rc = 0;
+
+	msg->lchan = lchan_lookup(msg->trx, rllh->chan_nr);
+	ts_name = gsm_lchan_name(msg->lchan);
+	
+	switch (rllh->c.msg_type) {
+	case RSL_MT_IPAC_CRCX_ACK:
+		DEBUGP(DRSL, "%s IPAC_CRCX_ACK ", ts_name);
+		rc = abis_rsl_rx_ipacc_crcx_ack(msg);
+		break;
+	case RSL_MT_IPAC_CRCX_NACK:
+		/* somehow the BTS was unable to bind the lchan to its local
+		 * port?!? */
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC_CRCX_NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_MDCX_ACK:
+		/* the BTS tells us that a connect operation was successful */
+		DEBUGP(DRSL, "%s IPAC_MDCX_ACK ", ts_name);
+		rc = abis_rsl_rx_ipacc_mdcx_ack(msg);
+		break;
+	case RSL_MT_IPAC_MDCX_NACK:
+		/* somehow the BTS was unable to connect the lchan to a remote
+		 * port */
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC_MDCX_NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_DLCX_IND:
+		DEBUGP(DRSL, "%s IPAC_DLCX_IND ", ts_name);
+		rc = abis_rsl_rx_ipacc_dlcx_ind(msg);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n",
+			rllh->c.msg_type);
+		break;
+	}
+	DEBUGPC(DRSL, "\n");
+
+	return rc;
+}
+
+
+/* Entry-point where L2 RSL from BTS enters */
+int abis_rsl_rcvmsg(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh;
+	int rc = 0;
+
+	if (!msg) {
+		DEBUGP(DRSL, "Empty RSL msg?..\n");
+		return -1;
+	}
+
+	if (msgb_l2len(msg) < sizeof(*rslh)) {
+		DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg));
+		return -1;
+	}
+
+	rslh = msgb_l2(msg);
+
+	switch (rslh->msg_discr & 0xfe) {
+	case ABIS_RSL_MDISC_RLL:
+		rc = abis_rsl_rx_rll(msg);
+		break;
+	case ABIS_RSL_MDISC_DED_CHAN:
+		rc = abis_rsl_rx_dchan(msg);
+		break;
+	case ABIS_RSL_MDISC_COM_CHAN:
+		rc = abis_rsl_rx_cchan(msg);
+		break;
+	case ABIS_RSL_MDISC_TRX:
+		rc = abis_rsl_rx_trx(msg);
+		break;
+	case ABIS_RSL_MDISC_LOC:
+		LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL msg disc 0x%02x\n",
+			rslh->msg_discr);
+		break;
+	case ABIS_RSL_MDISC_IPACCESS:
+		rc = abis_rsl_rx_ipacc(msg);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "unknown RSL message discriminator "
+			"0x%02x\n", rslh->msg_discr);
+		return -EINVAL;
+	}
+	msgb_free(msg);
+	return rc;
+}
+
+/* From Table 10.5.33 of GSM 04.08 */
+int rsl_number_of_paging_subchannels(struct gsm_bts *bts)
+{
+	if (bts->si_common.chan_desc.ccch_conf == RSL_BCCH_CCCH_CONF_1_C) {
+		return MAX(1, (3 - bts->si_common.chan_desc.bs_ag_blks_res))
+			* (bts->si_common.chan_desc.bs_pa_mfrms + 2);
+	} else {
+		return (9 - bts->si_common.chan_desc.bs_ag_blks_res)
+			* (bts->si_common.chan_desc.bs_pa_mfrms + 2);
+	}
+}
diff --git a/openbsc/src/bs11_config.c b/openbsc/src/bs11_config.c
new file mode 100644
index 0000000..a7493b4
--- /dev/null
+++ b/openbsc/src/bs11_config.c
@@ -0,0 +1,872 @@
+/* Siemens BS-11 microBTS configuration tool */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This software is based on ideas (but not code) of BS11Config
+ * (C) 2009 by Dieter Spaar <spaar@mirider.augusta.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <openbsc/debug.h>
+#include <osmocore/select.h>
+#include <openbsc/rs232.h>
+
+/* state of our bs11_config application */
+enum bs11cfg_state {
+	STATE_NONE,
+	STATE_LOGON_WAIT,
+	STATE_LOGON_ACK,
+	STATE_SWLOAD,
+	STATE_QUERY,
+};
+static enum bs11cfg_state bs11cfg_state = STATE_NONE;
+static char *command, *value;
+struct timer_list status_timer;
+
+static const u_int8_t obj_li_attr[] = {
+	NM_ATT_BS11_BIT_ERR_THESH, 0x09, 0x00,
+	NM_ATT_BS11_L1_PROT_TYPE, 0x00,
+	NM_ATT_BS11_LINE_CFG, 0x00,
+};
+static const u_int8_t obj_bbsig0_attr[] = {
+	NM_ATT_BS11_RSSI_OFFS, 0x02, 0x00, 0x00,
+	NM_ATT_BS11_DIVERSITY, 0x01, 0x00,
+};
+static const u_int8_t obj_pa0_attr[] = {
+	NM_ATT_BS11_TXPWR, 0x01, BS11_TRX_POWER_GSM_30mW,
+};
+static const char *trx1_password = "1111111111";
+#define TEI_OML	25
+
+static const u_int8_t too_fast[] = { 0x12, 0x80, 0x00, 0x00, 0x02, 0x02 };
+
+static struct log_target *stderr_target;
+
+/* dummy function to keep gsm_data.c happy */
+struct counter *counter_alloc(const char *name)
+{
+	return NULL;
+}
+
+int handle_serial_msg(struct msgb *rx_msg);
+
+/* create all objects for an initial configuration */
+static int create_objects(struct gsm_bts *bts)
+{
+	fprintf(stdout, "Crating Objects for minimal config\n");
+	abis_nm_bs11_create_object(bts, BS11_OBJ_LI, 0, sizeof(obj_li_attr),
+				   obj_li_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_GPSU, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_ALCO, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_CCLK, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 0,
+				   sizeof(obj_bbsig0_attr), obj_bbsig0_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 0,
+				   sizeof(obj_pa0_attr), obj_pa0_attr);
+	abis_nm_bs11_create_envaBTSE(bts, 0);
+	abis_nm_bs11_create_envaBTSE(bts, 1);
+	abis_nm_bs11_create_envaBTSE(bts, 2);
+	abis_nm_bs11_create_envaBTSE(bts, 3);
+
+	abis_nm_bs11_conn_oml_tei(bts, 0, 1, 0xff, TEI_OML);
+
+	abis_nm_bs11_set_trx_power(bts->c0, BS11_TRX_POWER_GSM_30mW);
+	
+	sleep(1);
+
+	abis_nm_bs11_set_trx1_pw(bts, trx1_password);
+
+	sleep(1);
+
+	return 0;
+}
+
+static int create_trx1(struct gsm_bts *bts)
+{
+	u_int8_t bbsig1_attr[sizeof(obj_bbsig0_attr)+12];
+	u_int8_t *cur = bbsig1_attr;
+	struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 1);
+
+	if (!trx)
+		trx = gsm_bts_trx_alloc(bts);
+
+	fprintf(stdout, "Crating Objects for TRX1\n");
+
+	abis_nm_bs11_set_trx1_pw(bts, trx1_password);
+
+	sleep(1);
+
+	cur = tlv_put(cur, NM_ATT_BS11_PASSWORD, 10,
+		      (u_int8_t *)trx1_password);
+	memcpy(cur, obj_bbsig0_attr, sizeof(obj_bbsig0_attr));
+	abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 1,
+				   sizeof(bbsig1_attr), bbsig1_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 1,
+				   sizeof(obj_pa0_attr), obj_pa0_attr);
+	abis_nm_bs11_set_trx_power(trx, BS11_TRX_POWER_GSM_30mW);
+	
+	return 0;
+}
+
+static char *serial_port = "/dev/ttyUSB0";
+static char *fname_safety = "BTSBMC76.SWI";
+static char *fname_software = "HS011106.SWL";
+static int delay_ms = 0;
+static int win_size = 8;
+static int param_disconnect = 0;
+static int param_restart = 0;
+static int param_forced = 0;
+static struct gsm_bts *g_bts;
+
+static int file_is_readable(const char *fname)
+{
+	int rc;
+	struct stat st;
+
+	rc = stat(fname, &st);
+	if (rc < 0)
+		return 0;
+
+	if (S_ISREG(st.st_mode) && (st.st_mode & S_IRUSR))
+		return 1;
+
+	return 0;
+}
+
+static int percent;
+static int percent_old;
+
+/* callback function passed to the ABIS OML code */
+static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *msg,
+		       void *data, void *param)
+{
+	if (hook != GSM_HOOK_NM_SWLOAD)
+		return 0;
+
+	switch (event) {
+	case NM_MT_LOAD_INIT_ACK:
+		fprintf(stdout, "Software Load Initiate ACK\n");
+		break;
+	case NM_MT_LOAD_INIT_NACK:
+		fprintf(stderr, "ERROR: Software Load Initiate NACK\n");
+		exit(5);
+		break;
+	case NM_MT_LOAD_END_ACK:
+		if (data) {
+			/* we did a safety load and must activate it */
+			abis_nm_software_activate(g_bts, fname_safety,
+						  swload_cbfn, g_bts);
+			sleep(5);
+		}
+		break;
+	case NM_MT_LOAD_END_NACK:
+		fprintf(stderr, "ERROR: Software Load End NACK\n");
+		exit(3);
+		break;
+	case NM_MT_ACTIVATE_SW_NACK:
+		fprintf(stderr, "ERROR: Activate Software NACK\n");
+		exit(4);
+		break;
+	case NM_MT_ACTIVATE_SW_ACK:
+		bs11cfg_state = STATE_NONE;
+		
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+		percent = abis_nm_software_load_status(g_bts);
+		if (percent > percent_old)
+			printf("Software Download Progress: %d%%\n", percent);
+		percent_old = percent;
+		break;
+	}
+	return 0;
+}
+
+static const char *bs11_link_state[] = {
+	[0x00]	= "Down",
+	[0x01]	= "Up",
+	[0x02]	= "Restoring",
+};
+
+static const char *linkstate_name(u_int8_t linkstate)
+{
+	if (linkstate > ARRAY_SIZE(bs11_link_state))
+		return "Unknown";
+
+	return bs11_link_state[linkstate];
+}
+
+static const char *mbccu_load[] = {
+	[0]	= "No Load",
+	[1]	= "Load BTSCAC",
+	[2]	= "Load BTSDRX",
+	[3]	= "Load BTSBBX",
+	[4]	= "Load BTSARC",
+	[5]	= "Load",
+};
+
+static const char *mbccu_load_name(u_int8_t linkstate)
+{
+	if (linkstate > ARRAY_SIZE(mbccu_load))
+		return "Unknown";
+
+	return mbccu_load[linkstate];
+}
+
+static const char *bts_phase_name(u_int8_t phase)
+{
+	switch (phase) {
+	case BS11_STATE_WARM_UP:
+	case BS11_STATE_WARM_UP_2:
+		return "Warm Up";
+		break;
+	case BS11_STATE_LOAD_SMU_SAFETY:
+		return "Load SMU Safety";
+		break;
+	case BS11_STATE_LOAD_SMU_INTENDED:
+		return "Load SMU Intended";
+		break;
+	case BS11_STATE_LOAD_MBCCU:
+		return "Load MBCCU";
+		break;
+	case BS11_STATE_SOFTWARE_RQD:
+		return "Software required";
+		break;
+	case BS11_STATE_WAIT_MIN_CFG:
+	case BS11_STATE_WAIT_MIN_CFG_2:
+		return "Wait minimal config";
+		break;
+	case BS11_STATE_MAINTENANCE:
+		return "Maintenance";
+		break;
+	case BS11_STATE_NORMAL:
+		return "Normal";
+		break;
+	case BS11_STATE_ABIS_LOAD:
+		return "Abis load";
+		break;
+	default:
+		return "Unknown";
+		break;
+	}
+}
+
+static const char *trx_power_name(u_int8_t pwr)
+{
+	switch (pwr) {
+	case BS11_TRX_POWER_GSM_2W:	
+		return "2W (GSM)";
+	case BS11_TRX_POWER_GSM_250mW:
+		return "250mW (GSM)";
+	case BS11_TRX_POWER_GSM_80mW:
+		return "80mW (GSM)";
+	case BS11_TRX_POWER_GSM_30mW:
+		return "30mW (GSM)";
+	case BS11_TRX_POWER_DCS_3W:
+		return "3W (DCS)";
+	case BS11_TRX_POWER_DCS_1W6:
+		return "1.6W (DCS)";
+	case BS11_TRX_POWER_DCS_500mW:
+		return "500mW (DCS)";
+	case BS11_TRX_POWER_DCS_160mW:
+		return "160mW (DCS)";
+	default:
+		return "unknown value";
+	}
+}
+
+static const char *pll_mode_name(u_int8_t mode)
+{
+	switch (mode) {
+	case BS11_LI_PLL_LOCKED:
+		return "E1 Locked";
+	case BS11_LI_PLL_STANDALONE:
+		return "Standalone";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *cclk_acc_name(u_int8_t acc)
+{
+	switch (acc) {
+	case 0:
+		/* Out of the demanded +/- 0.05ppm */
+		return "Medium";
+	case 1:
+		/* Synchronized with Abis, within demanded tolerance +/- 0.05ppm */
+		return "High";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *obj_name(struct abis_om_fom_hdr *foh)
+{
+	static char retbuf[256];
+
+	retbuf[0] = 0;
+
+	switch (foh->obj_class) {
+	case NM_OC_BS11:
+		strcat(retbuf, "BS11 ");
+		switch (foh->obj_inst.bts_nr) {
+		case BS11_OBJ_PA:
+			sprintf(retbuf+strlen(retbuf), "Power Amplifier %d ",
+				foh->obj_inst.ts_nr);
+			break;
+		case BS11_OBJ_LI:
+			sprintf(retbuf+strlen(retbuf), "Line Interface ");
+			break;
+		case BS11_OBJ_CCLK:
+			sprintf(retbuf+strlen(retbuf), "CCLK ");
+			break;
+		}
+		break;
+	case NM_OC_SITE_MANAGER:
+		strcat(retbuf, "SITE MANAGER ");
+		break;
+	}
+	return retbuf;
+}
+
+static void print_state(struct tlv_parsed *tp)
+{
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_BTS_STATE)) {
+		u_int8_t phase, mbccu;
+		if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 1) {
+			phase = *TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE);
+			printf("PHASE: %u %-20s ", phase & 0xf,
+				bts_phase_name(phase));
+		}
+		if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 2) {
+			mbccu = *(TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE)+1);
+			printf("MBCCU0: %-11s MBCCU1: %-11s ",
+				mbccu_load_name(mbccu & 0xf), mbccu_load_name(mbccu >> 4));
+		}
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_E1_STATE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_E1_STATE) >= 1) {
+		u_int8_t e1_state = *TLVP_VAL(tp, NM_ATT_BS11_E1_STATE);
+		printf("Abis-link: %-9s ", linkstate_name(e1_state & 0xf));
+	}
+	printf("\n");
+}
+
+static int print_attr(struct tlv_parsed *tp)
+{
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_PCB_SERIAL)) {
+		printf("\tBS-11 ESN PCB Serial Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_PCB_SERIAL));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_HW_CODE_NO)) {
+		printf("\tBS-11 ESN Hardware Code Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_HW_CODE_NO)+6);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_FW_CODE_NO)) {
+		printf("\tBS-11 ESN Firmware Code Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_FW_CODE_NO)+6);
+	}
+#if 0
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_BOOT_SW_VERS)) {
+		printf("BS-11 Boot Software Version: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_BOOT_SW_VERS)+6);
+	}
+#endif
+	if (TLVP_PRESENT(tp, NM_ATT_ABIS_CHANNEL) &&
+	    TLVP_LEN(tp, NM_ATT_ABIS_CHANNEL) >= 3) {
+		const u_int8_t *chan = TLVP_VAL(tp, NM_ATT_ABIS_CHANNEL);
+		printf("\tE1 Channel: Port=%u Timeslot=%u ",
+			chan[0], chan[1]);
+		if (chan[2] == 0xff)
+			printf("(Full Slot)\n");
+		else
+			printf("Subslot=%u\n", chan[2]);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_TEI))
+		printf("\tTEI: %d\n", *TLVP_VAL(tp, NM_ATT_TEI));
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_TXPWR) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_TXPWR) >= 1) {
+		printf("\tTRX Power: %s\n",
+			trx_power_name(*TLVP_VAL(tp, NM_ATT_BS11_TXPWR)));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL_MODE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_PLL_MODE) >= 1) {
+		printf("\tPLL Mode: %s\n",
+			pll_mode_name(*TLVP_VAL(tp, NM_ATT_BS11_PLL_MODE)));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_PLL) >= 4) {
+		const u_int8_t *vp = TLVP_VAL(tp, NM_ATT_BS11_PLL);
+		printf("\tPLL Set Value=%d, Work Value=%d\n",
+			vp[0] << 8 | vp[1], vp[2] << 8 | vp[3]);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_ACCURACY) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_CCLK_ACCURACY) >= 1) {
+		const u_int8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_ACCURACY);
+		printf("\tCCLK Accuracy: %s (%d)\n", cclk_acc_name(*acc), *acc);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_TYPE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_CCLK_TYPE) >= 1) {
+		const u_int8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_TYPE);
+		printf("\tCCLK Type=%d\n", *acc);
+	}
+
+
+	return 0;
+}
+
+static void cmd_query(void)
+{
+	struct gsm_bts_trx *trx = g_bts->c0;
+
+	bs11cfg_state = STATE_QUERY;
+	abis_nm_bs11_get_serno(g_bts);
+	abis_nm_bs11_get_oml_tei_ts(g_bts);
+	abis_nm_bs11_get_pll_mode(g_bts);
+	abis_nm_bs11_get_cclk(g_bts);
+	abis_nm_bs11_get_trx_power(trx);
+	trx = gsm_bts_trx_num(g_bts, 1);
+	if (trx)
+		abis_nm_bs11_get_trx_power(trx);
+	sleep(1);
+	abis_nm_bs11_factory_logon(g_bts, 0);
+	command = NULL;
+}
+
+/* handle a response from the BTS to a GET STATE command */
+static int handle_state_resp(enum abis_bs11_phase state)
+{
+	int rc = 0;
+
+	switch (state) {
+	case BS11_STATE_WARM_UP:
+	case BS11_STATE_LOAD_SMU_SAFETY:
+	case BS11_STATE_LOAD_SMU_INTENDED:
+	case BS11_STATE_LOAD_MBCCU:
+		break;
+	case BS11_STATE_SOFTWARE_RQD:
+		bs11cfg_state = STATE_SWLOAD;
+		/* send safety load. Use g_bts as private 'param'
+		 * argument, so our swload_cbfn can distinguish
+		 * a safety load from a regular software */
+		if (file_is_readable(fname_safety))
+			rc = abis_nm_software_load(g_bts, fname_safety,
+						   win_size, param_forced,
+						   swload_cbfn, g_bts);
+		else
+			fprintf(stderr, "No valid Safety Load file \"%s\"\n",
+				fname_safety);
+		break;
+	case BS11_STATE_WAIT_MIN_CFG:
+	case BS11_STATE_WAIT_MIN_CFG_2:
+		bs11cfg_state = STATE_SWLOAD;
+		rc = create_objects(g_bts);
+		break;
+	case BS11_STATE_MAINTENANCE:
+		if (command) {
+			if (!strcmp(command, "disconnect"))
+				abis_nm_bs11_factory_logon(g_bts, 0);
+			else if (!strcmp(command, "reconnect"))
+				rc = abis_nm_bs11_bsc_disconnect(g_bts, 1);
+			else if (!strcmp(command, "software")
+			    && bs11cfg_state != STATE_SWLOAD) {
+				bs11cfg_state = STATE_SWLOAD;
+				/* send software (FIXME: over A-bis?) */
+				if (file_is_readable(fname_software))
+					rc = abis_nm_bs11_load_swl(g_bts, fname_software,
+								   win_size, param_forced,
+								   swload_cbfn);
+				else
+					fprintf(stderr, "No valid Software file \"%s\"\n",
+						fname_software);
+			} else if (!strcmp(command, "delete-trx1")) {
+				printf("Locing BBSIG and PA objects of TRX1\n");
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11,
+						      BS11_OBJ_BBSIG, 0, 1,
+						      NM_STATE_LOCKED);
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11,
+						      BS11_OBJ_PA, 0, 1,
+						      NM_STATE_LOCKED);
+				sleep(1);
+				printf("Deleting BBSIG and PA objects of TRX1\n");
+				abis_nm_bs11_delete_object(g_bts, BS11_OBJ_BBSIG, 1);
+				abis_nm_bs11_delete_object(g_bts, BS11_OBJ_PA, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "create-trx1")) {
+				create_trx1(g_bts);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-e1-locked")) {
+				abis_nm_bs11_set_pll_locked(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-standalone")) {
+				abis_nm_bs11_set_pll_locked(g_bts, 0);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-setvalue")) {
+				abis_nm_bs11_set_pll(g_bts, atoi(value));
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-workvalue")) {
+				/* To set the work value we need to login as FIELD */
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				sleep(1);
+				abis_nm_bs11_infield_logon(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_set_pll(g_bts, atoi(value));
+				sleep(1);
+				abis_nm_bs11_infield_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "oml-tei")) {
+				abis_nm_bs11_conn_oml_tei(g_bts, 0, 1, 0xff, TEI_OML);
+				command = NULL;
+			} else if (!strcmp(command, "restart")) {
+				abis_nm_bs11_restart(g_bts);
+				command = NULL;
+			} else if (!strcmp(command, "query")) {
+				cmd_query();
+			} else if (!strcmp(command, "create-bport1")) {
+				abis_nm_bs11_create_bport(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "delete-bport1")) {
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11_BPORT, 1, 0xff, 0xff, NM_STATE_LOCKED);
+				sleep(1);
+				abis_nm_bs11_delete_bport(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "bport0-star")) {
+				abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_STAR);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "bport0-multidrop")) {
+				abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_MULTIDROP);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			}
+		}
+		break;
+	case BS11_STATE_NORMAL:
+		if (command) {
+			if (!strcmp(command, "reconnect"))
+				abis_nm_bs11_factory_logon(g_bts, 0);
+			else if (!strcmp(command, "disconnect"))
+				abis_nm_bs11_bsc_disconnect(g_bts, 0);
+			else if (!strcmp(command, "query")) {
+				cmd_query();
+			}
+		} else if (param_disconnect) {
+			param_disconnect = 0;
+			abis_nm_bs11_bsc_disconnect(g_bts, 0);
+			if (param_restart) {
+				param_restart = 0;
+				abis_nm_bs11_restart(g_bts);
+			}
+		}
+		break;
+	default:
+		break;
+	}
+	return rc;
+}
+
+/* handle a fully-received message/packet from the RS232 port */
+int handle_serial_msg(struct msgb *rx_msg)
+{
+	struct abis_om_hdr *oh;
+	struct abis_om_fom_hdr *foh;
+	struct tlv_parsed tp;
+	int rc = -1;
+
+#if 0
+	if (rx_msg->len < LAPD_HDR_LEN
+			  + sizeof(struct abis_om_fom_hdr)
+			  + sizeof(struct abis_om_hdr)) {
+		if (!memcmp(rx_msg->data + 2, too_fast,
+			    sizeof(too_fast))) {
+			fprintf(stderr, "BS11 tells us we're too "
+				"fast, try --delay bigger than %u\n",
+				delay_ms);
+			return -E2BIG;
+		} else
+			fprintf(stderr, "unknown BS11 message\n");
+	}
+#endif
+
+	oh = (struct abis_om_hdr *) msgb_l2(rx_msg);
+	foh = (struct abis_om_fom_hdr *) oh->data;
+	switch (foh->msg_type) {
+	case NM_MT_BS11_LMT_LOGON_ACK:
+		printf("LMT LOGON: ACK\n\n");
+		if (bs11cfg_state == STATE_NONE)
+			bs11cfg_state = STATE_LOGON_ACK;
+		rc = abis_nm_bs11_get_state(g_bts);
+		break;
+	case NM_MT_BS11_LMT_LOGOFF_ACK:
+		printf("LMT LOGOFF: ACK\n");
+		exit(0);
+		break;
+	case NM_MT_BS11_GET_STATE_ACK:
+		rc = abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh));
+		print_state(&tp);
+		if (TLVP_PRESENT(&tp, NM_ATT_BS11_BTS_STATE) &&
+		    TLVP_LEN(&tp, NM_ATT_BS11_BTS_STATE) >= 1)
+			rc = handle_state_resp(*TLVP_VAL(&tp, NM_ATT_BS11_BTS_STATE));
+		break;
+	case NM_MT_GET_ATTR_RESP:
+		printf("\n%sATTRIBUTES:\n", obj_name(foh));
+		abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh));
+		rc = print_attr(&tp);
+		//hexdump(foh->data, oh->length-sizeof(*foh));
+		break;
+	case NM_MT_BS11_SET_ATTR_ACK:
+		printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) ACK\n",
+			foh->obj_class, foh->obj_inst.bts_nr,
+			foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
+		rc = 0;
+		break;
+	case NM_MT_BS11_SET_ATTR_NACK:
+		printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) NACK\n",
+			foh->obj_class, foh->obj_inst.bts_nr,
+			foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
+		break;
+	default:
+		rc = abis_nm_rcvmsg(rx_msg);
+	}
+	if (rc < 0) {
+		perror("ERROR in main loop");
+		//break;
+	}
+	if (rc == 1)
+		return rc;
+
+	switch (bs11cfg_state) {
+	case STATE_NONE:
+		abis_nm_bs11_factory_logon(g_bts, 1);
+		break;
+	case STATE_LOGON_ACK:
+		bsc_schedule_timer(&status_timer, 5, 0);
+		break;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
+		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state)
+{
+	return 0;
+}
+
+void status_timer_cb(void *data)
+{
+	abis_nm_bs11_get_state(g_bts);
+}
+
+static void print_banner(void)
+{
+	printf("bs11_config (C) 2009 by Harald Welte and Dieter Spaar\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+}
+
+static void print_help(void)
+{
+	printf("bs11_config [options] [command]\n");
+	printf("\nSupported options:\n");
+	printf("\t-h --help\t\t\tPrint this help text\n");
+	printf("\t-p --port </dev/ttyXXX>\t\tSpecify serial port\n");
+	printf("\t-s --software <file>\t\tSpecify Software file\n");
+	printf("\t-S --safety <file>\t\tSpecify Safety Load file\n");
+	printf("\t-d --delay <ms>\t\t\tSpecify delay in milliseconds\n");
+	printf("\t-D --disconnect\t\t\tDisconnect BTS from BSC\n");
+	printf("\t-w --win-size <num>\t\tSpecify Window Size\n");
+	printf("\t-f --forced\t\t\tForce Software Load\n");
+	printf("\nSupported commands:\n");
+	printf("\tquery\t\t\tQuery the BS-11 about serial number and configuration\n");
+	printf("\tdisconnect\t\tDisconnect A-bis link (go into administrative state)\n");
+	printf("\tresconnect\t\tReconnect A-bis link (go into normal state)\n");
+	printf("\trestart\t\t\tRestart the BTS\n");
+	printf("\tsoftware\t\tDownload Software (only in administrative state)\n");
+	printf("\tcreate-trx1\t\tCreate objects for TRX1 (Danger: Your BS-11 might overheat)\n");
+	printf("\tdelete-trx1\t\tDelete objects for TRX1\n");
+	printf("\tpll-e1-locked\t\tSet the PLL to be locked to E1 clock\n");
+	printf("\tpll-standalone\t\tSet the PLL to be in standalone mode\n");
+	printf("\tpll-setvalue <value>\tSet the PLL set value\n");
+	printf("\tpll-workvalue <value>\tSet the PLL work value\n");
+	printf("\toml-tei\t\t\tSet OML E1 TS and TEI\n");
+	printf("\tbport0-star\t\tSet BPORT0 line config to star\n");
+	printf("\tbport0-multiport\tSet BPORT0 line config to multiport\n");
+	printf("\tcreate-bport1\t\tCreate BPORT1 object\n");
+	printf("\tdelete-bport1\t\tDelete BPORT1 object\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	int option_index = 0;
+	print_banner();
+
+	while (1) {
+		int c;
+		static struct option long_options[] = {
+			{ "help", 0, 0, 'h' },
+			{ "port", 1, 0, 'p' },
+			{ "software", 1, 0, 's' },
+			{ "safety", 1, 0, 'S' },
+			{ "delay", 1, 0, 'd' },
+			{ "disconnect", 0, 0, 'D' },
+			{ "win-size", 1, 0, 'w' },
+			{ "forced", 0, 0, 'f' },
+			{ "restart", 0, 0, 'r' },
+			{ "debug", 1, 0, 'b'},
+		};
+
+		c = getopt_long(argc, argv, "hp:s:S:td:Dw:fra:",
+				long_options, &option_index);
+
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_help();
+			exit(0);
+		case 'p':
+			serial_port = optarg;
+			break;
+		case 'b':
+			log_parse_category_mask(stderr_target, optarg);
+			break;
+		case 's':
+			fname_software = optarg;
+			break;
+		case 'S':
+			fname_safety = optarg;
+			break;
+		case 'd':
+			delay_ms = atoi(optarg);
+			break;
+		case 'w':
+			win_size = atoi(optarg);
+			break;
+		case 'D':
+			param_disconnect = 1;
+			break;
+		case 'f':
+			param_forced = 1;
+			break;
+		case 'r':
+			param_disconnect = 1;
+			param_restart = 1;
+			break;
+		default:
+			break;
+		}
+	}
+	if (optind < argc)
+		command = argv[optind];
+	        if (optind+1 < argc)
+			value = argv[optind+1];
+
+}
+
+static int num_sigint;
+
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "\nsignal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+		num_sigint++;
+		abis_nm_bs11_factory_logon(g_bts, 0);
+		if (num_sigint >= 3)
+			exit(0);
+		break;
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct gsm_network *gsmnet;
+	int rc;
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+	handle_options(argc, argv);
+	bts_model_bs11_init();
+
+	gsmnet = gsm_network_init(1, 1, NULL);
+	if (!gsmnet) {
+		fprintf(stderr, "Unable to allocate gsm network\n");
+		exit(1);
+	}
+	g_bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_BS11, HARDCODED_TSC,
+				HARDCODED_BSIC);
+
+	rc = rs232_setup(serial_port, delay_ms, g_bts);
+	if (rc < 0) {
+		fprintf(stderr, "Problem setting up serial port\n");
+		exit(1);
+	}
+
+	signal(SIGINT, &signal_handler);
+
+	abis_nm_bs11_factory_logon(g_bts, 1);
+	//abis_nm_bs11_get_serno(g_bts);
+
+	status_timer.cb = status_timer_cb;
+
+	while (1) {
+		bsc_select_main(0);
+	}
+
+	abis_nm_bs11_factory_logon(g_bts, 0);
+
+	exit(0);
+}
diff --git a/openbsc/src/bsc_api.c b/openbsc/src/bsc_api.c
new file mode 100644
index 0000000..b504752
--- /dev/null
+++ b/openbsc/src/bsc_api.c
@@ -0,0 +1,33 @@
+/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */
+
+/* (C) 2010 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/bsc_api.h>
+#include <openbsc/abis_rsl.h>
+
+
+int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn,
+			struct msgb *msg, int link_id)
+{
+	msg->lchan = conn->lchan;
+	msg->trx = msg->lchan->ts->trx;
+	return rsl_data_request(msg, link_id);
+}
diff --git a/openbsc/src/bsc_hack.c b/openbsc/src/bsc_hack.c
new file mode 100644
index 0000000..a50d4ab
--- /dev/null
+++ b/openbsc/src/bsc_hack.c
@@ -0,0 +1,268 @@
+/* A hackish minimal BSC (+MSC +HLR) implementation */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <openbsc/db.h>
+#include <osmocore/select.h>
+#include <openbsc/debug.h>
+#include <openbsc/e1_input.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+
+/* MCC and MNC for the Location Area Identifier */
+static struct log_target *stderr_target;
+struct gsm_network *bsc_gsmnet = 0;
+static const char *database_name = "hlr.sqlite3";
+static const char *config_file = "openbsc.cfg";
+extern const char *openbsc_version;
+extern const char *openbsc_copyright;
+
+/* timer to store statistics */
+#define DB_SYNC_INTERVAL	60, 0
+static struct timer_list db_sync_timer;
+
+extern int bsc_bootstrap_network(int (*mmc_rev)(struct gsm_network *, int, void *),
+				 const char *cfg_file);
+extern int bsc_shutdown_net(struct gsm_network *net);
+
+static void create_pcap_file(char *file)
+{
+	mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+	int fd = open(file, O_WRONLY|O_TRUNC|O_CREAT, mode);
+
+	if (fd < 0) {
+		perror("Failed to open file for pcap");
+		return;
+	}
+
+	e1_set_pcap_fd(fd);
+}
+
+static void print_usage()
+{
+	printf("Usage: bsc_hack\n");
+}
+
+static void print_help()
+{
+	printf("  Some useful help...\n");
+	printf("  -h --help this text\n");
+	printf("  -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n");
+	printf("  -c --config-file filename The config file to use.\n");
+	printf("  -s --disable-color\n");
+	printf("  -l --database db-name The database to use\n");
+	printf("  -a --authorize-everyone. Authorize every new subscriber. Dangerous!.\n");
+	printf("  -p --pcap file  The filename of the pcap file\n");
+	printf("  -T --timestamp Prefix every log line with a timestamp\n");
+	printf("  -V --version. Print the version of OpenBSC.\n");
+	printf("  -P --rtp-proxy Enable the RTP Proxy code inside OpenBSC\n");
+	printf("  -e --log-level number. Set a global loglevel.\n");
+}
+
+static void print_version()
+{
+	printf("%s\n", openbsc_version);
+}
+
+static void print_copyright()
+{
+	puts(openbsc_copyright);
+}
+
+static void handle_options(int argc, char** argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"debug", 1, 0, 'd'},
+			{"config-file", 1, 0, 'c'},
+			{"disable-color", 0, 0, 's'},
+			{"database", 1, 0, 'l'},
+			{"authorize-everyone", 0, 0, 'a'},
+			{"pcap", 1, 0, 'p'},
+			{"timestamp", 0, 0, 'T'},
+			{"version", 0, 0, 'V' },
+			{"rtp-proxy", 0, 0, 'P'},
+			{"log-level", 1, 0, 'e'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hd:sl:ar:p:TPVc:e:",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(stderr_target, optarg);
+			break;
+		case 'l':
+			database_name = strdup(optarg);
+			break;
+		case 'c':
+			config_file = strdup(optarg);
+			break;
+		case 'p':
+			create_pcap_file(optarg);
+			break;
+		case 'T':
+			log_set_print_timestamp(stderr_target, 1);
+			break;
+		case 'P':
+			ipacc_rtp_direct = 0;
+			break;
+		case 'e':
+			log_set_log_level(stderr_target, atoi(optarg));
+			break;
+		case 'V':
+			print_version();
+			printf("\n");
+			print_copyright();
+			exit(0);
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+}
+
+extern void *tall_vty_ctx;
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+		bsc_shutdown_net(bsc_gsmnet);
+		dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
+		sleep(3);
+		exit(0);
+		break;
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report(tall_vty_ctx, stderr);
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	case SIGUSR2:
+		talloc_report_full(tall_vty_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+/* timer handling */
+static int _db_store_counter(struct counter *counter, void *data)
+{
+	return db_store_counter(counter);
+}
+
+static void db_sync_timer_cb(void *data)
+{
+	/* store counters to database and re-schedule */
+	counters_for_each(_db_store_counter, NULL);
+	bsc_schedule_timer(&db_sync_timer, DB_SYNC_INTERVAL);
+}
+
+extern int bts_model_unknown_init(void);
+extern int bts_model_bs11_init(void);
+extern int bts_model_nanobts_init(void);
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	log_init(&log_info);
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc");
+	talloc_ctx_init();
+	on_dso_load_token();
+	on_dso_load_rrlp();
+	on_dso_load_ho_dec();
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+
+	bts_model_unknown_init();
+	bts_model_bs11_init();
+	bts_model_nanobts_init();
+
+	/* enable filters */
+	log_set_all_filter(stderr_target, 1);
+
+	/* parse options */
+	handle_options(argc, argv);
+
+	/* seed the PRNG */
+	srand(time(NULL));
+
+	if (db_init(database_name)) {
+		printf("DB: Failed to init database. Please check the option settings.\n");
+		return -1;
+	}
+	printf("DB: Database initialized.\n");
+
+	if (db_prepare()) {
+		printf("DB: Failed to prepare database.\n");
+		return -1;
+	}
+	printf("DB: Database prepared.\n");
+
+	/* setup the timer */
+	db_sync_timer.cb = db_sync_timer_cb;
+	db_sync_timer.data = NULL;
+	bsc_schedule_timer(&db_sync_timer, DB_SYNC_INTERVAL);
+
+	rc = bsc_bootstrap_network(mncc_recv, config_file);
+	if (rc < 0)
+		exit(1);
+
+	signal(SIGINT, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGUSR2, &signal_handler);
+	signal(SIGPIPE, SIG_IGN);
+
+	while (1) {
+		bsc_upqueue(bsc_gsmnet);
+		log_reset_context();
+		bsc_select_main(0);
+	}
+}
diff --git a/openbsc/src/bsc_init.c b/openbsc/src/bsc_init.c
new file mode 100644
index 0000000..9bc02c4
--- /dev/null
+++ b/openbsc/src/bsc_init.c
@@ -0,0 +1,1093 @@
+/* A hackish minimal BSC (+MSC +HLR) implementation */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/debug.h>
+#include <openbsc/misdn.h>
+#include <openbsc/telnet_interface.h>
+#include <openbsc/system_information.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/chan_alloc.h>
+#include <osmocore/talloc.h>
+
+/* global pointer to the gsm network data structure */
+extern struct gsm_network *bsc_gsmnet;
+
+static void patch_nm_tables(struct gsm_bts *bts);
+
+/* The following definitions are for OM and NM packets that we cannot yet
+ * generate by code but we just pass on */
+
+// BTS Site Manager, SET ATTRIBUTES
+
+/*
+  Object Class: BTS Site Manager
+  Instance 1: FF
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  sAbisExternalTime: 2007/09/08   14:36:11
+  omLAPDRelTimer: 30sec
+  shortLAPDIntTimer: 5sec
+  emergencyTimer1: 10 minutes
+  emergencyTimer2: 0 minutes
+*/
+
+unsigned char msg_1[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, 0xFF, 0xFF, 0xFF,
+		NM_ATT_BS11_ABIS_EXT_TIME, 0x07,
+			0xD7, 0x09, 0x08, 0x0E, 0x24, 0x0B, 0xCE,
+		0x02,
+			0x00, 0x1E,
+		NM_ATT_BS11_SH_LAPD_INT_TIMER,
+			0x01, 0x05,
+		0x42, 0x02, 0x00, 0x0A,
+		0x44, 0x02, 0x00, 0x00
+};
+
+// BTS, SET BTS ATTRIBUTES
+
+/*
+  Object Class: BTS
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET BTS ATTRIBUTES
+  bsIdentityCode / BSIC:
+    PLMN_colour_code: 7h
+    BS_colour_code:   7h
+  BTS Air Timer T3105: 4  ,unit 10 ms
+  btsIsHopping: FALSE
+  periodCCCHLoadIndication: 1sec
+  thresholdCCCHLoadIndication: 0%
+  cellAllocationNumber: 00h = GSM 900
+  enableInterferenceClass: 00h =  Disabled
+  fACCHQual: 6 (FACCH stealing flags minus 1)
+  intaveParameter: 31 SACCH multiframes
+  interferenceLevelBoundaries:
+    Interference Boundary 1: 0Ah
+    Interference Boundary 2: 0Fh
+    Interference Boundary 3: 14h
+    Interference Boundary 4: 19h
+    Interference Boundary 5: 1Eh
+  mSTxPwrMax: 11
+      GSM range:     2=39dBm, 15=13dBm, stepsize 2 dBm
+      DCS1800 range: 0=30dBm, 15=0dBm, stepsize 2 dBm
+      PCS1900 range: 0=30dBm, 15=0dBm, stepsize 2 dBm
+                    30=33dBm, 31=32dBm
+  ny1:
+    Maximum number of repetitions for PHYSICAL INFORMATION message (GSM 04.08): 20
+  powerOutputThresholds:
+    Out Power Fault Threshold:     -10 dB
+    Red Out Power Threshold:       - 6 dB
+    Excessive Out Power Threshold:   5 dB
+  rACHBusyThreshold: -127 dBm
+  rACHLoadAveragingSlots: 250 ,number of RACH burst periods
+  rfResourceIndicationPeriod: 125  SACCH multiframes
+  T200:
+    SDCCH:                044 in  5 ms
+    FACCH/Full rate:      031 in  5 ms
+    FACCH/Half rate:      041 in  5 ms
+    SACCH with TCH SAPI0: 090 in 10 ms
+    SACCH with SDCCH:     090 in 10 ms
+    SDCCH with SAPI3:     090 in  5 ms
+    SACCH with TCH SAPI3: 135 in 10 ms
+  tSync: 9000 units of 10 msec
+  tTrau: 9000 units of 10 msec
+  enableUmLoopTest: 00h =  disabled
+  enableExcessiveDistance: 00h =  Disabled
+  excessiveDistance: 64km
+  hoppingMode: 00h = baseband hopping
+  cellType: 00h =  Standard Cell
+  BCCH ARFCN / bCCHFrequency: 1
+*/
+
+static unsigned char bs11_attr_bts[] =
+{
+		NM_ATT_BSIC, HARDCODED_BSIC,
+		NM_ATT_BTS_AIR_TIMER, 0x04,
+		NM_ATT_BS11_BTSLS_HOPPING, 0x00,
+		NM_ATT_CCCH_L_I_P, 0x01,
+		NM_ATT_CCCH_L_T, 0x00,
+		NM_ATT_BS11_CELL_ALLOC_NR, NM_BS11_CANR_GSM,
+		NM_ATT_BS11_ENA_INTERF_CLASS, 0x01,
+		NM_ATT_BS11_FACCH_QUAL, 0x06,
+		/* interference avg. period in numbers of SACCH multifr */
+		NM_ATT_INTAVE_PARAM, 0x1F,
+		NM_ATT_INTERF_BOUND, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x7B,
+		NM_ATT_CCCH_L_T, 0x23,
+		NM_ATT_GSM_TIME, 0x28, 0x00,
+		NM_ATT_ADM_STATE, 0x03,
+		NM_ATT_RACH_B_THRESH, 0x7F,
+		NM_ATT_LDAVG_SLOTS, 0x00, 0xFA,
+		NM_ATT_BS11_RF_RES_IND_PER, 0x7D,
+		NM_ATT_T200, 0x2C, 0x1F, 0x29, 0x5A, 0x5A, 0x5A, 0x87,
+		NM_ATT_BS11_TSYNC, 0x23, 0x28,
+		NM_ATT_BS11_TTRAU, 0x23, 0x28,
+		NM_ATT_TEST_DUR, 0x01, 0x00,
+		NM_ATT_OUTST_ALARM, 0x01, 0x00,
+		NM_ATT_BS11_EXCESSIVE_DISTANCE, 0x01, 0x40,
+		NM_ATT_BS11_HOPPING_MODE, 0x01, 0x00,
+		NM_ATT_BS11_PLL, 0x01, 0x00,
+		NM_ATT_BCCH_ARFCN, 0x00, HARDCODED_ARFCN/*0x01*/,
+};
+
+// Handover Recognition, SET ATTRIBUTES
+
+/*
+Illegal Contents GSM Formatted O&M Msg
+  Object Class: Handover Recognition
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  enableDelayPowerBudgetHO: 00h = Disabled
+  enableDistanceHO: 00h =  Disabled
+  enableInternalInterCellHandover: 00h = Disabled
+  enableInternalIntraCellHandover: 00h =  Disabled
+  enablePowerBudgetHO: 00h = Disabled
+  enableRXLEVHO: 00h =  Disabled
+  enableRXQUALHO: 00h =  Disabled
+  hoAveragingDistance: 8  SACCH multiframes
+  hoAveragingLev:
+    A_LEV_HO: 8  SACCH multiframes
+    W_LEV_HO: 1  SACCH multiframes
+  hoAveragingPowerBudget:  16  SACCH multiframes
+  hoAveragingQual:
+    A_QUAL_HO: 8  SACCH multiframes
+    W_QUAL_HO: 2  SACCH multiframes
+  hoLowerThresholdLevDL: (10 - 110) dBm
+  hoLowerThresholdLevUL: (5 - 110) dBm
+  hoLowerThresholdQualDL: 06h =   6.4% < BER < 12.8%
+  hoLowerThresholdQualUL: 06h =   6.4% < BER < 12.8%
+  hoThresholdLevDLintra : (20 - 110) dBm
+  hoThresholdLevULintra: (20 - 110) dBm
+  hoThresholdMsRangeMax: 20 km
+  nCell: 06h
+  timerHORequest: 3  ,unit 2 SACCH multiframes
+*/
+
+unsigned char msg_3[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_BS11_HANDOVER, 0x00, 0xFF, 0xFF,
+		0xD0, 0x00,		/* enableDelayPowerBudgetHO */
+		0x64, 0x00,		/* enableDistanceHO */
+		0x67, 0x00,		/* enableInternalInterCellHandover */
+		0x68, 0x00,		/* enableInternalInterCellHandover */
+		0x6A, 0x00,		/* enablePowerBudgetHO */
+		0x6C, 0x00,		/* enableRXLEVHO */
+		0x6D, 0x00,		/* enableRXQUALHO */
+		0x6F, 0x08,		/* hoAveragingDistance */
+		0x70, 0x08, 0x01,	/* hoAveragingLev */
+		0x71, 0x10, 0x10, 0x10,
+		0x72, 0x08, 0x02,	/* hoAveragingQual */
+		0x73, 0x0A,		/* hoLowerThresholdLevDL */
+		0x74, 0x05,		/* hoLowerThresholdLevUL */
+		0x75, 0x06,		/* hoLowerThresholdQualDL */
+		0x76, 0x06,		/* hoLowerThresholdQualUL */
+		0x78, 0x14,		/* hoThresholdLevDLintra */
+		0x79, 0x14,		/* hoThresholdLevULintra */
+		0x7A, 0x14,		/* hoThresholdMsRangeMax */
+		0x7D, 0x06,		/* nCell */
+		NM_ATT_BS11_TIMER_HO_REQUEST, 0x03,
+		0x20, 0x01, 0x00,
+		0x45, 0x01, 0x00,
+		0x48, 0x01, 0x00,
+		0x5A, 0x01, 0x00,
+		0x5B, 0x01, 0x05,
+		0x5E, 0x01, 0x1A,
+		0x5F, 0x01, 0x20,
+		0x9D, 0x01, 0x00,
+		0x47, 0x01, 0x00,
+		0x5C, 0x01, 0x64,
+		0x5D, 0x01, 0x1E,
+		0x97, 0x01, 0x20,
+		0xF7, 0x01, 0x3C,
+};
+
+// Power Control, SET ATTRIBUTES
+
+/*
+  Object Class: Power Control
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  enableMsPowerControl: 00h =  Disabled
+  enablePowerControlRLFW: 00h =  Disabled
+  pcAveragingLev:
+    A_LEV_PC: 4  SACCH multiframes
+    W_LEV_PC: 1  SACCH multiframes
+  pcAveragingQual:
+    A_QUAL_PC: 4  SACCH multiframes
+    W_QUAL_PC: 2  SACCH multiframes
+  pcLowerThresholdLevDL: 0Fh
+  pcLowerThresholdLevUL: 0Ah
+  pcLowerThresholdQualDL: 05h =   3.2% < BER <  6.4%
+  pcLowerThresholdQualUL: 05h =   3.2% < BER <  6.4%
+  pcRLFThreshold: 0Ch
+  pcUpperThresholdLevDL: 14h
+  pcUpperThresholdLevUL: 0Fh
+  pcUpperThresholdQualDL: 04h =   1.6% < BER <  3.2%
+  pcUpperThresholdQualUL: 04h =   1.6% < BER <  3.2%
+  powerConfirm: 2  ,unit 2 SACCH multiframes
+  powerControlInterval: 2  ,unit 2 SACCH multiframes
+  powerIncrStepSize: 02h = 4 dB
+  powerRedStepSize: 01h = 2 dB
+  radioLinkTimeoutBs: 64  SACCH multiframes
+  enableBSPowerControl: 00h =  disabled
+*/
+
+unsigned char msg_4[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_BS11_PWR_CTRL, 0x00, 0xFF, 0xFF,
+		NM_ATT_BS11_ENA_MS_PWR_CTRL, 0x00,
+		NM_ATT_BS11_ENA_PWR_CTRL_RLFW, 0x00,
+		0x7E, 0x04, 0x01,	/* pcAveragingLev */
+		0x7F, 0x04, 0x02,	/* pcAveragingQual */
+		0x80, 0x0F,		/* pcLowerThresholdLevDL */
+		0x81, 0x0A,		/* pcLowerThresholdLevUL */
+		0x82, 0x05,		/* pcLowerThresholdQualDL */
+		0x83, 0x05,		/* pcLowerThresholdQualUL */
+		0x84, 0x0C, 		/* pcRLFThreshold */
+		0x85, 0x14, 		/* pcUpperThresholdLevDL */
+		0x86, 0x0F, 		/* pcUpperThresholdLevUL */
+		0x87, 0x04,		/* pcUpperThresholdQualDL */
+		0x88, 0x04,		/* pcUpperThresholdQualUL */
+		0x89, 0x02,		/* powerConfirm */
+		0x8A, 0x02,		/* powerConfirmInterval */
+		0x8B, 0x02,		/* powerIncrStepSize */
+		0x8C, 0x01,		/* powerRedStepSize */
+		0x8D, 0x40,		/* radioLinkTimeoutBs */
+		0x65, 0x01, 0x00 // set to 0x01 to enable BSPowerControl
+};
+
+
+// Transceiver, SET TRX ATTRIBUTES (TRX 0)
+
+/*
+  Object Class: Transceiver
+  BTS relat. Number: 0
+  Tranceiver number: 0
+  Instance 3: FF
+SET TRX ATTRIBUTES
+  aRFCNList (HEX):  0001
+  txPwrMaxReduction: 00h =   30dB
+  radioMeasGran: 254  SACCH multiframes
+  radioMeasRep: 01h =  enabled
+  memberOfEmergencyConfig: 01h =  TRUE
+  trxArea: 00h = TRX doesn't belong to a concentric cell
+*/
+
+static unsigned char bs11_attr_radio[] =
+{
+		NM_ATT_ARFCN_LIST, 0x01, 0x00, HARDCODED_ARFCN /*0x01*/,
+		NM_ATT_RF_MAXPOWR_R, 0x00,
+		NM_ATT_BS11_RADIO_MEAS_GRAN, 0x01, 0x05,
+		NM_ATT_BS11_RADIO_MEAS_REP, 0x01, 0x01,
+		NM_ATT_BS11_EMRG_CFG_MEMBER, 0x01, 0x01,
+		NM_ATT_BS11_TRX_AREA, 0x01, 0x00,
+};
+
+static unsigned char nanobts_attr_bts[] = {
+	NM_ATT_INTERF_BOUND, 0x55, 0x5b, 0x61, 0x67, 0x6d, 0x73,
+	/* interference avg. period in numbers of SACCH multifr */
+	NM_ATT_INTAVE_PARAM, 0x06,
+	/* conn fail based on SACCH error rate */
+	NM_ATT_CONN_FAIL_CRIT, 0x00, 0x02, 0x01, 0x10,
+	NM_ATT_T200, 0x1e, 0x24, 0x24, 0xa8, 0x34, 0x21, 0xa8,
+	NM_ATT_MAX_TA, 0x3f,
+	NM_ATT_OVERL_PERIOD, 0x00, 0x01, 10, /* seconds */
+	NM_ATT_CCCH_L_T, 10, /* percent */
+	NM_ATT_CCCH_L_I_P, 1, /* seconds */
+	NM_ATT_RACH_B_THRESH, 10, /* busy threshold in - dBm */
+	NM_ATT_LDAVG_SLOTS, 0x03, 0xe8, /* rach load averaging 1000 slots */
+	NM_ATT_BTS_AIR_TIMER, 128, /* miliseconds */
+	NM_ATT_NY1, 10, /* 10 retransmissions of physical config */
+	NM_ATT_BCCH_ARFCN, HARDCODED_ARFCN >> 8, HARDCODED_ARFCN & 0xff,
+	NM_ATT_BSIC, HARDCODED_BSIC,
+	NM_ATT_IPACC_CGI, 0, 7,  0x00, 0xf1, 0x10, 0x00, 0x01, 0x00, 0x00,
+};
+
+static unsigned char nanobts_attr_radio[] = {
+	NM_ATT_RF_MAXPOWR_R, 0x0c, /* number of -2dB reduction steps / Pn */
+	NM_ATT_ARFCN_LIST, 0x00, 0x02, HARDCODED_ARFCN >> 8, HARDCODED_ARFCN & 0xff,
+};
+
+static unsigned char nanobts_attr_nse[] = {
+	NM_ATT_IPACC_NSEI, 0, 2,  0x03, 0x9d, /* NSEI 925 */
+	NM_ATT_IPACC_NS_CFG, 0, 7,  3,  /* (un)blocking timer (Tns-block) */
+				    3,  /* (un)blocking retries */
+				    3,  /* reset timer (Tns-reset) */
+				    3,  /* reset retries */
+				    30,  /* test timer (Tns-test) */
+				    3,  /* alive timer (Tns-alive) */
+				    10, /* alive retrires */
+	NM_ATT_IPACC_BSSGP_CFG, 0, 11,
+				    3,  /* blockimg timer (T1) */
+				    3,  /* blocking retries */
+				    3,  /* unblocking retries */
+				    3,  /* reset timer */
+				    3,  /* reset retries */
+				    10, /* suspend timer (T3) in 100ms */
+				    3,  /* suspend retries */
+				    10, /* resume timer (T4) in 100ms */
+				    3,  /* resume retries */
+				    10, /* capability update timer (T5) */
+				    3,  /* capability update retries */
+};
+
+static unsigned char nanobts_attr_cell[] = {
+	NM_ATT_IPACC_RAC, 0, 1,  1, /* routing area code */
+	NM_ATT_IPACC_GPRS_PAGING_CFG, 0, 2,
+		5,	/* repeat time (50ms) */
+		3,	/* repeat count */
+	NM_ATT_IPACC_BVCI, 0, 2,  0x03, 0x9d, /* BVCI 925 */
+	NM_ATT_IPACC_RLC_CFG, 0, 9,
+		20, 	/* T3142 */
+		5, 	/* T3169 */
+		5,	/* T3191 */
+		200,	/* T3193 */
+		5,	/* T3195 */
+		10,	/* N3101 */
+		4,	/* N3103 */
+		8,	/* N3105 */
+		15,	/* RLC CV countdown */
+	NM_ATT_IPACC_CODING_SCHEMES, 0, 2,  0x0f, 0x00,	/* CS1..CS4 */
+	NM_ATT_IPACC_RLC_CFG_2, 0, 5,
+		0x00, 250,	/* T downlink TBF extension (0..500) */
+		0x00, 250,	/* T uplink TBF extension (0..500) */
+		2,	/* CS2 */
+#if 0
+	/* EDGE model only, breaks older models.
+	 * Should inquire the BTS capabilities */
+	NM_ATT_IPACC_RLC_CFG_3, 0, 1,
+		2,	/* MCS2 */
+#endif
+};
+
+static unsigned char nanobts_attr_nsvc0[] = {
+	NM_ATT_IPACC_NSVCI, 0, 2,  0x03, 0x9d, /* 925 */
+	NM_ATT_IPACC_NS_LINK_CFG, 0, 8,
+		0x59, 0xd8, /* remote udp port (23000) */
+		192, 168, 100, 11, /* remote ip address */
+		0x59, 0xd8, /* local udp port (23000) */
+};
+
+/* Callback function to be called whenever we get a GSM 12.21 state change event */
+int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
+		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state)
+{
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_bts_gprs_nsvc *nsvc;
+
+	/* This event-driven BTS setup is currently only required on nanoBTS */
+
+	/* EVT_STATECHG_ADM is called after we call chg_adm_state() and would create
+	 * endless loop */
+	if (evt != EVT_STATECHG_OPER)
+		return 0;
+
+	switch (obj_class) {
+	case NM_OC_SITE_MANAGER:
+		bts = container_of(obj, struct gsm_bts, site_mgr);
+		if ((new_state->operational == 2 &&
+		     new_state->availability == NM_AVSTATE_OK) ||
+		    (new_state->operational == 1 &&
+		     new_state->availability == NM_AVSTATE_OFF_LINE))
+			abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff);
+		break;
+	case NM_OC_BTS:
+		bts = obj;
+		if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			patch_nm_tables(bts);
+			abis_nm_set_bts_attr(bts, nanobts_attr_bts,
+					     sizeof(nanobts_attr_bts));
+			abis_nm_chg_adm_state(bts, obj_class,
+					      bts->bts_nr, 0xff, 0xff,
+					      NM_STATE_UNLOCKED);
+			abis_nm_opstart(bts, obj_class,
+					bts->bts_nr, 0xff, 0xff);
+		}
+		break;
+	case NM_OC_CHANNEL:
+		ts = obj;
+		trx = ts->trx;
+		if (new_state->operational == 1 &&
+		    new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			patch_nm_tables(trx->bts);
+			enum abis_nm_chan_comb ccomb =
+						abis_nm_chcomb4pchan(ts->pchan);
+			abis_nm_set_channel_attr(ts, ccomb);
+			abis_nm_chg_adm_state(trx->bts, obj_class,
+					      trx->bts->bts_nr, trx->nr, ts->nr,
+					      NM_STATE_UNLOCKED);
+			abis_nm_opstart(trx->bts, obj_class,
+					trx->bts->bts_nr, trx->nr, ts->nr);
+		}
+		break;
+	case NM_OC_RADIO_CARRIER:
+		trx = obj;
+		if (new_state->operational == 1 &&
+		    new_state->availability == NM_AVSTATE_OK)
+			abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr,
+					trx->nr, 0xff);
+		break;
+	case NM_OC_GPRS_NSE:
+		bts = container_of(obj, struct gsm_bts, gprs.nse);
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		if (new_state->availability == 5) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  0xff, 0xff, nanobts_attr_nse,
+						  sizeof(nanobts_attr_nse));
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					0xff, 0xff);
+			abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+					      0xff, 0xff, NM_STATE_UNLOCKED);
+		}
+		break;
+	case NM_OC_GPRS_CELL:
+		bts = container_of(obj, struct gsm_bts, gprs.cell);
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		if (new_state->availability == 5) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  0, 0xff, nanobts_attr_cell,
+						  sizeof(nanobts_attr_cell));
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					0, 0xff);
+			abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+					      0, 0xff, NM_STATE_UNLOCKED);
+		}
+		break;
+	case NM_OC_GPRS_NSVC:
+		nsvc = obj;
+		bts = nsvc->bts;
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		/* We skip NSVC1 since we only use NSVC0 */
+		if (nsvc->id == 1)
+			break;
+		if (new_state->availability == NM_AVSTATE_OFF_LINE) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  nsvc->id, 0xff,
+						  nanobts_attr_nsvc0,
+						  sizeof(nanobts_attr_nsvc0));
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					nsvc->id, 0xff);
+			abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+					      nsvc->id, 0xff,
+					      NM_STATE_UNLOCKED);
+		}
+	default:
+		break;
+	}
+	return 0;
+}
+
+/* Callback function to be called every time we receive a 12.21 SW activated report */
+static int sw_activ_rep(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct gsm_bts *bts = mb->trx->bts;
+	struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+
+	if (!trx)
+		return -EINVAL;
+
+	switch (foh->obj_class) {
+	case NM_OC_BASEB_TRANSC:
+		abis_nm_chg_adm_state(trx->bts, foh->obj_class,
+				      trx->bts->bts_nr, trx->nr, 0xff,
+				      NM_STATE_UNLOCKED);
+		abis_nm_opstart(trx->bts, foh->obj_class,
+				trx->bts->bts_nr, trx->nr, 0xff);
+		/* TRX software is active, tell it to initiate RSL Link */
+		abis_nm_ipaccess_rsl_connect(trx, 0, 3003, trx->rsl_tei);
+		break;
+	case NM_OC_RADIO_CARRIER: {
+		/*
+		 * Locking the radio carrier will make it go
+		 * offline again and we would come here. The
+		 * framework should determine that there was
+		 * no change and avoid recursion.
+		 *
+		 * This code is here to make sure that on start
+		 * a TRX remains locked.
+		 */
+		int rc_state = trx->nm_state.administrative;
+		/* Patch ARFCN into radio attribute */
+		nanobts_attr_radio[5] &= 0xf0;
+		nanobts_attr_radio[5] |= trx->arfcn >> 8;
+		nanobts_attr_radio[6] = trx->arfcn & 0xff;
+		abis_nm_set_radio_attr(trx, nanobts_attr_radio,
+				       sizeof(nanobts_attr_radio));
+		abis_nm_chg_adm_state(trx->bts, foh->obj_class,
+				      trx->bts->bts_nr, trx->nr, 0xff,
+				      rc_state);
+		abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr,
+				trx->nr, 0xff);
+		break;
+		}
+	}
+	return 0;
+}
+
+/* Callback function for NACK on the OML NM */
+static int oml_msg_nack(u_int8_t mt)
+{
+	if (mt == NM_MT_SET_BTS_ATTR_NACK) {
+		LOGP(DNM, LOGL_FATAL, "Failed to set BTS attributes. That is fatal. "
+				"Was the bts type and frequency properly specified?\n");
+		exit(-1);
+	}
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from NM */
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	u_int8_t *msg_type;
+
+	switch (signal) {
+	case S_NM_SW_ACTIV_REP:
+		return sw_activ_rep(signal_data);
+	case S_NM_NACK:
+		msg_type = signal_data;
+		return oml_msg_nack(*msg_type);
+	default:
+		break;
+	}
+	return 0;
+}
+
+static void bootstrap_om_nanobts(struct gsm_bts *bts)
+{
+	/* We don't do callback based bootstrapping, but event driven (see above) */
+}
+
+static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts)
+{
+	enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan);
+	struct gsm_e1_subslot *e1l = &ts->e1_link;
+
+	abis_nm_set_channel_attr(ts, ccomb);
+
+	if (is_ipaccess_bts(ts->trx->bts))
+		return;
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+		abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts,
+					e1l->e1_ts_ss);
+		break;
+	default:
+		break;
+	}
+}
+
+static void nm_reconfig_trx(struct gsm_bts_trx *trx)
+{
+	struct gsm_e1_subslot *e1l = &trx->rsl_e1_link;
+	int i;
+
+	patch_nm_tables(trx->bts);
+
+	switch (trx->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		/* FIXME: discover this by fetching an attribute */
+#if 0
+		trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */
+#else
+		trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */
+#endif
+		abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts,
+					e1l->e1_ts_ss);
+		abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr,
+				      e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei);
+
+		/* Set Radio Attributes */
+		if (trx == trx->bts->c0)
+			abis_nm_set_radio_attr(trx, bs11_attr_radio,
+					       sizeof(bs11_attr_radio));
+		else {
+			u_int8_t trx1_attr_radio[sizeof(bs11_attr_radio)];
+			u_int8_t arfcn_low = trx->arfcn & 0xff;
+			u_int8_t arfcn_high = (trx->arfcn >> 8) & 0x0f;
+			memcpy(trx1_attr_radio, bs11_attr_radio,
+				sizeof(trx1_attr_radio));
+
+			/* patch ARFCN into TRX Attributes */
+			trx1_attr_radio[2] &= 0xf0;
+			trx1_attr_radio[2] |= arfcn_high;
+			trx1_attr_radio[3] = arfcn_low;
+
+			abis_nm_set_radio_attr(trx, trx1_attr_radio,
+					       sizeof(trx1_attr_radio));
+		}
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		switch (trx->bts->band) {
+		case GSM_BAND_850:
+		case GSM_BAND_900:
+			trx->nominal_power = 20;
+			break;
+		case GSM_BAND_1800:
+		case GSM_BAND_1900:
+			trx->nominal_power = 23;
+			break;
+		default:
+			LOGP(DNM, LOGL_ERROR, "Unsupported nanoBTS GSM band %s\n",
+				gsm_band_name(trx->bts->band));
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		nm_reconfig_ts(&trx->ts[i]);
+}
+
+static void nm_reconfig_bts(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		patch_nm_tables(bts);
+		abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/
+		abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts));
+		abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */
+		abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */
+		break;
+	default:
+		break;
+	}
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		nm_reconfig_trx(trx);
+}
+
+static void bootstrap_om_bs11(struct gsm_bts *bts)
+{
+	/* stop sending event reports */
+	abis_nm_event_reports(bts, 0);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* reconfigure BTS with all TRX and all TS */
+	nm_reconfig_bts(bts);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	/* restart sending event reports */
+	abis_nm_event_reports(bts, 1);
+}
+
+static void bootstrap_om(struct gsm_bts *bts)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		bootstrap_om_bs11(bts);
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		bootstrap_om_nanobts(bts);
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "Unable to bootstrap OML: Unknown BTS type %d\n", bts->type);
+	}
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+	LOGP(DNM, LOGL_NOTICE, "shutting down OML for BTS %u\n", bts->nr);
+
+	/* stop sending event reports */
+	abis_nm_event_reports(bts, 0);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	return 0;
+}
+
+int bsc_shutdown_net(struct gsm_network *net)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		int rc;
+		rc = shutdown_om(bts);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+/* set all system information types */
+static int set_system_infos(struct gsm_bts_trx *trx)
+{
+	int i, rc;
+	u_int8_t si_tmp[23];
+	struct gsm_bts *bts = trx->bts;
+
+	bts->si_common.cell_sel_par.ms_txpwr_max_ccch =
+			ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+	bts->si_common.cell_sel_par.neci = bts->network->neci;
+
+	if (trx == trx->bts->c0) {
+		for (i = 1; i <= 4; i++) {
+			rc = gsm_generate_si(si_tmp, trx->bts, i);
+			if (rc < 0)
+				goto err_out;
+			DEBUGP(DRR, "SI%2u: %s\n", i, hexdump(si_tmp, rc));
+			rsl_bcch_info(trx, i, si_tmp, sizeof(si_tmp));
+		}
+		if (bts->gprs.mode != BTS_GPRS_NONE) {
+			i = 13;
+			rc = gsm_generate_si(si_tmp, trx->bts, RSL_SYSTEM_INFO_13);
+			if (rc < 0)
+				goto err_out;
+			DEBUGP(DRR, "SI%2u: %s\n", i, hexdump(si_tmp, rc));
+			rsl_bcch_info(trx, RSL_SYSTEM_INFO_13, si_tmp, rc);
+		}
+	}
+
+	i = 5;
+	rc = gsm_generate_si(si_tmp, trx->bts, RSL_SYSTEM_INFO_5);
+	if (rc < 0)
+		goto err_out;
+	DEBUGP(DRR, "SI%2u: %s\n", i, hexdump(si_tmp, rc));
+	rsl_sacch_filling(trx, RSL_SYSTEM_INFO_5, si_tmp, rc);
+
+	i = 6;
+	rc = gsm_generate_si(si_tmp, trx->bts, RSL_SYSTEM_INFO_6);
+	if (rc < 0)
+		goto err_out;
+	DEBUGP(DRR, "SI%2u: %s\n", i, hexdump(si_tmp, rc));
+	rsl_sacch_filling(trx, RSL_SYSTEM_INFO_6, si_tmp, rc);
+
+	return 0;
+err_out:
+	LOGP(DRR, LOGL_ERROR, "Cannot generate SI %u for BTS %u, most likely "
+		"a problem with neighbor cell list generation\n",
+		i, trx->bts->nr);
+	return rc;
+}
+
+/*
+ * Patch the various SYSTEM INFORMATION tables to update
+ * the LAI
+ */
+static void patch_nm_tables(struct gsm_bts *bts)
+{
+	u_int8_t arfcn_low = bts->c0->arfcn & 0xff;
+	u_int8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f;
+
+	/* patch ARFCN into BTS Attributes */
+	bs11_attr_bts[69] &= 0xf0;
+	bs11_attr_bts[69] |= arfcn_high;
+	bs11_attr_bts[70] = arfcn_low;
+	nanobts_attr_bts[42] &= 0xf0;
+	nanobts_attr_bts[42] |= arfcn_high;
+	nanobts_attr_bts[43] = arfcn_low;
+
+	/* patch ARFCN into TRX Attributes */
+	bs11_attr_radio[2] &= 0xf0;
+	bs11_attr_radio[2] |= arfcn_high;
+	bs11_attr_radio[3] = arfcn_low;
+
+	/* patch the RACH attributes */
+	if (bts->rach_b_thresh != -1) {
+		nanobts_attr_bts[33] = bts->rach_b_thresh & 0xff;
+		bs11_attr_bts[33] = bts->rach_b_thresh & 0xff;
+	}
+
+	if (bts->rach_ldavg_slots != -1) {
+		u_int8_t avg_high = bts->rach_ldavg_slots & 0xff;
+		u_int8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f;
+
+		nanobts_attr_bts[35] = avg_high;
+		nanobts_attr_bts[36] = avg_low;
+		bs11_attr_bts[35] = avg_high;
+		bs11_attr_bts[36] = avg_low;
+	}
+
+	/* patch BSIC */
+	bs11_attr_bts[1] = bts->bsic;
+	nanobts_attr_bts[sizeof(nanobts_attr_bts)-11] = bts->bsic;
+
+	/* patch CGI */
+	abis_nm_ipaccess_cgi(nanobts_attr_bts+sizeof(nanobts_attr_bts)-7, bts);
+
+	/* patch the power reduction */
+	bs11_attr_radio[5] = bts->c0->max_power_red / 2;
+	nanobts_attr_radio[1] = bts->c0->max_power_red / 2;
+
+	/* patch NSEI */
+	nanobts_attr_nse[3] = bts->gprs.nse.nsei >> 8;
+	nanobts_attr_nse[4] = bts->gprs.nse.nsei & 0xff;
+
+	/* patch NSVCI */
+	nanobts_attr_nsvc0[3] = bts->gprs.nsvc[0].nsvci >> 8;
+	nanobts_attr_nsvc0[4] = bts->gprs.nsvc[0].nsvci & 0xff;
+
+	/* patch IP address as SGSN IP */
+	*(u_int16_t *)(nanobts_attr_nsvc0+8) =
+				htons(bts->gprs.nsvc[0].remote_port);
+	*(u_int32_t *)(nanobts_attr_nsvc0+10) =
+				htonl(bts->gprs.nsvc[0].remote_ip);
+	*(u_int16_t *)(nanobts_attr_nsvc0+14) =
+				htons(bts->gprs.nsvc[0].local_port);
+
+	/* patch BVCI */
+	nanobts_attr_cell[12] = bts->gprs.cell.bvci >> 8;
+	nanobts_attr_cell[13] = bts->gprs.cell.bvci & 0xff;
+	/* patch RAC */
+	nanobts_attr_cell[3] = bts->gprs.rac;
+
+	if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+		/* patch EGPRS coding schemes MCS 1..9 */
+		nanobts_attr_cell[29] = 0x8f;
+		nanobts_attr_cell[30] = 0xff;
+	}
+}
+
+static void bootstrap_rsl(struct gsm_bts_trx *trx)
+{
+	LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) "
+		"on ARFCN %u using MCC=%u MNC=%u LAC=%u CID=%u BSIC=%u TSC=%u\n",
+		trx->bts->nr, trx->nr, trx->arfcn, bsc_gsmnet->country_code,
+		bsc_gsmnet->network_code, trx->bts->location_area_code,
+		trx->bts->cell_identity, trx->bts->bsic, trx->bts->tsc);
+	set_system_infos(trx);
+}
+
+void input_event(int event, enum e1inp_sign_type type, struct gsm_bts_trx *trx)
+{
+	int ts_no, lchan_no;
+
+	switch (event) {
+	case EVT_E1_TEI_UP:
+		switch (type) {
+		case E1INP_SIGN_OML:
+			bootstrap_om(trx->bts);
+			break;
+		case E1INP_SIGN_RSL:
+			bootstrap_rsl(trx);
+			break;
+		default:
+			break;
+		}
+		break;
+	case EVT_E1_TEI_DN:
+		LOGP(DMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", type, trx);
+
+		if (type == E1INP_SIGN_OML)
+			counter_inc(trx->bts->network->stats.bts.oml_fail);
+		else if (type == E1INP_SIGN_RSL)
+			counter_inc(trx->bts->network->stats.bts.rsl_fail);
+
+		/*
+		 * free all allocated channels. change the nm_state so the
+		 * trx and trx_ts becomes unusable and chan_alloc.c can not
+		 * allocate from it.
+		 */
+		for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[ts_no];
+
+			for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) {
+				if (ts->lchan[lchan_no].state != GSM_LCHAN_NONE)
+					lchan_free(&ts->lchan[lchan_no]);
+				lchan_reset(&ts->lchan[lchan_no]);
+			}
+
+			ts->nm_state.operational = 0;
+			ts->nm_state.availability = 0;
+		}
+
+		trx->nm_state.operational = 0;
+		trx->nm_state.availability = 0;
+		trx->bb_transc.nm_state.operational = 0;
+		trx->bb_transc.nm_state.availability = 0;
+		break;
+	default:
+		break;
+	}
+}
+
+static int bootstrap_bts(struct gsm_bts *bts)
+{
+	int i, n;
+
+	switch (bts->band) {
+	case GSM_BAND_1800:
+		if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) {
+			LOGP(DNM, LOGL_ERROR, "GSM1800 channel must be between 512-885.\n");
+			return -EINVAL;
+		}
+		break;
+	case GSM_BAND_1900:
+		if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) {
+			LOGP(DNM, LOGL_ERROR, "GSM1900 channel must be between 512-810.\n");
+			return -EINVAL;
+		}
+		break;
+	case GSM_BAND_900:
+		if (bts->c0->arfcn < 1 ||
+		   (bts->c0->arfcn > 124 && bts->c0->arfcn < 955) ||
+		    bts->c0->arfcn > 1023)  {
+			LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 1-124, 955-1023.\n");
+			return -EINVAL;
+		}
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "Unsupported frequency band.\n");
+		return -EINVAL;
+	}
+
+	if (bts->network->auth_policy == GSM_AUTH_POLICY_ACCEPT_ALL &&
+	    !bts->si_common.rach_control.cell_bar)
+		LOGP(DNM, LOGL_ERROR, "\nWARNING: You are running an 'accept-all' "
+			"network on a BTS that is not barred.  This "
+			"configuration is likely to interfere with production "
+			"GSM networks and should only be used in a RF "
+			"shielded environment such as a faraday cage!\n\n");
+
+	/* Control Channel Description */
+	bts->si_common.chan_desc.att = 1;
+	bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5;
+	/* T3212 is set from vty/config */
+
+	/* Set ccch config by looking at ts config */
+	for (n=0, i=0; i<8; i++)
+		n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0;
+
+	switch (n) {
+	case 0:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;
+		break;
+	case 1:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC;
+		break;
+	case 2:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC;
+		break;
+	case 3:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC;
+		break;
+	case 4:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC;
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n");
+		return -EINVAL;
+	}
+
+	/* some defaults for our system information */
+	bts->si_common.cell_options.radio_link_timeout = 2; /* 12 */
+	bts->si_common.cell_options.dtx = 2; /* MS shall not use upplink DTX */
+	bts->si_common.cell_options.pwrc = 0; /* PWRC not set */
+
+	bts->si_common.cell_sel_par.acs = 0;
+
+	bts->si_common.ncc_permitted = 0xff;
+
+	paging_init(bts);
+
+	return 0;
+}
+
+int bsc_bootstrap_network(int (*mncc_recv)(struct gsm_network *, int, void *),
+			  const char *config_file)
+{
+	struct gsm_bts *bts;
+	int rc;
+
+	/* initialize our data structures */
+	bsc_gsmnet = gsm_network_init(1, 1, mncc_recv);
+	if (!bsc_gsmnet)
+		return -ENOMEM;
+
+	bsc_gsmnet->name_long = talloc_strdup(bsc_gsmnet, "OpenBSC");
+	bsc_gsmnet->name_short = talloc_strdup(bsc_gsmnet, "OpenBSC");
+
+	telnet_init(bsc_gsmnet, 4242);
+	rc = vty_read_config_file(config_file);
+	if (rc < 0) {
+		LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	register_signal_handler(SS_NM, nm_sig_cb, NULL);
+
+	llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+		bootstrap_bts(bts);
+		if (!is_ipaccess_bts(bts))
+			rc = e1_reconfig_bts(bts);
+
+		if (rc < 0)
+			exit (1);
+	}
+
+	/* initialize nanoBTS support omce */
+	rc = ipaccess_setup(bsc_gsmnet);
+
+	return 0;
+}
diff --git a/openbsc/src/bsc_rll.c b/openbsc/src/bsc_rll.c
new file mode 100644
index 0000000..9a4f5aa
--- /dev/null
+++ b/openbsc/src/bsc_rll.c
@@ -0,0 +1,122 @@
+/* GSM BSC Radio Link Layer API
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/linuxlist.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/abis_rsl.h>
+
+struct bsc_rll_req {
+	struct llist_head list;
+	struct timer_list timer;
+
+	struct gsm_lchan *lchan;
+	u_int8_t link_id;
+
+	void (*cb)(struct gsm_lchan *lchan, u_int8_t link_id,
+		   void *data, enum bsc_rllr_ind);
+	void *data;
+};
+
+/* we only compare C1, C2 and SAPI */
+#define LINKID_MASK	0xC7
+
+static LLIST_HEAD(bsc_rll_reqs);
+
+static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type)
+{
+	struct gsm_subscriber_connection *conn;
+
+	conn = &rllr->lchan->conn;
+	llist_del(&rllr->list);
+	put_subscr_con(conn);
+	rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type);
+	talloc_free(rllr);
+}
+
+static void timer_cb(void *_rllr)
+{
+	struct bsc_rll_req *rllr = _rllr;
+
+	complete_rllr(rllr, BSC_RLLR_IND_TIMEOUT);
+}
+
+/* establish a RLL connection with given SAPI / priority */
+int rll_establish(struct gsm_lchan *lchan, u_int8_t sapi,
+		  void (*cb)(struct gsm_lchan *, u_int8_t, void *,
+			     enum bsc_rllr_ind),
+		  void *data)
+{
+	struct gsm_subscriber_connection *conn;
+	struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req);
+	u_int8_t link_id;
+	if (!rllr)
+		return -ENOMEM;
+
+	link_id = sapi;
+
+	/* If we are a TCH and not in signalling mode, we need to
+	 * indicate that the new RLL connection is to be made on the SACCH */
+	if ((lchan->type == GSM_LCHAN_TCH_F ||
+	     lchan->type == GSM_LCHAN_TCH_H) && sapi != 0)
+		link_id |= 0x40;
+
+	conn = &lchan->conn;
+	use_subscr_con(conn);
+	rllr->lchan = lchan;
+	rllr->link_id = link_id;
+	rllr->cb = cb;
+	rllr->data = data;
+
+	llist_add(&rllr->list, &bsc_rll_reqs);
+
+	rllr->timer.cb = &timer_cb;
+	rllr->timer.data = rllr;
+
+	bsc_schedule_timer(&rllr->timer, 10, 0);
+
+	/* send the RSL RLL ESTablish REQuest */
+	return rsl_establish_request(rllr->lchan, rllr->link_id);
+}
+
+/* Called from RSL code in case we have received an indication regarding
+ * any RLL link */
+void rll_indication(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t type)
+{
+	struct bsc_rll_req *rllr, *rllr2;
+
+	llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) {
+		if (rllr->lchan == lchan &&
+		    (rllr->link_id & LINKID_MASK) == (link_id & LINKID_MASK)) {
+			bsc_del_timer(&rllr->timer);
+			complete_rllr(rllr, type);
+			return;
+		}
+	}
+}
diff --git a/openbsc/src/bsc_version.c b/openbsc/src/bsc_version.c
new file mode 100644
index 0000000..0266194
--- /dev/null
+++ b/openbsc/src/bsc_version.c
@@ -0,0 +1,32 @@
+/* Hold the copyright and version string */
+/* (C) 2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "bscconfig.h"
+
+const char *openbsc_version = "OpenBSC " PACKAGE_VERSION;
+const char *openbsc_copyright =
+	"Copyright (C) 2008-2010 Harald Welte, Holger Freyther\n"
+	"Contributions by Daniel Willmann, Jan Lübbe,Stefan Schmidt\n"
+	"Dieter Spaar, Andreas Eversberg\n\n"
+	"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
+	"This is free software: you are free to change and redistribute it.\n"
+	"There is NO WARRANTY, to the extent permitted by law.\n";
+
+
diff --git a/openbsc/src/bts_ipaccess_nanobts.c b/openbsc/src/bts_ipaccess_nanobts.c
new file mode 100644
index 0000000..cb48ea9
--- /dev/null
+++ b/openbsc/src/bts_ipaccess_nanobts.c
@@ -0,0 +1,84 @@
+/* ip.access nanoBTS specific code */
+
+/* (C) 2009-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/tlv.h>
+#include <openbsc/abis_nm.h>
+
+static struct gsm_bts_model model_nanobts = {
+	.type = GSM_BTS_TYPE_NANOBTS,
+	.nm_att_tlvdef = {
+		.def = {
+			/* ip.access specifics */
+			[NM_ATT_IPACC_DST_IP] =		{ TLV_TYPE_FIXED, 4 },
+			[NM_ATT_IPACC_DST_IP_PORT] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_STREAM_ID] =	{ TLV_TYPE_TV, },
+			[NM_ATT_IPACC_FREQ_CTRL] =	{ TLV_TYPE_TV, },
+			[NM_ATT_IPACC_SEC_OML_CFG] =	{ TLV_TYPE_FIXED, 6 },
+			[NM_ATT_IPACC_IP_IF_CFG] =	{ TLV_TYPE_FIXED, 8 },
+			[NM_ATT_IPACC_IP_GW_CFG] =	{ TLV_TYPE_FIXED, 12 },
+			[NM_ATT_IPACC_IN_SERV_TIME] =	{ TLV_TYPE_FIXED, 4 },
+			[NM_ATT_IPACC_LOCATION] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_PAGING_CFG] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_UNIT_ID] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_UNIT_NAME] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SNMP_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NV_FLAGS] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_FREQ_CTRL] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CUR_SW_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_TIMING_BUS] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CGI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RAC] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_OBJ_VERSION] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NSEI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_BVCI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NSVCI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NS_CFG] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_BSSGP_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NS_LINK_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_ALM_THRESH_LIST]=	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_TIB_CONTROL] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SUPP_FEATURES] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CODING_SCHEMES] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG_2] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_HEARTB_TOUT] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_UPTIME] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG_3] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SSL_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SEC_POSSIBLE] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_IML_SSL_STATE] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_REVOC_DATE] =	{ TLV_TYPE_TL16V },
+		},
+	},
+};
+
+int bts_model_nanobts_init(void)
+{
+	return gsm_bts_model_register(&model_nanobts);
+}
diff --git a/openbsc/src/bts_siemens_bs11.c b/openbsc/src/bts_siemens_bs11.c
new file mode 100644
index 0000000..c966825
--- /dev/null
+++ b/openbsc/src/bts_siemens_bs11.c
@@ -0,0 +1,66 @@
+/* Siemens BS-11 specific code */
+
+/* (C) 2009-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/tlv.h>
+#include <openbsc/abis_nm.h>
+
+static struct gsm_bts_model model_bs11 = {
+	.type = GSM_BTS_TYPE_BS11,
+	.nm_att_tlvdef = {
+		.def = {
+			[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TLV },
+			/* BS11 specifics */
+			[NM_ATT_BS11_ESN_FW_CODE_NO] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_ESN_HW_CODE_NO] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_ESN_PCB_SERIAL] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_BOOT_SW_VERS] =	{ TLV_TYPE_TLV },
+			[0xd5] =			{ TLV_TYPE_TLV },
+			[0xa8] =			{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PASSWORD] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_TXPWR] =		{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_RSSI_OFFS] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LINE_CFG] = 	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_L1_PROT_TYPE] =	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_BIT_ERR_THESH] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_BS11_DIVERSITY] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV },	
+			[NM_ATT_BS11_LMT_LOGIN_TIME] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_USER_NAME] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_BTS_STATE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_E1_STATE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PLL_MODE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PLL]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_CCLK_ACCURACY] =	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_CCLK_TYPE] =	{ TLV_TYPE_TV },
+			[0x95] =			{ TLV_TYPE_FIXED, 2 },
+		},
+	},
+};
+
+int bts_model_bs11_init(void)
+{
+	return gsm_bts_model_register(&model_bs11);
+}
diff --git a/openbsc/src/bts_unknown.c b/openbsc/src/bts_unknown.c
new file mode 100644
index 0000000..aac5d99
--- /dev/null
+++ b/openbsc/src/bts_unknown.c
@@ -0,0 +1,40 @@
+/* Generic BTS - VTY code tries to allocate this BTS before type is known */
+
+/* (C) 2010 by Daniel Willmann <daniel@totalueberwachung.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/tlv.h>
+#include <openbsc/abis_nm.h>
+
+static struct gsm_bts_model model_unknown = {
+	.type = GSM_BTS_TYPE_UNKNOWN,
+	.nm_att_tlvdef = {
+		.def = {
+		},
+	},
+};
+
+int bts_model_unknown_init(void)
+{
+	return gsm_bts_model_register(&model_unknown);
+}
diff --git a/openbsc/src/chan_alloc.c b/openbsc/src/chan_alloc.c
new file mode 100644
index 0000000..107abdc
--- /dev/null
+++ b/openbsc/src/chan_alloc.c
@@ -0,0 +1,458 @@
+/* GSM Channel allocation routines
+ *
+ * (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+
+static void auto_release_channel(void *_lchan);
+
+static int ts_is_usable(struct gsm_bts_trx_ts *ts)
+{
+	/* FIXME: How does this behave for BS-11 ? */
+	if (is_ipaccess_bts(ts->trx->bts)) {
+		if (!nm_is_running(&ts->nm_state))
+			return 0;
+	}
+
+	return 1;
+}
+
+int trx_is_usable(struct gsm_bts_trx *trx)
+{
+	/* FIXME: How does this behave for BS-11 ? */
+	if (is_ipaccess_bts(trx->bts)) {
+		if (!nm_is_running(&trx->nm_state) ||
+		    !nm_is_running(&trx->bb_transc.nm_state))
+			return 0;
+	}
+
+	return 1;
+}
+
+struct gsm_bts_trx_ts *ts_c0_alloc(struct gsm_bts *bts,
+				   enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx *trx = bts->c0;
+	struct gsm_bts_trx_ts *ts = &trx->ts[0];
+
+	if (pchan != GSM_PCHAN_CCCH &&
+	    pchan != GSM_PCHAN_CCCH_SDCCH4)
+		return NULL;
+
+	if (ts->pchan != GSM_PCHAN_NONE)
+		return NULL;
+
+	ts->pchan = pchan;
+
+	return ts;
+}
+
+/* Allocate a physical channel (TS) */
+struct gsm_bts_trx_ts *ts_alloc(struct gsm_bts *bts,
+				enum gsm_phys_chan_config pchan)
+{
+	int j;
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		int from, to;
+
+		if (!trx_is_usable(trx))
+			continue;
+
+		/* the following constraints are pure policy,
+		 * no requirement to put this restriction in place */
+		if (trx == bts->c0) {
+			/* On the first TRX we run one CCCH and one SDCCH8 */
+			switch (pchan) {
+			case GSM_PCHAN_CCCH:
+			case GSM_PCHAN_CCCH_SDCCH4:
+				from = 0; to = 0;
+				break;
+			case GSM_PCHAN_TCH_F:
+			case GSM_PCHAN_TCH_H:
+				from = 1; to = 7;
+				break;
+			case GSM_PCHAN_SDCCH8_SACCH8C:
+			default:
+				return NULL;
+			}
+		} else {
+			/* Every secondary TRX is configured for TCH/F
+			 * and TCH/H only */
+			switch (pchan) {
+			case GSM_PCHAN_SDCCH8_SACCH8C:
+				from = 1; to = 1;
+			case GSM_PCHAN_TCH_F:
+			case GSM_PCHAN_TCH_H:
+				from = 1; to = 7;
+				break;
+			default:
+				return NULL;
+			}
+		}
+
+		for (j = from; j <= to; j++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[j];
+
+			if (!ts_is_usable(ts))
+				continue;
+
+			if (ts->pchan == GSM_PCHAN_NONE) {
+				ts->pchan = pchan;
+				/* set channel attribute on OML */
+				abis_nm_set_channel_attr(ts, abis_nm_chcomb4pchan(pchan));
+				return ts;
+			}
+		}
+	}
+	return NULL;
+}
+
+/* Free a physical channel (TS) */
+void ts_free(struct gsm_bts_trx_ts *ts)
+{
+	ts->pchan = GSM_PCHAN_NONE;
+}
+
+static const u_int8_t subslots_per_pchan[] = {
+	[GSM_PCHAN_NONE] = 0,
+	[GSM_PCHAN_CCCH] = 0,
+	[GSM_PCHAN_CCCH_SDCCH4] = 4,
+	[GSM_PCHAN_TCH_F] = 1,
+	[GSM_PCHAN_TCH_H] = 2,
+	[GSM_PCHAN_SDCCH8_SACCH8C] = 8,
+	/* FIXME: what about dynamic TCH_F_TCH_H ? */
+	[GSM_PCHAN_TCH_F_PDCH] = 1,
+};
+
+static struct gsm_lchan *
+_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx_ts *ts;
+	int j, ss;
+
+	if (!trx_is_usable(trx))
+		return NULL;
+
+	for (j = 0; j < 8; j++) {
+		ts = &trx->ts[j];
+		if (!ts_is_usable(ts))
+			continue;
+		/* ip.access dynamic TCH/F + PDCH combination */
+		if (ts->pchan == GSM_PCHAN_TCH_F_PDCH &&
+		    pchan == GSM_PCHAN_TCH_F) {
+			/* we can only consider such a dynamic channel
+			 * if the PDCH is currently inactive */
+			if (ts->flags & TS_F_PDCH_MODE)
+				continue;
+		} else if (ts->pchan != pchan)
+			continue;
+		/* check if all sub-slots are allocated yet */
+		for (ss = 0; ss < subslots_per_pchan[pchan]; ss++) {
+			struct gsm_lchan *lc = &ts->lchan[ss];
+			if (lc->type == GSM_LCHAN_NONE &&
+			    lc->state == LCHAN_S_NONE)
+				return lc;
+		}
+	}
+
+	return NULL;
+}
+
+static struct gsm_lchan *
+_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_lchan *lc;
+
+	if (bts->chan_alloc_reverse) {
+		llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
+			lc = _lc_find_trx(trx, pchan);
+			if (lc)
+				return lc;
+		}
+	} else {
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			lc = _lc_find_trx(trx, pchan);
+			if (lc)
+				return lc;
+		}
+	}
+
+	/* we cannot allocate more of these */
+	if (pchan == GSM_PCHAN_CCCH_SDCCH4)
+		return NULL;
+
+	/* if we've reached here, we need to allocate a new physical
+	 * channel for the logical channel type requested */
+	ts = ts_alloc(bts, pchan);
+	if (!ts) {
+		/* no more radio resources */
+		return NULL;
+	}
+	return &ts->lchan[0];
+}
+
+/* Allocate a logical channel */
+struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type)
+{
+	struct gsm_lchan *lchan = NULL;
+	enum gsm_phys_chan_config first, second;
+
+	switch (type) {
+	case GSM_LCHAN_SDCCH:
+		if (bts->chan_alloc_reverse) {
+			first = GSM_PCHAN_SDCCH8_SACCH8C;
+			second = GSM_PCHAN_CCCH_SDCCH4;
+		} else {
+			first = GSM_PCHAN_CCCH_SDCCH4;
+			second = GSM_PCHAN_SDCCH8_SACCH8C;
+		}
+
+		lchan = _lc_find_bts(bts, first);
+		if (lchan == NULL)
+			lchan = _lc_find_bts(bts, second);
+		break;
+	case GSM_LCHAN_TCH_F:
+		lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+		break;
+	case GSM_LCHAN_TCH_H:
+		lchan =_lc_find_bts(bts, GSM_PCHAN_TCH_H);
+		/* If we don't have TCH/H available, fall-back to TCH/F */
+		if (!lchan) {
+			lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+			type = GSM_LCHAN_TCH_F;
+		}
+		break;
+	default:
+		LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
+	}
+
+	if (lchan) {
+		lchan->type = type;
+
+		/* clear sapis */
+		memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis));
+
+		/* clear multi rate config */
+		memset(&lchan->mr_conf, 0, sizeof(lchan->mr_conf));
+
+		/* clear per MSC/BSC data */
+		memset(&lchan->conn, 0, sizeof(lchan->conn));
+
+		/* Configure the time and start it so it will be closed */
+		lchan->conn.lchan = lchan;
+		lchan->conn.bts = lchan->ts->trx->bts;
+		lchan->conn.release_timer.cb = auto_release_channel;
+		lchan->conn.release_timer.data = lchan;
+		bsc_schedule_timer(&lchan->conn.release_timer, LCHAN_RELEASE_TIMEOUT);
+
+	} else {
+		struct challoc_signal_data sig;
+		sig.bts = bts;
+		sig.type = type;
+		dispatch_signal(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig);
+	}
+
+	return lchan;
+}
+
+/* Free a logical channel */
+void lchan_free(struct gsm_lchan *lchan)
+{
+	struct challoc_signal_data sig;
+	int i;
+
+	sig.type = lchan->type;
+	lchan->type = GSM_LCHAN_NONE;
+	if (lchan->conn.subscr) {
+		subscr_put(lchan->conn.subscr);
+		lchan->conn.subscr = NULL;
+	}
+
+	/* We might kill an active channel... */
+	if (lchan->conn.use_count != 0) {
+		dispatch_signal(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, lchan);
+		lchan->conn.use_count = 0;
+	}
+
+	/* stop the timer */
+	bsc_del_timer(&lchan->conn.release_timer);
+	bsc_del_timer(&lchan->T3101);
+
+	/* clear cached measuement reports */
+	lchan->meas_rep_idx = 0;
+	for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) {
+		lchan->meas_rep[i].flags = 0;
+		lchan->meas_rep[i].nr = 0;
+	}
+	for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++)
+		lchan->neigh_meas[i].arfcn = 0;
+
+	lchan->conn.silent_call = 0;
+
+	sig.lchan = lchan;
+	sig.bts = lchan->ts->trx->bts;
+	dispatch_signal(SS_CHALLOC, S_CHALLOC_FREED, &sig);
+
+	/* FIXME: ts_free() the timeslot, if we're the last logical
+	 * channel using it */
+}
+
+/*
+ * There was an error with the TRX and we need to forget
+ * any state so that a lchan can be allocated again after
+ * the trx is fully usable.
+ */
+void lchan_reset(struct gsm_lchan *lchan)
+{
+	bsc_del_timer(&lchan->T3101);
+
+	lchan->type = GSM_LCHAN_NONE;
+	lchan->state = LCHAN_S_NONE;
+}
+
+
+/* Consider releasing the channel now */
+int lchan_auto_release(struct gsm_lchan *lchan)
+{
+	if (lchan->conn.use_count > 0) {
+		return 0;
+	}
+
+	/* Assume we have GSM04.08 running and send a release */
+	if (lchan->conn.subscr) {
+		gsm48_send_rr_release(lchan);
+	}
+
+	/* spoofed? message */
+	if (lchan->conn.use_count < 0)
+		LOGP(DRLL, LOGL_ERROR, "Channel count is negative: %d\n",
+			lchan->conn.use_count);
+
+	DEBUGP(DRLL, "%s Recycling Channel\n", gsm_lchan_name(lchan));
+	rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
+	rsl_release_request(lchan, 0);
+	return 1;
+}
+
+/* Auto release the channel when the use count is zero */
+static void auto_release_channel(void *_lchan)
+{
+	struct gsm_lchan *lchan = _lchan;
+
+	if (!lchan_auto_release(lchan))
+		bsc_schedule_timer(&lchan->conn.release_timer, LCHAN_RELEASE_TIMEOUT);
+}
+
+struct gsm_lchan* lchan_find(struct gsm_bts *bts, struct gsm_subscriber *subscr) {
+	struct gsm_bts_trx *trx;
+	int ts_no, lchan_no;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		for (ts_no = 0; ts_no < 8; ++ts_no) {
+			for (lchan_no = 0; lchan_no < TS_MAX_LCHAN; ++lchan_no) {
+				struct gsm_lchan *lchan =
+					&trx->ts[ts_no].lchan[lchan_no];
+				if (subscr == lchan->conn.subscr)
+					return lchan;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+struct gsm_lchan *lchan_for_subscr(struct gsm_subscriber *subscr)
+{
+	struct gsm_bts *bts;
+	struct gsm_network *net = subscr->net;
+	struct gsm_lchan *lchan;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		lchan = lchan_find(bts, subscr);
+		if (lchan)
+			return lchan;
+	}
+
+	return NULL;
+}
+
+void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		int i;
+
+		/* skip administratively deactivated tranxsceivers */
+		if (!nm_is_running(&trx->nm_state) ||
+		    !nm_is_running(&trx->bb_transc.nm_state))
+			continue;
+
+		for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[i];
+			struct load_counter *pl = &cl->pchan[ts->pchan];
+			int j;
+
+			/* skip administratively deactivated timeslots */
+			if (!nm_is_running(&ts->nm_state))
+				continue;
+
+			for (j = 0; j < subslots_per_pchan[ts->pchan]; j++) {
+				struct gsm_lchan *lchan = &ts->lchan[j];
+
+				pl->total++;
+
+				switch (lchan->state) {
+				case LCHAN_S_NONE:
+					break;
+				default:
+					pl->used++;
+					break;
+				}
+			}
+		}
+	}
+}
+
+void network_chan_load(struct pchan_load *pl, struct gsm_network *net)
+{
+	struct gsm_bts *bts;
+
+	memset(pl, 0, sizeof(*pl));
+
+	llist_for_each_entry(bts, &net->bts_list, list)
+		bts_chan_load(pl, bts);
+}
diff --git a/openbsc/src/db.c b/openbsc/src/db.c
new file mode 100644
index 0000000..8bf47ab
--- /dev/null
+++ b/openbsc/src/db.c
@@ -0,0 +1,1182 @@
+/* Simple HLR/VLR database backend using dbi */
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <osmocore/statistics.h>
+
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <dbi/dbi.h>
+
+static char *db_basename = NULL;
+static char *db_dirname = NULL;
+static dbi_conn conn;
+
+static char *create_stmts[] = {
+	"CREATE TABLE IF NOT EXISTS Meta ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"key TEXT UNIQUE NOT NULL, "
+		"value TEXT NOT NULL"
+		")",
+	"INSERT OR IGNORE INTO Meta "
+		"(key, value) "
+		"VALUES "
+		"('revision', '2')",
+	"CREATE TABLE IF NOT EXISTS Subscriber ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"imsi NUMERIC UNIQUE NOT NULL, "
+		"name TEXT, "
+		"extension TEXT UNIQUE, "
+		"authorized INTEGER NOT NULL DEFAULT 0, "
+		"tmsi TEXT UNIQUE, "
+		"lac INTEGER NOT NULL DEFAULT 0"
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthToken ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"subscriber_id INTEGER UNIQUE NOT NULL, "
+		"created TIMESTAMP NOT NULL, "
+		"token TEXT UNIQUE NOT NULL"
+		")",
+	"CREATE TABLE IF NOT EXISTS Equipment ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"name TEXT, "
+		"classmark1 NUMERIC, "
+		"classmark2 BLOB, "
+		"classmark3 BLOB, "
+		"imei NUMERIC UNIQUE NOT NULL"
+		")",
+	"CREATE TABLE IF NOT EXISTS EquipmentWatch ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"subscriber_id NUMERIC NOT NULL, "
+		"equipment_id NUMERIC NOT NULL, "
+		"UNIQUE (subscriber_id, equipment_id) "
+		")",
+	"CREATE TABLE IF NOT EXISTS SMS ("
+		/* metadata, not part of sms */
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"sent TIMESTAMP, "
+		"sender_id INTEGER NOT NULL, "
+		"receiver_id INTEGER NOT NULL, "
+		"deliver_attempts INTEGER NOT NULL DEFAULT 0, "
+		/* data directly copied/derived from SMS */
+		"valid_until TIMESTAMP, "
+		"reply_path_req INTEGER NOT NULL, "
+		"status_rep_req INTEGER NOT NULL, "
+		"protocol_id INTEGER NOT NULL, "
+		"data_coding_scheme INTEGER NOT NULL, "
+		"ud_hdr_ind INTEGER NOT NULL, "
+		"dest_addr TEXT, "
+		"user_data BLOB, "	/* TP-UD */
+		/* additional data, interpreted from SMS */
+		"header BLOB, "		/* UD Header */
+		"text TEXT "		/* decoded UD after UDH */
+		")",
+	"CREATE TABLE IF NOT EXISTS VLR ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"subscriber_id NUMERIC UNIQUE NOT NULL, "
+		"last_bts NUMERIC NOT NULL "
+		")",
+	"CREATE TABLE IF NOT EXISTS ApduBlobs ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"apdu_id_flags INTEGER NOT NULL, "
+		"subscriber_id INTEGER NOT NULL, "
+		"apdu BLOB "
+		")",
+	"CREATE TABLE IF NOT EXISTS Counters ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"timestamp TIMESTAMP NOT NULL, "
+		"value INTEGER NOT NULL, "
+		"name TEXT NOT NULL "
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthKeys ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"subscriber_id INTEGER UNIQUE NOT NULL, "
+		"algorithm_id INTEGER NOT NULL, "
+		"a3a8_ki BLOB "
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthTuples ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"subscriber_id NUMERIC UNIQUE NOT NULL, "
+		"issued TIMESTAMP NOT NULL, "
+		"use_count INTEGER NOT NULL DEFAULT 0, "
+		"key_seq INTEGER NOT NULL, "
+		"rand BLOB NOT NULL, "
+		"sres BLOB NOT NULL, "
+		"kc BLOB NOT NULL "
+		")",
+};
+
+void db_error_func(dbi_conn conn, void *data)
+{
+	const char *msg;
+	dbi_conn_error(conn, &msg);
+	LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg);
+}
+
+static int check_db_revision(void)
+{
+	dbi_result result;
+	const char *rev;
+
+	result = dbi_conn_query(conn,
+				"SELECT value FROM Meta WHERE key='revision'");
+	if (!result)
+		return -EINVAL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -EINVAL;
+	}
+	rev = dbi_result_get_string(result, "value");
+	if (!rev || atoi(rev) != 2) {
+		dbi_result_free(result);
+		return -EINVAL;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_init(const char *name)
+{
+	dbi_initialize(NULL);
+
+	conn = dbi_conn_new("sqlite3");
+	if (conn == NULL) {
+		LOGP(DDB, LOGL_FATAL, "Failed to create connection.\n");
+		return 1;
+	}
+
+	dbi_conn_error_handler( conn, db_error_func, NULL );
+
+	/* MySQL
+	dbi_conn_set_option(conn, "host", "localhost");
+	dbi_conn_set_option(conn, "username", "your_name");
+	dbi_conn_set_option(conn, "password", "your_password");
+	dbi_conn_set_option(conn, "dbname", "your_dbname");
+	dbi_conn_set_option(conn, "encoding", "UTF-8");
+	*/
+
+	/* SqLite 3 */
+	db_basename = strdup(name);
+	db_dirname = strdup(name);
+	dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname));
+	dbi_conn_set_option(conn, "dbname", basename(db_basename));
+
+	if (dbi_conn_connect(conn) < 0)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	free(db_dirname);
+	free(db_basename);
+	db_dirname = db_basename = NULL;
+	return -1;
+}
+
+
+int db_prepare()
+{
+	dbi_result result;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
+		result = dbi_conn_query(conn, create_stmts[i]);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR,
+			     "Failed to create some table.\n");
+			return 1;
+		}
+		dbi_result_free(result);
+	}
+
+	if (check_db_revision() < 0) {
+		LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, "
+			"please update your database schema\n");
+                return -1;
+	}
+
+	return 0;
+}
+
+int db_fini()
+{
+	dbi_conn_close(conn);
+	dbi_shutdown();
+
+	if (db_dirname)
+	    free(db_dirname);
+	if (db_basename)
+	    free(db_basename);
+	return 0;
+}
+
+struct gsm_subscriber *db_create_subscriber(struct gsm_network *net, char *imsi)
+{
+	dbi_result result;
+	struct gsm_subscriber *subscr;
+
+	/* Is this subscriber known in the db? */
+	subscr = db_get_subscriber(net, GSM_SUBSCRIBER_IMSI, imsi);
+	if (subscr) {
+		result = dbi_conn_queryf(conn,
+                         "UPDATE Subscriber set updated = datetime('now') "
+                         "WHERE imsi = %s " , imsi);
+		if (!result)
+			LOGP(DDB, LOGL_ERROR, "failed to update timestamp\n");
+		else
+			dbi_result_free(result);
+		return subscr;
+	}
+
+	subscr = subscr_alloc();
+	subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
+	if (!subscr)
+		return NULL;
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO Subscriber "
+		"(imsi, created, updated) "
+		"VALUES "
+		"(%s, datetime('now'), datetime('now')) ",
+		imsi
+	);
+	if (!result)
+		LOGP(DDB, LOGL_ERROR, "Failed to create Subscriber by IMSI.\n");
+	subscr->net = net;
+	subscr->id = dbi_conn_sequence_last(conn, NULL);
+	strncpy(subscr->imsi, imsi, GSM_IMSI_LENGTH-1);
+	dbi_result_free(result);
+	LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi);
+	db_subscriber_alloc_exten(subscr);
+	return subscr;
+}
+
+static_assert(sizeof(unsigned char) == sizeof(struct gsm48_classmark1), classmark1_size);
+
+static int get_equipment_by_subscr(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	const char *string;
+	unsigned char cm1;
+	const unsigned char *cm2, *cm3;
+	struct gsm_equipment *equip = &subscr->equipment;
+
+	result = dbi_conn_queryf(conn,
+				"SELECT equipment.* FROM Equipment,EquipmentWatch "
+				"WHERE EquipmentWatch.equipment_id=Equipment.id "
+				"AND EquipmentWatch.subscriber_id = %llu "
+				"ORDER BY updated DESC", subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	equip->id = dbi_result_get_ulonglong(result, "id");
+
+	string = dbi_result_get_string(result, "imei");
+	if (string)
+		strncpy(equip->imei, string, sizeof(equip->imei));
+
+	string = dbi_result_get_string(result, "classmark1");
+	if (string) {
+		cm1 = atoi(string) & 0xff;
+		memcpy(&equip->classmark1, &cm1, sizeof(equip->classmark1));
+	}
+
+	equip->classmark2_len = dbi_result_get_field_length(result, "classmark2");
+	cm2 = dbi_result_get_binary(result, "classmark2");
+	if (equip->classmark2_len > sizeof(equip->classmark2))
+		equip->classmark2_len = sizeof(equip->classmark2);
+	memcpy(equip->classmark2, cm2, equip->classmark2_len);
+
+	equip->classmark3_len = dbi_result_get_field_length(result, "classmark3");
+	cm3 = dbi_result_get_binary(result, "classmark3");
+	if (equip->classmark3_len > sizeof(equip->classmark3))
+		equip->classmark3_len = sizeof(equip->classmark3);
+	memcpy(equip->classmark3, cm3, equip->classmark3_len);
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int get_authinfo_by_subscr(struct gsm_auth_info *ainfo,
+			   struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	const unsigned char *a3a8_ki;
+
+	result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthKeys WHERE subscriber_id=%u",
+			 subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	ainfo->auth_algo = dbi_result_get_ulonglong(result, "algorithm_id");
+	ainfo->a3a8_ki_len = dbi_result_get_field_length(result, "a3a8_ki");
+	a3a8_ki = dbi_result_get_binary(result, "a3a8_ki");
+	if (ainfo->a3a8_ki_len > sizeof(ainfo->a3a8_ki))
+		ainfo->a3a8_ki_len = sizeof(ainfo->a3a8_ki_len);
+	memcpy(ainfo->a3a8_ki, a3a8_ki, ainfo->a3a8_ki_len);
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int set_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+			    struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	struct gsm_auth_info ainfo_old;
+	int rc, upd;
+	unsigned char *ki_str;
+
+	/* Deletion ? */
+	if (ainfo == NULL) {
+		result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthKeys WHERE subscriber_id=%u",
+			subscr->id);
+
+		if (!result)
+			return -EIO;
+
+		dbi_result_free(result);
+
+		return 0;
+	}
+
+	/* Check if already existing */
+	rc = get_authinfo_by_subscr(&ainfo_old, subscr);
+	if (rc && rc != -ENOENT)
+		return rc;
+	upd = rc ? 0 : 1;
+
+	/* Update / Insert */
+	dbi_conn_quote_binary_copy(conn,
+		ainfo->a3a8_ki, ainfo->a3a8_ki_len, &ki_str);
+
+	if (!upd) {
+		result = dbi_conn_queryf(conn,
+				"INSERT INTO AuthKeys "
+				"(subscriber_id, algorithm_id, a3a8_ki) "
+				"VALUES (%u, %u, %s)",
+				subscr->id, ainfo->auth_algo, ki_str);
+	} else {
+		result = dbi_conn_queryf(conn,
+				"UPDATE AuthKeys "
+				"SET algorithm_id=%u, a3a8_ki=%s "
+				"WHERE subscriber_id=%u",
+				ainfo->auth_algo, ki_str, subscr->id);
+	}
+
+	free(ki_str);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int get_authtuple_by_subscr(struct gsm_auth_tuple *atuple,
+			    struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	int len;
+	const unsigned char *blob;
+
+	result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthTuples WHERE subscriber_id=%u",
+			subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	memset(atuple, 0, sizeof(atuple));
+
+	atuple->use_count = dbi_result_get_ulonglong(result, "use_count");
+	atuple->key_seq = dbi_result_get_ulonglong(result, "key_seq");
+
+	len = dbi_result_get_field_length(result, "rand");
+	if (len != sizeof(atuple->rand))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "rand");
+	memcpy(atuple->rand, blob, len);
+
+	len = dbi_result_get_field_length(result, "sres");
+	if (len != sizeof(atuple->sres))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "sres");
+	memcpy(atuple->sres, blob, len);
+
+	len = dbi_result_get_field_length(result, "kc");
+	if (len != sizeof(atuple->kc))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "kc");
+	memcpy(atuple->kc, blob, len);
+
+	dbi_result_free(result);
+
+	return 0;
+
+err_size:
+	dbi_result_free(result);
+	return -EIO;
+}
+
+int set_authtuple_for_subscr(struct gsm_auth_tuple *atuple,
+			     struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	int rc, upd;
+	struct gsm_auth_tuple atuple_old;
+	unsigned char *rand_str, *sres_str, *kc_str;
+
+	/* Deletion ? */
+	if (atuple == NULL) {
+		result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthTuples WHERE subscriber_id=%u",
+			subscr->id);
+
+		if (!result)
+			return -EIO;
+
+		dbi_result_free(result);
+
+		return 0;
+	}
+
+	/* Check if already existing */
+	rc = get_authtuple_by_subscr(&atuple_old, subscr);
+	if (rc && rc != -ENOENT)
+		return rc;
+	upd = rc ? 0 : 1;
+
+	/* Update / Insert */
+	dbi_conn_quote_binary_copy(conn,
+		atuple->rand, sizeof(atuple->rand), &rand_str);
+	dbi_conn_quote_binary_copy(conn,
+		atuple->sres, sizeof(atuple->sres), &sres_str);
+	dbi_conn_quote_binary_copy(conn,
+		atuple->kc, sizeof(atuple->kc), &kc_str);
+
+	if (!upd) {
+		result = dbi_conn_queryf(conn,
+				"INSERT INTO AuthTuples "
+				"(subscriber_id, issued, use_count, "
+				 "key_seq, rand, sres, kc) "
+				"VALUES (%u, datetime('now'), %u, "
+				 "%u, %s, %s, %s ) ",
+				subscr->id, atuple->use_count, atuple->key_seq,
+				rand_str, sres_str, kc_str);
+	} else {
+		char *issued = atuple->key_seq == atuple_old.key_seq ?
+					"issued" : "datetime('now')";
+		result = dbi_conn_queryf(conn,
+				"UPDATE AuthKeys "
+				"SET issued=%s, use_count=%u, "
+				 "key_seq=%u, rand=%s, sres=%s, kc=%s "
+				"WHERE subscriber_id = %u",
+				issued, atuple->use_count, atuple->key_seq,
+				rand_str, sres_str, kc_str, subscr->id);
+	}
+
+	free(rand_str);
+	free(sres_str);
+	free(kc_str);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+#define BASE_QUERY "SELECT * FROM Subscriber "
+struct gsm_subscriber *db_get_subscriber(struct gsm_network *net,
+					 enum gsm_subscriber_field field,
+					 const char *id)
+{
+	dbi_result result;
+	const char *string;
+	char *quoted;
+	struct gsm_subscriber *subscr;
+
+	switch (field) {
+	case GSM_SUBSCRIBER_IMSI:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE imsi = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_TMSI:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE tmsi = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_EXTENSION:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE extension = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_ID:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE id = %s ", quoted);
+		free(quoted);
+		break;
+	default:
+		LOGP(DDB, LOGL_NOTICE, "Unknown query selector for Subscriber.\n");
+		return NULL;
+	}
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber.\n");
+		return NULL;
+	}
+	if (!dbi_result_next_row(result)) {
+		DEBUGP(DDB, "Failed to find the Subscriber. '%u' '%s'\n",
+			field, id);
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	subscr = subscr_alloc();
+	subscr->net = net;
+	subscr->id = dbi_result_get_ulonglong(result, "id");
+	string = dbi_result_get_string(result, "imsi");
+	if (string)
+		strncpy(subscr->imsi, string, GSM_IMSI_LENGTH);
+
+	string = dbi_result_get_string(result, "tmsi");
+	if (string)
+		subscr->tmsi = tmsi_from_string(string);
+
+	string = dbi_result_get_string(result, "name");
+	if (string)
+		strncpy(subscr->name, string, GSM_NAME_LENGTH);
+
+	string = dbi_result_get_string(result, "extension");
+	if (string)
+		strncpy(subscr->extension, string, GSM_EXTENSION_LENGTH);
+
+	subscr->lac = dbi_result_get_uint(result, "lac");
+	subscr->authorized = dbi_result_get_uint(result, "authorized");
+	DEBUGP(DDB, "Found Subscriber: ID %llu, IMSI %s, NAME '%s', TMSI %u, EXTEN '%s', LAC %hu, AUTH %u\n",
+		subscr->id, subscr->imsi, subscr->name, subscr->tmsi, subscr->extension,
+		subscr->lac, subscr->authorized);
+	dbi_result_free(result);
+
+	get_equipment_by_subscr(subscr);
+
+	return subscr;
+}
+
+int db_sync_subscriber(struct gsm_subscriber *subscriber)
+{
+	dbi_result result;
+	char tmsi[14];
+	char *q_tmsi;
+
+	if (subscriber->tmsi != GSM_RESERVED_TMSI) {
+		sprintf(tmsi, "%u", subscriber->tmsi);
+		dbi_conn_quote_string_copy(conn,
+				   tmsi,
+				   &q_tmsi);
+	} else
+		q_tmsi = strdup("NULL");
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE Subscriber "
+		"SET updated = datetime('now'), "
+		"name = '%s', "
+		"extension = '%s', "
+		"authorized = %i, "
+		"tmsi = %s, "
+		"lac = %i "
+		"WHERE imsi = %s ",
+		subscriber->name,
+		subscriber->extension,
+		subscriber->authorized,
+		q_tmsi,
+		subscriber->lac,
+		subscriber->imsi);
+
+	free(q_tmsi);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to update Subscriber (by IMSI).\n");
+		return 1;
+	}
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_sync_equipment(struct gsm_equipment *equip)
+{
+	dbi_result result;
+	unsigned char *cm2, *cm3;
+	u_int8_t classmark1;
+
+	memcpy(&classmark1, &equip->classmark1, sizeof(classmark1));
+	DEBUGP(DDB, "Sync Equipment IMEI=%s, classmark1=%02x",
+		equip->imei, classmark1);
+	if (equip->classmark2_len)
+		DEBUGPC(DDB, ", classmark2=%s",
+			hexdump(equip->classmark2, equip->classmark2_len));
+	if (equip->classmark3_len)
+		DEBUGPC(DDB, ", classmark3=%s",
+			hexdump(equip->classmark3, equip->classmark3_len));
+	DEBUGPC(DDB, "\n");
+
+	dbi_conn_quote_binary_copy(conn, equip->classmark2,
+				   equip->classmark2_len, &cm2);
+	dbi_conn_quote_binary_copy(conn, equip->classmark3,
+				   equip->classmark3_len, &cm3);
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE Equipment SET "
+			"updated = datetime('now'), "
+			"classmark1 = %u, "
+			"classmark2 = %s, "
+			"classmark3 = %s "
+		"WHERE imei = '%s' ",
+		classmark1, cm2, cm3, equip->imei);
+
+	free(cm2);
+	free(cm3);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to update Equipment\n");
+		return -EIO;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber)
+{
+	dbi_result result = NULL;
+	char tmsi[14];
+	char* tmsi_quoted;
+
+	for (;;) {
+		subscriber->tmsi = rand();
+		if (subscriber->tmsi == GSM_RESERVED_TMSI)
+			continue;
+
+		sprintf(tmsi, "%u", subscriber->tmsi);
+		dbi_conn_quote_string_copy(conn, tmsi, &tmsi_quoted);
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM Subscriber "
+			"WHERE tmsi = %s ",
+			tmsi_quoted);
+
+		free(tmsi_quoted);
+
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+				"while allocating new TMSI.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)) {
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			DEBUGP(DDB, "Allocated TMSI %u for IMSI %s.\n",
+				subscriber->tmsi, subscriber->imsi);
+			return db_sync_subscriber(subscriber);
+		}
+		dbi_result_free(result);
+	}
+	return 0;
+}
+
+int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber)
+{
+	dbi_result result = NULL;
+	u_int32_t try;
+
+	for (;;) {
+		try = (rand()%(GSM_MAX_EXTEN-GSM_MIN_EXTEN+1)+GSM_MIN_EXTEN);
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM Subscriber "
+			"WHERE extension = %i",
+			try
+		);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+				"while allocating new extension.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)){
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			break;
+		}
+		dbi_result_free(result);
+	}
+	sprintf(subscriber->extension, "%i", try);
+	DEBUGP(DDB, "Allocated extension %i for IMSI %s.\n", try, subscriber->imsi);
+	return db_sync_subscriber(subscriber);
+}
+/*
+ * try to allocate a new unique token for this subscriber and return it
+ * via a parameter. if the subscriber already has a token, return
+ * an error.
+ */
+
+int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, u_int32_t *token)
+{
+	dbi_result result;
+	u_int32_t try;
+
+	for (;;) {
+		try = rand();
+		if (!try) /* 0 is an invalid token */
+			continue;
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthToken "
+			"WHERE subscriber_id = %llu OR token = \"%08X\" ",
+			subscriber->id, try);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query AuthToken "
+				"while allocating new token.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)) {
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			break;
+		}
+		dbi_result_free(result);
+	}
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO AuthToken "
+		"(subscriber_id, created, token) "
+		"VALUES "
+		"(%llu, datetime('now'), \"%08X\") ",
+		subscriber->id, try);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create token %08X for "
+			"IMSI %s.\n", try, subscriber->imsi);
+		return 1;
+	}
+	dbi_result_free(result);
+	*token = try;
+	DEBUGP(DDB, "Allocated token %08X for IMSI %s.\n", try, subscriber->imsi);
+
+	return 0;
+}
+
+int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char imei[GSM_IMEI_LENGTH])
+{
+	unsigned long long equipment_id, watch_id;
+	dbi_result result;
+
+	strncpy(subscriber->equipment.imei, imei,
+		sizeof(subscriber->equipment.imei)-1),
+
+	result = dbi_conn_queryf(conn,
+		"INSERT OR IGNORE INTO Equipment "
+		"(imei, created, updated) "
+		"VALUES "
+		"(%s, datetime('now'), datetime('now')) ",
+		imei);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create Equipment by IMEI.\n");
+		return 1;
+	}
+
+	equipment_id = 0;
+	if (dbi_result_get_numrows_affected(result)) {
+		equipment_id = dbi_conn_sequence_last(conn, NULL);
+	}
+	dbi_result_free(result);
+
+	if (equipment_id)
+		DEBUGP(DDB, "New Equipment: ID %llu, IMEI %s\n", equipment_id, imei);
+	else {
+		result = dbi_conn_queryf(conn,
+			"SELECT id FROM Equipment "
+			"WHERE imei = %s ",
+			imei
+		);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Equipment by IMEI.\n");
+			return 1;
+		}
+		if (!dbi_result_next_row(result)) {
+			LOGP(DDB, LOGL_ERROR, "Failed to find the Equipment.\n");
+			dbi_result_free(result);
+			return 1;
+		}
+		equipment_id = dbi_result_get_ulonglong(result, "id");
+		dbi_result_free(result);
+	}
+
+	result = dbi_conn_queryf(conn,
+		"INSERT OR IGNORE INTO EquipmentWatch "
+		"(subscriber_id, equipment_id, created, updated) "
+		"VALUES "
+		"(%llu, %llu, datetime('now'), datetime('now')) ",
+		subscriber->id, equipment_id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create EquipmentWatch.\n");
+		return 1;
+	}
+
+	watch_id = 0;
+	if (dbi_result_get_numrows_affected(result))
+		watch_id = dbi_conn_sequence_last(conn, NULL);
+
+	dbi_result_free(result);
+	if (watch_id)
+		DEBUGP(DDB, "New EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+			equipment_id, subscriber->imsi, imei);
+	else {
+		result = dbi_conn_queryf(conn,
+			"UPDATE EquipmentWatch "
+			"SET updated = datetime('now') "
+			"WHERE subscriber_id = %llu AND equipment_id = %llu ",
+			subscriber->id, equipment_id);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to update EquipmentWatch.\n");
+			return 1;
+		}
+		dbi_result_free(result);
+		DEBUGP(DDB, "Updated EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+			equipment_id, subscriber->imsi, imei);
+	}
+
+	return 0;
+}
+
+/* store an [unsent] SMS to the database */
+int db_sms_store(struct gsm_sms *sms)
+{
+	dbi_result result;
+	char *q_text, *q_daddr;
+	unsigned char *q_udata;
+	char *validity_timestamp = "2222-2-2";
+
+	/* FIXME: generate validity timestamp based on validity_minutes */
+
+	dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text);
+	dbi_conn_quote_string_copy(conn, (char *)sms->dest_addr, &q_daddr);
+	dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len,
+				   &q_udata);
+	/* FIXME: correct validity period */
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO SMS "
+		"(created, sender_id, receiver_id, valid_until, "
+		 "reply_path_req, status_rep_req, protocol_id, "
+		 "data_coding_scheme, ud_hdr_ind, dest_addr, "
+		 "user_data, text) VALUES "
+		"(datetime('now'), %llu, %llu, %u, "
+		 "%u, %u, %u, %u, %u, %s, %s, %s)",
+		sms->sender->id,
+		sms->receiver ? sms->receiver->id : 0, validity_timestamp,
+		sms->reply_path_req, sms->status_rep_req, sms->protocol_id,
+		sms->data_coding_scheme, sms->ud_hdr_ind,
+		q_daddr, q_udata, q_text);
+	free(q_text);
+	free(q_daddr);
+	free(q_udata);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result)
+{
+	struct gsm_sms *sms = sms_alloc();
+	long long unsigned int sender_id, receiver_id;
+	const char *text, *daddr;
+	const unsigned char *user_data;
+
+	if (!sms)
+		return NULL;
+
+	sms->id = dbi_result_get_ulonglong(result, "id");
+
+	sender_id = dbi_result_get_ulonglong(result, "sender_id");
+	sms->sender = subscr_get_by_id(net, sender_id);
+
+	receiver_id = dbi_result_get_ulonglong(result, "receiver_id");
+	sms->receiver = subscr_get_by_id(net, receiver_id);
+
+	/* FIXME: validity */
+	/* FIXME: those should all be get_uchar, but sqlite3 is braindead */
+	sms->reply_path_req = dbi_result_get_uint(result, "reply_path_req");
+	sms->status_rep_req = dbi_result_get_uint(result, "status_rep_req");
+	sms->ud_hdr_ind = dbi_result_get_uint(result, "ud_hdr_ind");
+	sms->protocol_id = dbi_result_get_uint(result, "protocol_id");
+	sms->data_coding_scheme = dbi_result_get_uint(result,
+						  "data_coding_scheme");
+	/* sms->msg_ref is temporary and not stored in DB */
+
+	daddr = dbi_result_get_string(result, "dest_addr");
+	if (daddr) {
+		strncpy(sms->dest_addr, daddr, sizeof(sms->dest_addr));
+		sms->dest_addr[sizeof(sms->dest_addr)-1] = '\0';
+	}
+
+	sms->user_data_len = dbi_result_get_field_length(result, "user_data");
+	user_data = dbi_result_get_binary(result, "user_data");
+	if (sms->user_data_len > sizeof(sms->user_data))
+		sms->user_data_len = (u_int8_t) sizeof(sms->user_data);
+	memcpy(sms->user_data, user_data, sms->user_data_len);
+
+	text = dbi_result_get_string(result, "text");
+	if (text) {
+		strncpy(sms->text, text, sizeof(sms->text));
+		sms->text[sizeof(sms->text)-1] = '\0';
+	}
+	return sms;
+}
+
+/* retrieve the next unsent SMS with ID >= min_id */
+struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT * FROM SMS,Subscriber "
+		"WHERE sms.id >= %llu AND sms.sent is NULL "
+			"AND sms.receiver_id = subscriber.id "
+			"AND subscriber.lac > 0 "
+		"ORDER BY sms.id LIMIT 1",
+		min_id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net, unsigned long long min_subscr_id)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT * FROM SMS,Subscriber "
+		"WHERE sms.receiver_id >= %llu AND sms.sent is NULL "
+			"AND sms.receiver_id = subscriber.id "
+			"AND subscriber.lac > 0 "
+		"ORDER BY sms.receiver_id, id LIMIT 1",
+		min_subscr_id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* retrieve the next unsent SMS for a given subscriber */
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT * FROM SMS,Subscriber "
+		"WHERE sms.receiver_id = %llu AND sms.sent is NULL "
+			"AND sms.receiver_id = subscriber.id "
+			"AND subscriber.lac > 0 "
+		"ORDER BY sms.id LIMIT 1",
+		subscr->id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(subscr->net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* mark a given SMS as read */
+int db_sms_mark_sent(struct gsm_sms *sms)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE SMS "
+		"SET sent = datetime('now') "
+		"WHERE id = %llu", sms->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id);
+		return 1;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+/* increase the number of attempted deliveries */
+int db_sms_inc_deliver_attempts(struct gsm_sms *sms)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE SMS "
+		"SET deliver_attempts = deliver_attempts + 1 "
+		"WHERE id = %llu", sms->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for "
+			"SMS %llu.\n", sms->id);
+		return 1;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_apdu_blob_store(struct gsm_subscriber *subscr,
+			u_int8_t apdu_id_flags, u_int8_t len,
+			u_int8_t *apdu)
+{
+	dbi_result result;
+	unsigned char *q_apdu;
+
+	dbi_conn_quote_binary_copy(conn, apdu, len, &q_apdu);
+
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO ApduBlobs "
+		"(created,subscriber_id,apdu_id_flags,apdu) VALUES "
+		"(datetime('now'),%llu,%u,%s)",
+		subscr->id, apdu_id_flags, q_apdu);
+
+	free(q_apdu);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_store_counter(struct counter *ctr)
+{
+	dbi_result result;
+	char *q_name;
+
+	dbi_conn_quote_string_copy(conn, ctr->name, &q_name);
+
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO Counters "
+		"(timestamp,name,value) VALUES "
+		"(datetime('now'),%s,%lu)", q_name, ctr->value);
+
+	free(q_name);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
diff --git a/openbsc/src/debug.c b/openbsc/src/debug.c
new file mode 100644
index 0000000..9218f64
--- /dev/null
+++ b/openbsc/src/debug.c
@@ -0,0 +1,203 @@
+/* OpenBSC Debugging/Logging support code */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <errno.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+#include <osmocore/logging.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+/* default categories */
+static const struct log_info_cat default_categories[] = {
+	[DRLL] = {
+		.name = "DRLL",
+		.description = "A-bis Radio Link Layer (RLL)",
+		.color = "\033[1;31m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DCC] = {
+		.name = "DCC",
+		.description = "Layer3 Call Control (CC)",
+		.color = "\033[1;32m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMM] = {
+		.name = "DMM",
+		.description = "Layer3 Mobility Management (MM)",
+		.color = "\033[1;33m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DRR] = {
+		.name = "DRR",
+		.description = "Layer3 Radio Resource (RR)",
+		.color = "\033[1;34m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DRSL] = {
+		.name = "DRSL",
+		.description = "A-bis Radio Siganlling Link (RSL)",
+		.color = "\033[1;35m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DNM] =	{
+		.name = "DNM",
+		.description = "A-bis Network Management / O&M (NM/OML)",
+		.color = "\033[1;36m",
+		.enabled = 1, .loglevel = LOGL_INFO,
+	},
+	[DMNCC] = {
+		.name = "DMNCC",
+		.description = "MNCC API for Call Control application",
+		.color = "\033[1;39m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DSMS] = {
+		.name = "DSMS",
+		.description = "Layer3 Short Message Service (SMS)",
+		.color = "\033[1;37m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DPAG]	= {
+		.name = "DPAG",
+		.description = "Paging Subsystem",
+		.color = "\033[1;38m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMEAS] = {
+		.name = "DMEAS",
+		.description = "Radio Measurement Processing",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DMI] = {
+		.name = "DMI",
+		.description = "A-bis Input Driver for Signalling",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DMIB] = {
+		.name = "DMIB",
+		.description = "A-bis Input Driver for B-Channels (voice)",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DMUX] = {
+		.name = "DMUX",
+		.description = "A-bis B-Subchannel TRAU Frame Multiplex",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DINP] = {
+		.name = "DINP",
+		.description = "A-bis Intput Subsystem",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DSCCP] = {
+		.name = "DSCCP",
+		.description = "SCCP Protocol",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMSC] = {
+		.name = "DMSC",
+		.description = "Mobile Switching Center",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMGCP] = {
+		.name = "DMGCP",
+		.description = "Media Gateway Control Protocol",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DHO] = {
+		.name = "DHO",
+		.description = "Hand-Over",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DDB] = {
+		.name = "DDB",
+		.description = "Database Layer",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DREF] = {
+		.name = "DREF",
+		.description = "Reference Counting",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DGPRS] = {
+		.name = "DGPRS",
+		.description = "GPRS Packet Service",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DNS] = {
+		.name = "DNS",
+		.description = "GPRS Network Service (NS)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DBSSGP] = {
+		.name = "DBSSGP",
+		.description = "GPRS BSS Gateway Protocol (BSSGP)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+};
+
+enum log_ctxt {
+	CTX_SUBSCRIBER,
+};
+
+enum log_filter {
+	_FLT_ALL = LOG_FILTER_ALL,	/* libosmocore */
+	FLT_IMSI = 1,
+};
+
+static int filter_fn(const struct log_context *ctx,
+		     struct log_target *tar)
+{
+	struct gsm_subscriber *subscr = ctx->ctx[CTX_SUBSCRIBER];
+
+	if ((tar->filter_map & (1 << FLT_IMSI)) != 0
+	    && subscr && strcmp(subscr->imsi, tar->filter_data[FLT_IMSI]) == 0)
+		return 1;
+
+	return 0;
+}
+
+const struct log_info log_info = {
+	.filter_fn = filter_fn,
+	.cat = default_categories,
+	.num_cat = ARRAY_SIZE(default_categories),
+};
+
+void log_set_imsi_filter(struct log_target *target, const char *imsi)
+{
+	if (imsi) {
+		target->filter_map |= (1 << FLT_IMSI);
+		target->filter_data[FLT_IMSI] = talloc_strdup(target, imsi);
+	} else if (target->filter_data[FLT_IMSI]) {
+		target->filter_map &= ~(1 << FLT_IMSI);
+		talloc_free(target->filter_data[FLT_IMSI]);
+		target->filter_data[FLT_IMSI] = NULL;
+	}
+}
diff --git a/openbsc/src/e1_config.c b/openbsc/src/e1_config.c
new file mode 100644
index 0000000..50fbcec
--- /dev/null
+++ b/openbsc/src/e1_config.c
@@ -0,0 +1,236 @@
+#include <string.h>
+#include <errno.h>
+
+#include <netinet/in.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/misdn.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+
+#define SAPI_L2ML	0
+#define SAPI_OML	62
+#define SAPI_RSL	0	/* 63 ? */
+
+/* The e1_reconfig_*() functions below tale the configuration present in the
+ * bts/trx/ts data structures and ensure the E1 configuration reflects the
+ * timeslot/subslot/TEI configuration */
+
+int e1_reconfig_ts(struct gsm_bts_trx_ts *ts)
+{
+	struct gsm_e1_subslot *e1_link = &ts->e1_link;
+	struct e1inp_line *line;
+	struct e1inp_ts *e1_ts;
+
+	DEBUGP(DMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+	if (!e1_link->e1_ts)
+		return 0;
+
+	line = e1inp_line_get_create(e1_link->e1_nr);
+	if (!line)
+		return -ENOMEM;
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+		e1_ts = &line->ts[e1_link->e1_ts-1];
+		e1inp_ts_config(e1_ts, line, E1INP_TS_TYPE_TRAU);
+		subch_demux_activate(&e1_ts->trau.demux, e1_link->e1_ts_ss);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+int e1_reconfig_trx(struct gsm_bts_trx *trx)
+{
+	struct gsm_e1_subslot *e1_link = &trx->rsl_e1_link;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_line *line;
+	struct e1inp_sign_link *rsl_link;
+	int i;
+
+	if (!e1_link->e1_ts)
+		return -EINVAL;
+
+	/* RSL Link */
+	line = e1inp_line_get_create(e1_link->e1_nr);
+	if (!line)
+		return -ENOMEM;
+	sign_ts = &line->ts[e1_link->e1_ts-1];
+	e1inp_ts_config(sign_ts, line, E1INP_TS_TYPE_SIGN);
+	rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, trx->rsl_tei, SAPI_RSL);
+	if (!rsl_link)
+		return -ENOMEM;
+	if (trx->rsl_link)
+		e1inp_sign_link_destroy(trx->rsl_link);
+	trx->rsl_link = rsl_link;
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		e1_reconfig_ts(&trx->ts[i]);
+
+	return 0;
+}
+
+int e1_reconfig_bts(struct gsm_bts *bts)
+{
+	struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_line *line;
+	struct e1inp_sign_link *oml_link;
+	struct gsm_bts_trx *trx;
+
+	DEBUGP(DMI, "e1_reconfig_bts(%u)\n", bts->nr);
+
+	if (!e1_link->e1_ts)
+		return -EINVAL;
+
+	/* OML link */
+	line = e1inp_line_get_create(e1_link->e1_nr);
+	if (!line)
+		return -ENOMEM;
+	sign_ts = &line->ts[e1_link->e1_ts-1];
+	e1inp_ts_config(sign_ts, line, E1INP_TS_TYPE_SIGN);
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  bts->c0, bts->oml_tei, SAPI_OML);
+	if (!oml_link)
+		return -ENOMEM;
+	if (bts->oml_link)
+		e1inp_sign_link_destroy(bts->oml_link);
+	bts->oml_link = oml_link;
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		e1_reconfig_trx(trx);
+
+	/* notify E1 input something has changed */
+	return e1inp_line_update(line);
+}
+
+#if 0
+/* do some compiled-in configuration for our BTS/E1 setup */
+int e1_config(struct gsm_bts *bts, int cardnr, int release_l2)
+{
+	struct e1inp_line *line;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_sign_link *oml_link, *rsl_link;
+	struct gsm_bts_trx *trx = bts->c0;
+	int base_ts;
+
+	switch (bts->nr) {
+	case 0:
+		/* First BTS uses E1 TS 01,02,03,04,05 */
+		base_ts = HARDCODED_BTS0_TS - 1;
+		break;
+	case 1:
+		/* Second BTS uses E1 TS 06,07,08,09,10 */
+		base_ts = HARDCODED_BTS1_TS - 1;
+		break;
+	case 2:
+		/* Third BTS uses E1 TS 11,12,13,14,15 */
+		base_ts = HARDCODED_BTS2_TS - 1;
+	default:
+		return -EINVAL;
+	}
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return -ENOMEM;
+
+	/* create E1 timeslots for signalling and TRAU frames */
+	e1inp_ts_config(&line->ts[base_ts+1-1], line, E1INP_TS_TYPE_SIGN);
+	e1inp_ts_config(&line->ts[base_ts+2-1], line, E1INP_TS_TYPE_TRAU);
+	e1inp_ts_config(&line->ts[base_ts+3-1], line, E1INP_TS_TYPE_TRAU);
+
+	/* create signalling links for TS1 */
+	sign_ts = &line->ts[base_ts+1-1];
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  trx, TEI_OML, SAPI_OML);
+	rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, TEI_RSL, SAPI_RSL);
+
+	/* create back-links from bts/trx */
+	bts->oml_link = oml_link;
+	trx->rsl_link = rsl_link;
+
+	/* enable subchannel demuxer on TS2 */
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 1);
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 2);
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 3);
+
+	/* enable subchannel demuxer on TS3 */
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 0);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 1);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 2);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 3);
+
+	trx = gsm_bts_trx_num(bts, 1);
+	if (trx) {
+		/* create E1 timeslots for TRAU frames of TRX1 */
+		e1inp_ts_config(&line->ts[base_ts+4-1], line, E1INP_TS_TYPE_TRAU);
+		e1inp_ts_config(&line->ts[base_ts+5-1], line, E1INP_TS_TYPE_TRAU);
+
+		/* create RSL signalling link for TRX1 */
+		sign_ts = &line->ts[base_ts+1-1];
+		rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, TEI_RSL+1, SAPI_RSL);
+		/* create back-links from trx */
+		trx->rsl_link = rsl_link;
+
+		/* enable subchannel demuxer on TS2 */
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 0);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 1);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 2);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 3);
+
+		/* enable subchannel demuxer on TS3 */
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 0);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 1);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 2);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 3);
+	}
+
+	return mi_setup(cardnr, line, release_l2);
+}
+#endif
+
+/* configure pseudo E1 line in ip.access style and connect to BTS */
+int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin)
+{
+	struct e1inp_line *line;
+	struct e1inp_ts *sign_ts, *rsl_ts;
+	struct e1inp_sign_link *oml_link, *rsl_link;
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return -ENOMEM;
+
+	/* create E1 timeslots for signalling and TRAU frames */
+	e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN);
+	e1inp_ts_config(&line->ts[2-1], line, E1INP_TS_TYPE_SIGN);
+
+	/* create signalling links for TS1 */
+	sign_ts = &line->ts[1-1];
+	rsl_ts = &line->ts[2-1];
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  bts->c0, 0xff, 0);
+	rsl_link = e1inp_sign_link_create(rsl_ts, E1INP_SIGN_RSL,
+					  bts->c0, 0, 0);
+
+	/* create back-links from bts/trx */
+	bts->oml_link = oml_link;
+	bts->c0->rsl_link = rsl_link;
+
+	/* default port at BTS for incoming connections is 3006 */
+	if (sin->sin_port == 0)
+		sin->sin_port = htons(3006);
+
+	return ipaccess_connect(line, sin);
+}
diff --git a/openbsc/src/e1_input.c b/openbsc/src/e1_input.c
new file mode 100644
index 0000000..b1dfe9b
--- /dev/null
+++ b/openbsc/src/e1_input.c
@@ -0,0 +1,565 @@
+/* OpenBSC Abis interface to E1 */
+
+/* (C) 2008-2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <mISDNif.h>
+
+//#define AF_COMPATIBILITY_FUNC
+//#include <compat_af_isdn.h>
+#ifndef AF_ISDN
+#define AF_ISDN 34
+#define PF_ISDN AF_ISDN
+#endif
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <osmocore/linuxlist.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/misdn.h>
+
+#define NUM_E1_TS	32
+
+/* list of all E1 drivers */
+LLIST_HEAD(e1inp_driver_list);
+
+/* list of all E1 lines */
+LLIST_HEAD(e1inp_line_list);
+
+static void *tall_sigl_ctx;
+
+/* to be implemented, e.g. by bsc_hack.c */
+void input_event(int event, enum e1inp_sign_type type, struct gsm_bts_trx *trx);
+
+/*
+ * pcap writing of the misdn load
+ * pcap format is from http://wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+#define DLT_LINUX_LAPD		177
+#define PCAP_INPUT		0
+#define PCAP_OUTPUT		1
+
+struct pcap_hdr {
+	u_int32_t magic_number;
+	u_int16_t version_major;
+	u_int16_t version_minor;
+	int32_t  thiszone;
+	u_int32_t sigfigs;
+	u_int32_t snaplen;
+	u_int32_t network;
+} __attribute__((packed));
+
+struct pcaprec_hdr {
+	u_int32_t ts_sec;
+	u_int32_t ts_usec;
+	u_int32_t incl_len;
+	u_int32_t orig_len;
+} __attribute__((packed));
+
+struct fake_linux_lapd_header {
+        u_int16_t pkttype;
+	u_int16_t hatype;
+	u_int16_t halen;
+	u_int64_t addr;
+	int16_t protocol;
+} __attribute__((packed));
+
+struct lapd_header {
+	u_int8_t ea1 : 1;
+	u_int8_t cr : 1;
+	u_int8_t sapi : 6;
+	u_int8_t ea2 : 1;
+	u_int8_t tei : 7;
+	u_int8_t control_foo; /* fake UM's ... */
+} __attribute__((packed));
+
+static_assert((int)&((struct fake_linux_lapd_header*)NULL)->hatype == 2,	hatype_offset);
+static_assert((int)&((struct fake_linux_lapd_header*)NULL)->halen == 4,		halen_offset);
+static_assert((int)&((struct fake_linux_lapd_header*)NULL)->addr == 6,		addr_offset);
+static_assert((int)&((struct fake_linux_lapd_header*)NULL)->protocol == 14,	proto_offset);
+static_assert(sizeof(struct fake_linux_lapd_header) == 16,			lapd_header_size);
+
+
+static int pcap_fd = -1;
+
+void e1_set_pcap_fd(int fd)
+{
+	int ret;
+	struct pcap_hdr header = {
+		.magic_number	= 0xa1b2c3d4,
+		.version_major	= 2,
+		.version_minor	= 4,
+		.thiszone	= 0,
+		.sigfigs	= 0,
+		.snaplen	= 65535,
+		.network	= DLT_LINUX_LAPD,
+	};
+
+	pcap_fd = fd;
+	ret = write(pcap_fd, &header, sizeof(header));
+}
+
+/* This currently only works for the D-Channel */
+static void write_pcap_packet(int direction, int sapi, int tei,
+			      struct msgb *msg) {
+	if (pcap_fd < 0)
+		return;
+
+	int ret;
+	time_t cur_time;
+	struct tm *tm;
+	int mi_head = (direction==PCAP_INPUT) ? MISDN_HEADER_LEN : 0;
+
+	struct fake_linux_lapd_header header = {
+		.pkttype	= 4,
+		.hatype		= 0,
+		.halen		= 0,
+		.addr		= direction == PCAP_OUTPUT ? 0x0 : 0x1,
+		.protocol	= ntohs(48),
+	};
+
+	struct lapd_header lapd_header = {
+		.ea1		= 0,
+		.cr		= direction == PCAP_OUTPUT ? 1 : 0,
+		.sapi		= sapi & 0x3F,
+		.ea2		= 1,
+		.tei		= tei & 0x7F,
+		.control_foo	= 0x03 /* UI */,
+	};	
+
+	struct pcaprec_hdr payload_header = {
+		.ts_sec	    = 0,
+		.ts_usec    = 0,
+		.incl_len   = msg->len + sizeof(struct fake_linux_lapd_header)
+				+ sizeof(struct lapd_header)
+				- mi_head,
+		.orig_len   = msg->len + sizeof(struct fake_linux_lapd_header)
+				+ sizeof(struct lapd_header)
+				- mi_head,
+	};
+
+
+	cur_time = time(NULL);
+	tm = localtime(&cur_time);
+	payload_header.ts_sec = mktime(tm);
+
+	ret = write(pcap_fd, &payload_header, sizeof(payload_header));
+	ret = write(pcap_fd, &header, sizeof(header));
+	ret = write(pcap_fd, &lapd_header, sizeof(lapd_header));
+	ret = write(pcap_fd, msg->data + mi_head,
+			msg->len - mi_head);
+}
+
+static const char *sign_types[] = {
+	[E1INP_SIGN_NONE]	= "None",
+	[E1INP_SIGN_OML]	= "OML",
+	[E1INP_SIGN_RSL]	= "RSL",
+};
+const char *e1inp_signtype_name(enum e1inp_sign_type tp)
+{
+	if (tp >= ARRAY_SIZE(sign_types))
+		return "undefined";
+	return sign_types[tp];
+}
+
+static const char *ts_types[] = {
+	[E1INP_TS_TYPE_NONE]	= "None",
+	[E1INP_TS_TYPE_SIGN]	= "Signalling",
+	[E1INP_TS_TYPE_TRAU]	= "TRAU",
+};
+
+const char *e1inp_tstype_name(enum e1inp_ts_type tp)
+{
+	if (tp >= ARRAY_SIZE(ts_types))
+		return "undefined";
+	return ts_types[tp];
+}
+
+/* callback when a TRAU frame was received */
+static int subch_cb(struct subch_demux *dmx, int ch, u_int8_t *data, int len,
+		    void *_priv)
+{
+	struct e1inp_ts *e1i_ts = _priv;
+	struct gsm_e1_subslot src_ss;
+
+	src_ss.e1_nr = e1i_ts->line->num;
+	src_ss.e1_ts = e1i_ts->num;
+	src_ss.e1_ts_ss = ch;
+
+	return trau_mux_input(&src_ss, data, len);
+}
+
+int abis_rsl_sendmsg(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link;
+	struct e1inp_driver *e1inp_driver;
+	struct e1inp_ts *e1i_ts;
+
+	msg->l2h = msg->data;
+
+	if (!msg->trx) {
+		LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx == NULL: %s\n",
+			hexdump(msg->data, msg->len));
+		talloc_free(msg);
+		return -EINVAL;
+	} else if (!msg->trx->rsl_link) {
+		LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx->rsl_link == NULL: %s\n",
+			hexdump(msg->data, msg->len));
+		talloc_free(msg);
+		return -EIO;
+	}
+
+	sign_link = msg->trx->rsl_link;
+	e1i_ts = sign_link->ts;
+	if (!bsc_timer_pending(&e1i_ts->sign.tx_timer)) {
+		/* notify the driver we have something to write */
+		e1inp_driver = sign_link->ts->line->driver;
+		e1inp_driver->want_write(e1i_ts);
+	}
+	msgb_enqueue(&sign_link->tx_list, msg);
+
+	/* dump it */
+	write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg);
+
+	return 0;
+}
+
+int _abis_nm_sendmsg(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link;
+	struct e1inp_driver *e1inp_driver;
+	struct e1inp_ts *e1i_ts;
+
+	msg->l2h = msg->data;
+
+	if (!msg->trx || !msg->trx->bts || !msg->trx->bts->oml_link) {
+		LOGP(DRSL, LOGL_ERROR, "nm_sendmsg: msg->trx == NULL\n");
+		return -EINVAL;
+	}
+
+	sign_link = msg->trx->bts->oml_link;
+	e1i_ts = sign_link->ts;
+	if (!bsc_timer_pending(&e1i_ts->sign.tx_timer)) {
+		/* notify the driver we have something to write */
+		e1inp_driver = sign_link->ts->line->driver;
+		e1inp_driver->want_write(e1i_ts);
+	}
+	msgb_enqueue(&sign_link->tx_list, msg);
+
+	/* dump it */
+	write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg);
+
+	return 0;
+}
+
+/* Timeslot */
+
+/* configure and initialize one e1inp_ts */
+int e1inp_ts_config(struct e1inp_ts *ts, struct e1inp_line *line,
+		    enum e1inp_ts_type type)
+{
+	if (ts->type == type && ts->line && line)
+		return 0;
+
+	ts->type = type;
+	ts->line = line;
+
+	switch (type) {
+	case E1INP_TS_TYPE_SIGN:
+		INIT_LLIST_HEAD(&ts->sign.sign_links);
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		subchan_mux_init(&ts->trau.mux);
+		ts->trau.demux.out_cb = subch_cb;
+		ts->trau.demux.data = ts;
+		subch_demux_init(&ts->trau.demux);
+		break;
+	default:
+		LOGP(DMI, LOGL_ERROR, "unsupported E1 timeslot type %u\n",
+			ts->type);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct e1inp_line *e1inp_line_get(u_int8_t e1_nr)
+{
+	struct e1inp_line *e1i_line;
+
+	/* iterate over global list of e1 lines */
+	llist_for_each_entry(e1i_line, &e1inp_line_list, list) {
+		if (e1i_line->num == e1_nr)
+			return e1i_line;
+	}
+	return NULL;
+}
+
+struct e1inp_line *e1inp_line_get_create(u_int8_t e1_nr)
+{
+	struct e1inp_line *line;
+	int i;
+
+	line = e1inp_line_get(e1_nr);
+	if (line)
+		return line;
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return NULL;
+
+	line->num = e1_nr;
+	for (i = 0; i < NUM_E1_TS; i++) {
+		line->ts[i].num = i+1;
+		line->ts[i].line = line;
+	}
+	llist_add_tail(&line->list, &e1inp_line_list);
+
+	return line;
+}
+
+static struct e1inp_ts *e1inp_ts_get(u_int8_t e1_nr, u_int8_t ts_nr)
+{
+	struct e1inp_line *e1i_line;
+
+	e1i_line = e1inp_line_get(e1_nr);
+	if (!e1i_line)
+		return NULL;
+
+	return &e1i_line->ts[ts_nr-1];
+}
+
+struct subch_mux *e1inp_get_mux(u_int8_t e1_nr, u_int8_t ts_nr)
+{
+	struct e1inp_ts *e1i_ts = e1inp_ts_get(e1_nr, ts_nr);
+
+	if (!e1i_ts)
+		return NULL;
+
+	return &e1i_ts->trau.mux;
+}
+
+/* Signalling Link */
+
+struct e1inp_sign_link *e1inp_lookup_sign_link(struct e1inp_ts *e1i,
+					 	u_int8_t tei, u_int8_t sapi)
+{
+	struct e1inp_sign_link *link;
+
+	llist_for_each_entry(link, &e1i->sign.sign_links, list) {
+		if (link->sapi == sapi && link->tei == tei)
+			return link;
+	}
+
+	return NULL;
+}
+
+/* create a new signalling link in a E1 timeslot */
+
+struct e1inp_sign_link *
+e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type,
+			struct gsm_bts_trx *trx, u_int8_t tei,
+			u_int8_t sapi)
+{
+	struct e1inp_sign_link *link;
+
+	if (ts->type != E1INP_TS_TYPE_SIGN)
+		return NULL;
+
+	link = talloc_zero(tall_sigl_ctx, struct e1inp_sign_link);
+	if (!link)
+		return NULL;
+
+	link->ts = ts;
+	link->type = type;
+	INIT_LLIST_HEAD(&link->tx_list);
+	link->trx = trx;
+	link->tei = tei;
+	link->sapi = sapi;
+
+	llist_add_tail(&link->list, &ts->sign.sign_links);
+
+	return link;
+}
+
+void e1inp_sign_link_destroy(struct e1inp_sign_link *link)
+{
+	struct msgb *msg;
+
+	llist_del(&link->list);
+	while (!llist_empty(&link->tx_list)) {
+		msg = msgb_dequeue(&link->tx_list);
+		msgb_free(msg);
+	}
+
+	if (link->ts->type == E1INP_TS_TYPE_SIGN)
+		bsc_del_timer(&link->ts->sign.tx_timer);
+
+	talloc_free(link);
+}
+
+/* the E1 driver tells us he has received something on a TS */
+int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
+		u_int8_t tei, u_int8_t sapi)
+{
+	struct e1inp_sign_link *link;
+	int ret;
+
+	switch (ts->type) {
+	case E1INP_TS_TYPE_SIGN:
+		/* consult the list of signalling links */
+		write_pcap_packet(PCAP_INPUT, sapi, tei, msg);
+		link = e1inp_lookup_sign_link(ts, tei, sapi);
+		if (!link) {
+			LOGP(DMI, LOGL_ERROR, "didn't find signalling link for "
+				"tei %d, sapi %d\n", tei, sapi);
+			return -EINVAL;
+		}
+
+		log_set_context(BSC_CTX_BTS, link->trx->bts);
+		switch (link->type) {
+		case E1INP_SIGN_OML:
+			msg->trx = link->trx;
+			ret = abis_nm_rcvmsg(msg);
+			break;
+		case E1INP_SIGN_RSL:
+			msg->trx = link->trx;
+			ret = abis_rsl_rcvmsg(msg);
+			break;
+		default:
+			ret = -EINVAL;
+			LOGP(DMI, LOGL_ERROR, "unknown link type %u\n", link->type);
+			break;
+		}
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		ret = subch_demux_in(&ts->trau.demux, msg->l2h, msgb_l2len(msg));
+		break;
+	default:
+		ret = -EINVAL;
+		LOGP(DMI, LOGL_ERROR, "unknown TS type %u\n", ts->type);
+		break;
+	}
+
+	return ret;
+}
+
+#define TSX_ALLOC_SIZE 4096
+
+/* called by driver if it wants to transmit on a given TS */
+struct msgb *e1inp_tx_ts(struct e1inp_ts *e1i_ts,
+			 struct e1inp_sign_link **sign_link)
+{
+	struct e1inp_sign_link *link;
+	struct msgb *msg = NULL;
+	int len;
+
+	switch (e1i_ts->type) {
+	case E1INP_TS_TYPE_SIGN:
+		/* FIXME: implement this round robin */
+		llist_for_each_entry(link, &e1i_ts->sign.sign_links, list) {
+			msg = msgb_dequeue(&link->tx_list);
+			if (msg) {
+				if (sign_link)
+					*sign_link = link;
+				break;
+			}
+		}
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		msg = msgb_alloc(TSX_ALLOC_SIZE, "TRAU_TX");
+		if (!msg)
+			return NULL;
+		len = subchan_mux_out(&e1i_ts->trau.mux, msg->data, 40);
+		msgb_put(msg, 40);
+		break;
+	default:
+		LOGP(DMI, LOGL_ERROR, "unsupported E1 TS type %u\n", e1i_ts->type);
+		return NULL;
+	}
+	return msg;
+}
+
+/* called by driver in case some kind of link state event */
+int e1inp_event(struct e1inp_ts *ts, int evt, u_int8_t tei, u_int8_t sapi)
+{
+	struct e1inp_sign_link *link;
+
+	link = e1inp_lookup_sign_link(ts, tei, sapi);
+	if (!link)
+		return -EINVAL;
+
+	/* FIXME: report further upwards */
+	input_event(evt, link->type, link->trx);
+	return 0;
+}
+
+/* register a driver with the E1 core */
+int e1inp_driver_register(struct e1inp_driver *drv)
+{
+	llist_add_tail(&drv->list, &e1inp_driver_list);
+	return 0;
+}
+
+int e1inp_line_update(struct e1inp_line *line)
+{
+	return mi_e1_line_update(line);
+}
+
+static int e1i_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	if (subsys != SS_GLOBAL ||
+	    signal != S_GLOBAL_SHUTDOWN)
+		return 0;
+
+	if (pcap_fd) {
+		close(pcap_fd);
+		pcap_fd = -1;
+	}
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_e1_inp(void)
+{
+	tall_sigl_ctx = talloc_named_const(tall_bsc_ctx, 1,
+					   "e1inp_sign_link");
+	register_signal_handler(SS_GLOBAL, e1i_sig_cb, NULL);
+}
diff --git a/openbsc/src/gprs/Makefile.am b/openbsc/src/gprs/Makefile.am
new file mode 100644
index 0000000..7cb7ba6
--- /dev/null
+++ b/openbsc/src/gprs/Makefile.am
@@ -0,0 +1,18 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS)
+
+sbin_PROGRAMS = osmo-gbproxy osmo-sgsn
+noinst_LIBRARIES = libsgsn.a
+
+libsgsn_a_SOURCES = gprs_ns.c gprs_bssgp.c gprs_llc.c gsm_04_08_gprs.c \
+		crc24.c gprs_sgsn.c gprs_bssgp_util.c
+
+osmo_gbproxy_SOURCES = gb_proxy.c gb_proxy_main.c gb_proxy_vty.c \
+			gprs_ns.c gprs_bssgp_util.c \
+			$(top_srcdir)/src/socket.c $(top_srcdir)/src/debug.c
+osmo_gbproxy_LDADD = $(top_builddir)/src/libvty.a
+
+osmo_sgsn_SOURCES = sgsn_main.c sgsn_vty.c \
+			$(top_srcdir)/src/socket.c $(top_srcdir)/src/debug.c
+osmo_sgsn_LDADD = $(top_builddir)/src/libvty.a libsgsn.a
diff --git a/openbsc/src/gprs/crc24.c b/openbsc/src/gprs/crc24.c
new file mode 100644
index 0000000..1082120
--- /dev/null
+++ b/openbsc/src/gprs/crc24.c
@@ -0,0 +1,69 @@
+/* GPRS LLC CRC-24 Implementation */
+
+/* (C) 2008-2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+#include <openbsc/crc24.h>
+
+/* CRC24 table - FCS */
+static const u_int32_t tbl_crc24[256] = {
+	0x00000000, 0x00d6a776, 0x00f64557, 0x0020e221, 0x00b78115, 0x00612663, 0x0041c442, 0x00976334,
+	0x00340991, 0x00e2aee7, 0x00c24cc6, 0x0014ebb0, 0x00838884, 0x00552ff2, 0x0075cdd3, 0x00a36aa5,
+	0x00681322, 0x00beb454, 0x009e5675, 0x0048f103, 0x00df9237, 0x00093541, 0x0029d760, 0x00ff7016,
+	0x005c1ab3, 0x008abdc5, 0x00aa5fe4, 0x007cf892, 0x00eb9ba6, 0x003d3cd0, 0x001ddef1, 0x00cb7987,
+	0x00d02644, 0x00068132, 0x00266313, 0x00f0c465, 0x0067a751, 0x00b10027, 0x0091e206, 0x00474570,
+	0x00e42fd5, 0x003288a3, 0x00126a82, 0x00c4cdf4, 0x0053aec0, 0x008509b6, 0x00a5eb97, 0x00734ce1,
+	0x00b83566, 0x006e9210, 0x004e7031, 0x0098d747, 0x000fb473, 0x00d91305, 0x00f9f124, 0x002f5652,
+	0x008c3cf7, 0x005a9b81, 0x007a79a0, 0x00acded6, 0x003bbde2, 0x00ed1a94, 0x00cdf8b5, 0x001b5fc3,
+	0x00fb4733, 0x002de045, 0x000d0264, 0x00dba512, 0x004cc626, 0x009a6150, 0x00ba8371, 0x006c2407,
+	0x00cf4ea2, 0x0019e9d4, 0x00390bf5, 0x00efac83, 0x0078cfb7, 0x00ae68c1, 0x008e8ae0, 0x00582d96,
+	0x00935411, 0x0045f367, 0x00651146, 0x00b3b630, 0x0024d504, 0x00f27272, 0x00d29053, 0x00043725,
+	0x00a75d80, 0x0071faf6, 0x005118d7, 0x0087bfa1, 0x0010dc95, 0x00c67be3, 0x00e699c2, 0x00303eb4,
+	0x002b6177, 0x00fdc601, 0x00dd2420, 0x000b8356, 0x009ce062, 0x004a4714, 0x006aa535, 0x00bc0243,
+	0x001f68e6, 0x00c9cf90, 0x00e92db1, 0x003f8ac7, 0x00a8e9f3, 0x007e4e85, 0x005eaca4, 0x00880bd2,
+	0x00437255, 0x0095d523, 0x00b53702, 0x00639074, 0x00f4f340, 0x00225436, 0x0002b617, 0x00d41161,
+	0x00777bc4, 0x00a1dcb2, 0x00813e93, 0x005799e5, 0x00c0fad1, 0x00165da7, 0x0036bf86, 0x00e018f0,
+	0x00ad85dd, 0x007b22ab, 0x005bc08a, 0x008d67fc, 0x001a04c8, 0x00cca3be, 0x00ec419f, 0x003ae6e9,
+	0x00998c4c, 0x004f2b3a, 0x006fc91b, 0x00b96e6d, 0x002e0d59, 0x00f8aa2f, 0x00d8480e, 0x000eef78,
+	0x00c596ff, 0x00133189, 0x0033d3a8, 0x00e574de, 0x007217ea, 0x00a4b09c, 0x008452bd, 0x0052f5cb,
+	0x00f19f6e, 0x00273818, 0x0007da39, 0x00d17d4f, 0x00461e7b, 0x0090b90d, 0x00b05b2c, 0x0066fc5a,
+	0x007da399, 0x00ab04ef, 0x008be6ce, 0x005d41b8, 0x00ca228c, 0x001c85fa, 0x003c67db, 0x00eac0ad,
+	0x0049aa08, 0x009f0d7e, 0x00bfef5f, 0x00694829, 0x00fe2b1d, 0x00288c6b, 0x00086e4a, 0x00dec93c,
+	0x0015b0bb, 0x00c317cd, 0x00e3f5ec, 0x0035529a, 0x00a231ae, 0x007496d8, 0x005474f9, 0x0082d38f,
+	0x0021b92a, 0x00f71e5c, 0x00d7fc7d, 0x00015b0b, 0x0096383f, 0x00409f49, 0x00607d68, 0x00b6da1e,
+	0x0056c2ee, 0x00806598, 0x00a087b9, 0x007620cf, 0x00e143fb, 0x0037e48d, 0x001706ac, 0x00c1a1da,
+	0x0062cb7f, 0x00b46c09, 0x00948e28, 0x0042295e, 0x00d54a6a, 0x0003ed1c, 0x00230f3d, 0x00f5a84b,
+	0x003ed1cc, 0x00e876ba, 0x00c8949b, 0x001e33ed, 0x008950d9, 0x005ff7af, 0x007f158e, 0x00a9b2f8,
+	0x000ad85d, 0x00dc7f2b, 0x00fc9d0a, 0x002a3a7c, 0x00bd5948, 0x006bfe3e, 0x004b1c1f, 0x009dbb69,
+	0x0086e4aa, 0x005043dc, 0x0070a1fd, 0x00a6068b, 0x003165bf, 0x00e7c2c9, 0x00c720e8, 0x0011879e,
+	0x00b2ed3b, 0x00644a4d, 0x0044a86c, 0x00920f1a, 0x00056c2e, 0x00d3cb58, 0x00f32979, 0x00258e0f,
+	0x00eef788, 0x003850fe, 0x0018b2df, 0x00ce15a9, 0x0059769d, 0x008fd1eb, 0x00af33ca, 0x007994bc,
+	0x00dafe19, 0x000c596f, 0x002cbb4e, 0x00fa1c38, 0x006d7f0c, 0x00bbd87a, 0x009b3a5b, 0x004d9d2d
+};
+
+#define INIT_CRC24	0xffffff
+
+u_int32_t crc24_calc(u_int32_t fcs, u_int8_t *cp, unsigned int len)
+{
+	while (len--)
+		fcs = (fcs >> 8) ^ tbl_crc24[(fcs ^ *cp++) & 0xff];
+	return fcs;
+}
diff --git a/openbsc/src/gprs/gb_proxy.c b/openbsc/src/gprs/gb_proxy.c
new file mode 100644
index 0000000..ea992e9
--- /dev/null
+++ b/openbsc/src/gprs/gb_proxy.c
@@ -0,0 +1,505 @@
+/* NS-over-IP proxy */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gb_proxy.h>
+
+struct gbprox_peer {
+	struct llist_head list;
+
+	/* NS-VC over which we send/receive data to this BVC */
+	struct gprs_nsvc *nsvc;
+
+	/* BVCI used for Point-to-Point to this peer */
+	uint16_t bvci;
+
+	/* Routeing Area that this peer is part of (raw 04.08 encoding) */
+	uint8_t ra[6];
+};
+
+/* Linked list of all Gb peers (except SGSN) */
+static LLIST_HEAD(gbprox_bts_peers);
+
+/* Find the gbprox_peer by its BVCI */
+static struct gbprox_peer *peer_by_bvci(uint16_t bvci)
+{
+	struct gbprox_peer *peer;
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		if (peer->bvci == bvci)
+			return peer;
+	}
+	return NULL;
+}
+
+static struct gbprox_peer *peer_by_nsvc(struct gprs_nsvc *nsvc)
+{
+	struct gbprox_peer *peer;
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		if (peer->nsvc == nsvc)
+			return peer;
+	}
+	return NULL;
+}
+
+/* look-up a peer by its Routeing Area Code (RAC) */
+static struct gbprox_peer *peer_by_rac(const uint8_t *ra)
+{
+	struct gbprox_peer *peer;
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		if (!memcmp(&peer->ra, ra, 6))
+			return peer;
+	}
+	return NULL;
+}
+
+/* look-up a peer by its Location Area Code (LAC) */
+static struct gbprox_peer *peer_by_lac(const uint8_t *la)
+{
+	struct gbprox_peer *peer;
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		if (!memcmp(&peer->ra, la, 5))
+			return peer;
+	}
+	return NULL;
+}
+
+static struct gbprox_peer *peer_alloc(uint16_t bvci)
+{
+	struct gbprox_peer *peer;
+
+	peer = talloc_zero(tall_bsc_ctx, struct gbprox_peer);
+	if (!peer)
+		return NULL;
+
+	peer->bvci = bvci;
+	llist_add(&peer->list, &gbprox_bts_peers);
+
+	return peer;
+}
+
+static void peer_free(struct gbprox_peer *peer)
+{
+	llist_del(&peer->list);
+	talloc_free(peer);
+}
+
+/* strip off the NS header */
+static void strip_ns_hdr(struct msgb *msg)
+{
+	int strip_len = msgb_bssgph(msg) - msg->data;
+	msgb_pull(msg, strip_len);
+}
+
+/* feed a message down the NS-VC associated with the specified peer */
+static int gbprox_relay2sgsn(struct msgb *msg, uint16_t ns_bvci)
+{
+	DEBUGP(DGPRS, "NSEI=%u proxying to SGSN (NS_BVCI=%u, NSEI=%u)\n",
+		msgb_nsei(msg), ns_bvci, gbcfg.nsip_sgsn_nsei);
+
+	msgb_bvci(msg) = ns_bvci;
+	msgb_nsei(msg) = gbcfg.nsip_sgsn_nsei;
+
+	strip_ns_hdr(msg);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* feed a message down the NS-VC associated with the specified peer */
+static int gbprox_relay2peer(struct msgb *msg, struct gbprox_peer *peer,
+			  uint16_t ns_bvci)
+{
+	DEBUGP(DGPRS, "NSEI=%u proxying to BSS (NS_BVCI=%u, NSEI=%u)\n",
+		msgb_nsei(msg), ns_bvci, peer->nsvc->nsei);
+
+	msgb_bvci(msg) = ns_bvci;
+	msgb_nsei(msg) = peer->nsvc->nsei;
+
+	strip_ns_hdr(msg);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* Send a message to a peer identified by ptp_bvci but using ns_bvci
+ * in the NS hdr */
+static int gbprox_relay2bvci(struct msgb *msg, uint16_t ptp_bvci,
+			  uint16_t ns_bvci)
+{
+	struct gbprox_peer *peer;
+
+	peer = peer_by_bvci(ptp_bvci);
+	if (!peer) {
+		LOGP(DGPRS, LOGL_ERROR, "Cannot find BSS for BVCI %u\n",
+			ptp_bvci);
+		return -ENOENT;
+	}
+
+	return gbprox_relay2peer(msg, peer, ns_bvci);
+}
+
+/* Receive an incoming signalling message from a BSS-side NS-VC */
+static int gbprox_rx_sig_from_bss(struct msgb *msg, struct gprs_nsvc *nsvc,
+				  uint16_t ns_bvci)
+{
+	struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	struct tlv_parsed tp;
+	uint8_t pdu_type = bgph->pdu_type;
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	struct gbprox_peer *from_peer;
+	struct gprs_ra_id raid;
+
+	if (ns_bvci != 0) {
+		LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u BVCI %u is not signalling\n",
+			nsvc->nsei, ns_bvci);
+		return -EINVAL;
+	}
+
+	/* we actually should never see those two for BVCI == 0, but double-check
+	 * just to make sure  */
+	if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+	    pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+		LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u UNITDATA not allowed in "
+			"signalling\n", nsvc->nsei);
+		return -EINVAL;
+	}
+
+	bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+	switch (pdu_type) {
+	case BSSGP_PDUT_SUSPEND:
+	case BSSGP_PDUT_RESUME:
+		/* We implement RAC snooping during SUSPEND/RESUME, since
+		 * it establishes a relationsip between BVCI/peer and the
+		 * routeing area code.  The snooped information is then
+		 * used for routing the {SUSPEND,RESUME}_[N]ACK back to
+		 * the correct BSSGP */
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+			goto err_mand_ie;
+		from_peer = peer_by_nsvc(nsvc);
+		if (!from_peer)
+			goto err_no_peer;
+		memcpy(&from_peer->ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA),
+			sizeof(&from_peer->ra));
+		gsm48_parse_ra(&raid, from_peer->ra);
+		DEBUGP(DGPRS, "NSEI=%u RAC snooping: RAC %u/%u/%u/%u behind BVCI=%u, "
+			"NSVCI=%u\n", nsvc->nsei, raid.mcc, raid.mnc, raid.lac,
+			raid.rac , from_peer->bvci, nsvc->nsvci);
+		/* FIXME: This only supports one BSS per RA */
+		break;
+	case BSSGP_PDUT_BVC_RESET:
+		/* If we receive a BVC reset on the signalling endpoint, we
+		 * don't want the SGSN to reset, as the signalling endpoint
+		 * is common for all point-to-point BVCs (and thus all BTS) */
+		if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+			uint16_t bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+			if (bvci == 0) {
+				/* FIXME: only do this if SGSN is alive! */
+				LOGP(DGPRS, LOGL_INFO, "NSEI=%u Sending fake "
+					"BVC RESET ACK of BVCI=0\n", nsvc->nsei);
+				return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+							    nsvc->nsei, 0, ns_bvci);
+			} else if (!peer_by_bvci(bvci)) {
+				/* if a PTP-BVC is reset, and we don't know that
+				 * PTP-BVCI yet, we should allocate a new peer */
+				LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for "
+				     "BVCI=%u via NSVCI=%u/NSEI=%u\n", bvci,
+				     nsvc->nsvci, nsvc->nsei);
+				from_peer = peer_alloc(bvci);
+				from_peer->nsvc = nsvc;
+			}
+		}
+		break;
+	}
+
+	/* Normally, we can simply pass on all signalling messages from BSS to SGSN */
+	return gbprox_relay2sgsn(msg, ns_bvci);
+err_no_peer:
+	LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) cannot find peer based on RAC\n",
+		nsvc->nsei);
+	return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg);
+err_mand_ie:
+	LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) missing mandatory RA IE\n",
+		nsvc->nsei);
+	return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+}
+
+/* Receive paging request from SGSN, we need to relay to proper BSS */
+static int gbprox_rx_paging(struct msgb *msg, struct tlv_parsed *tp,
+			    struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+	struct gbprox_peer *peer;
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+		uint16_t bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+		return gbprox_relay2bvci(msg, bvci, ns_bvci);
+	} else if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
+		peer = peer_by_rac(TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
+		return gbprox_relay2peer(msg, peer, ns_bvci);
+	} else if (TLVP_PRESENT(tp, BSSGP_IE_LOCATION_AREA)) {
+		peer = peer_by_lac(TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA));
+		return gbprox_relay2peer(msg, peer, ns_bvci);
+	} else
+		return -EINVAL;
+}
+
+/* Receive an incoming BVC-RESET message from the SGSN */
+static int rx_reset_from_sgsn(struct msgb *msg, struct tlv_parsed *tp,
+			      struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+	struct gbprox_peer *peer;
+	uint16_t ptp_bvci;
+
+	if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE,
+				       NULL, msg);
+	}
+	ptp_bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+
+	if (ptp_bvci >= 2) {
+		/* A reset for a PTP BVC was received, forward it to its
+		 * respective peer */
+		peer = peer_by_bvci(ptp_bvci);
+		if (!peer) {
+			LOGP(DGPRS, LOGL_ERROR, "Cannot find BSS for BVCI %u\n",
+				ptp_bvci);
+			return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI,
+					       NULL, msg);
+		}
+		return gbprox_relay2peer(msg, peer, ns_bvci);
+	}
+
+	/* A reset for the Signalling entity has been received
+	 * from the SGSN.  As the signalling BVCI is shared
+	 * among all the BSS's that we multiplex, it needs to
+	 * be relayed  */
+	llist_for_each_entry(peer, &gbprox_bts_peers, list)
+		gbprox_relay2peer(msg, peer, ns_bvci);
+
+	return 0;
+}
+
+/* Receive an incoming signalling message from the SGSN-side NS-VC */
+static int gbprox_rx_sig_from_sgsn(struct msgb *msg, struct gprs_nsvc *nsvc,
+				   uint16_t ns_bvci)
+{
+	struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	struct tlv_parsed tp;
+	uint8_t pdu_type = bgph->pdu_type;
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	struct gbprox_peer *peer;
+	uint16_t bvci;
+	int rc = 0;
+
+	if (ns_bvci != 0) {
+		LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BVCI %u is not "
+			"signalling\n", nsvc->nsei, ns_bvci);
+		/* FIXME: Send proper error message */
+		return -EINVAL;
+	}
+
+	/* we actually should never see those two for BVCI == 0, but double-check
+	 * just to make sure  */
+	if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+	    pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+		LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) UNITDATA not allowed in "
+			"signalling\n", nsvc->nsei);
+		return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+	}
+
+	rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+	switch (pdu_type) {
+	case BSSGP_PDUT_BVC_RESET:
+		rc = rx_reset_from_sgsn(msg, &tp, nsvc, ns_bvci);
+		break;
+	case BSSGP_PDUT_FLUSH_LL:
+	case BSSGP_PDUT_BVC_BLOCK_ACK:
+	case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+	case BSSGP_PDUT_BVC_RESET_ACK:
+		/* simple case: BVCI IE is mandatory */
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+			goto err_mand_ie;
+		bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+		rc = gbprox_relay2bvci(msg, bvci, ns_bvci);
+		break;
+	case BSSGP_PDUT_PAGING_PS:
+	case BSSGP_PDUT_PAGING_CS:
+		/* process the paging request (LAC/RAC lookup) */
+		rc = gbprox_rx_paging(msg, &tp, nsvc, ns_bvci);
+		break;
+	case BSSGP_PDUT_STATUS:
+		/* Some exception has occurred */
+		LOGP(DGPRS, LOGL_NOTICE,
+			"NSEI=%u(SGSN) STATUS ", nsvc->nsei);
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_CAUSE)) {
+			LOGPC(DGPRS, LOGL_NOTICE, "\n");
+			goto err_mand_ie;
+		}
+		LOGPC(DGPRS, LOGL_NOTICE,
+			"cause=0x%02x(%s) ", *TLVP_VAL(&tp, BSSGP_IE_CAUSE),
+			bssgp_cause_str(*TLVP_VAL(&tp, BSSGP_IE_CAUSE)));
+		if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+			uint16_t *bvci = (uint16_t *)
+						TLVP_VAL(&tp, BSSGP_IE_BVCI);
+			LOGPC(DGPRS, LOGL_NOTICE,
+				"BVCI=%u\n", ntohs(*bvci));
+		} else
+			LOGPC(DGPRS, LOGL_NOTICE, "\n");
+		break;
+	/* those only exist in the SGSN -> BSS direction */
+	case BSSGP_PDUT_SUSPEND_ACK:
+	case BSSGP_PDUT_SUSPEND_NACK:
+	case BSSGP_PDUT_RESUME_ACK:
+	case BSSGP_PDUT_RESUME_NACK:
+		/* RAC IE is mandatory */
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+			goto err_mand_ie;
+		peer = peer_by_rac(TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA));
+		if (!peer)
+			goto err_no_peer;
+		rc = gbprox_relay2peer(msg, peer, ns_bvci);
+		break;
+	case BSSGP_PDUT_SGSN_INVOKE_TRACE:
+		LOGP(DGPRS, LOGL_ERROR,
+		     "NSEI=%u(SGSN) INVOKE TRACE not supported\n", nsvc->nsei);
+		rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
+		break;
+	default:
+		DEBUGP(DGPRS, "BSSGP PDU type 0x%02x unknown\n", pdu_type);
+		rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+		break;
+	}
+
+	return rc;
+err_mand_ie:
+	LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) missing mandatory IE\n",
+		nsvc->nsei);
+	return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+err_no_peer:
+	LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) cannot find peer based on RAC\n",
+		nsvc->nsei);
+	return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg);
+}
+
+/* Main input function for Gb proxy */
+int gbprox_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+	int rc;
+
+	/* Only BVCI=0 messages need special treatment */
+	if (ns_bvci == 0 || ns_bvci == 1) {
+		if (nsvc->remote_end_is_sgsn)
+			rc = gbprox_rx_sig_from_sgsn(msg, nsvc, ns_bvci);
+		else
+			rc = gbprox_rx_sig_from_bss(msg, nsvc, ns_bvci);
+	} else {
+		/* All other BVCI are PTP and thus can be simply forwarded */
+		if (!nsvc->remote_end_is_sgsn) {
+			rc = gbprox_relay2sgsn(msg, ns_bvci);
+		} else {
+			struct gbprox_peer *peer = peer_by_bvci(ns_bvci);
+			if (!peer) {
+				LOGP(DGPRS, LOGL_NOTICE, "Allocationg new peer for "
+				     "BVCI=%u via NSVC=%u/NSEI=%u\n", ns_bvci,
+				     nsvc->nsvci, nsvc->nsei);
+				peer = peer_alloc(ns_bvci);
+				peer->nsvc = nsvc;
+			}
+			rc = gbprox_relay2peer(msg, peer, ns_bvci);
+		}
+	}
+
+	return rc;
+}
+
+/* Signal handler for signals from NS layer */
+int gbprox_signal(unsigned int subsys, unsigned int signal,
+		  void *handler_data, void *signal_data)
+{
+	struct ns_signal_data *nssd = signal_data;
+	struct gprs_nsvc *nsvc = nssd->nsvc;
+	struct gbprox_peer *peer;
+
+	if (subsys != SS_NS)
+		return 0;
+
+	/* We currently only care about signals from the SGSN */
+	if (!nsvc->remote_end_is_sgsn)
+		return 0;
+
+	/* iterate over all BTS peers and send the respective PDU */
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		switch (signal) {
+		case S_NS_RESET:
+			gprs_ns_tx_reset(peer->nsvc, nssd->cause);
+			break;
+		case S_NS_BLOCK:
+			gprs_ns_tx_block(peer->nsvc, nssd->cause);
+			break;
+		case S_NS_UNBLOCK:
+			gprs_ns_tx_unblock(peer->nsvc);
+			break;
+		}
+	}
+	return 0;
+}
+
+
+#include <vty/command.h>
+
+gDEFUN(show_gbproxy, show_gbproxy_cmd, "show gbproxy",
+       SHOW_STR "Display information about the Gb proxy")
+{
+	struct gbprox_peer *peer;
+
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		struct gprs_nsvc *nsvc = peer->nsvc;
+		struct gprs_ra_id raid;
+		gsm48_parse_ra(&raid, peer->ra);
+
+		vty_out(vty, "NSEI %5u, NS-VC %5u, PTP-BVCI %u, "
+			"RAC %u-%u-%u-%u%s",
+			nsvc->nsei, nsvc->nsvci, peer->bvci,
+			raid.mcc, raid.mnc, raid.lac, raid.rac, VTY_NEWLINE);
+		if (nsvc->nsi->ll == GPRS_NS_LL_UDP)
+			vty_out(vty, "  remote address %s:%u%s",
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				ntohs(nsvc->ip.bts_addr.sin_port), VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
diff --git a/openbsc/src/gprs/gb_proxy_main.c b/openbsc/src/gprs/gb_proxy_main.c
new file mode 100644
index 0000000..1a5d7bd
--- /dev/null
+++ b/openbsc/src/gprs/gb_proxy_main.c
@@ -0,0 +1,149 @@
+/* NS-over-IP proxy */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/telnet_interface.h>
+#include <openbsc/vty.h>
+#include <openbsc/gb_proxy.h>
+
+#include "../../bscconfig.h"
+
+/* this is here for the vty... it will never be called */
+void subscr_put() { abort(); }
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+void *tall_bsc_ctx;
+
+const char *openbsc_version = "Osmocom NSIP Proxy " PACKAGE_VERSION;
+const char *openbsc_copyright =
+	"Copyright (C) 2010 Harald Welte and On-Waves\n"
+	"Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\n"
+	"Dieter Spaar, Andreas Eversberg, Holger Freyther\n\n"
+	"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
+	"This is free software: you are free to change and redistribute it.\n"
+	"There is NO WARRANTY, to the extent permitted by law.\n";
+
+static char *config_file = "osmo_gbproxy.cfg";
+struct gbproxy_config gbcfg;
+
+/* Pointer to the SGSN peer */
+extern struct gbprox_peer *gbprox_peer_sgsn;
+
+/* call-back function for the NS protocol */
+static int proxy_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+		      struct msgb *msg, u_int16_t bvci)
+{
+	int rc = 0;
+
+	switch (event) {
+	case GPRS_NS_EVT_UNIT_DATA:
+		rc = gbprox_rcvmsg(msg, nsvc, bvci);
+		break;
+	default:
+		LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event);
+		if (msg)
+			talloc_free(msg);
+		rc = -EIO;
+		break;
+	}
+	return rc;
+}
+
+
+int main(int argc, char **argv)
+{
+	struct gsm_network dummy_network;
+	struct log_target *stderr_target;
+	struct sockaddr_in sin;
+	int rc;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 0, "nsip_proxy");
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	telnet_init(&dummy_network, 4244);
+	rc = gbproxy_parse_config(config_file, &gbcfg);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n");
+		exit(2);
+	}
+
+	bssgp_nsi = gprs_ns_instantiate(&proxy_ns_cb);
+	if (!bssgp_nsi) {
+		LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+		exit(1);
+	}
+	gbcfg.nsi = bssgp_nsi;
+	register_signal_handler(SS_NS, &gbprox_signal, NULL);
+	nsip_listen(bssgp_nsi, gbcfg.nsip_listen_port);
+
+	/* 'establish' the outgoing connection to the SGSN */
+	sin.sin_family = AF_INET;
+	sin.sin_port = htons(gbcfg.nsip_sgsn_port);
+	sin.sin_addr.s_addr = htonl(gbcfg.nsip_sgsn_ip);
+	nsip_connect(bssgp_nsi, &sin, gbcfg.nsip_sgsn_nsei,
+			gbcfg.nsip_sgsn_nsvci);
+
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
+struct gsm_network;
+int bsc_vty_init(struct gsm_network *dummy)
+{
+	cmd_init(1);
+	vty_init();
+
+	openbsc_vty_add_cmds();
+        gbproxy_vty_init();
+	return 0;
+}
+
diff --git a/openbsc/src/gprs/gb_proxy_vty.c b/openbsc/src/gprs/gb_proxy_vty.c
new file mode 100644
index 0000000..a40392b
--- /dev/null
+++ b/openbsc/src/gprs/gb_proxy_vty.c
@@ -0,0 +1,208 @@
+/*
+ * (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gb_proxy.h>
+#include <openbsc/gprs_ns.h>
+
+#include <vty/command.h>
+#include <vty/vty.h>
+
+static struct gbproxy_config *g_cfg = NULL;
+
+/*
+ * vty code for mgcp below
+ */
+static struct cmd_node gbproxy_node = {
+	GBPROXY_NODE,
+	"%s(gbproxy)#",
+	1,
+};
+
+static int config_write_gbproxy(struct vty *vty)
+{
+	struct in_addr ia;
+
+	vty_out(vty, "gbproxy%s", VTY_NEWLINE);
+
+	if (g_cfg->nsip_listen_ip) {
+		ia.s_addr = htonl(g_cfg->nsip_listen_ip);
+		vty_out(vty, "  nsip bss local ip %s%s", inet_ntoa(ia),
+			VTY_NEWLINE);
+	}
+	vty_out(vty, "  nsip bss local port %u%s", g_cfg->nsip_listen_port,
+		VTY_NEWLINE);
+	ia.s_addr = htonl(g_cfg->nsip_sgsn_ip);
+	vty_out(vty, "  nsip sgsn remote ip %s%s", inet_ntoa(ia),
+		VTY_NEWLINE);
+	vty_out(vty, "  nsip sgsn remote port %u%s", g_cfg->nsip_sgsn_port,
+		VTY_NEWLINE);
+	vty_out(vty, "  nsip sgsn nsei %u%s", g_cfg->nsip_sgsn_nsei,
+		VTY_NEWLINE);
+	vty_out(vty, "  nsip sgsn nsvci %u%s", g_cfg->nsip_sgsn_nsvci,
+		VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_ns, show_ns_cmd, "show ns",
+      SHOW_STR "Display information about the NS protocol")
+{
+	/* FIXME: iterate over list of NS-VC's and display their state */
+	struct gprs_ns_inst *nsi = g_cfg->nsi;
+	struct gprs_nsvc *nsvc;
+
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		vty_out(vty, "NSEI %5u, NS-VC %5u, %s-mode, %s %s%s",
+			nsvc->nsei, nsvc->nsvci,
+			nsvc->remote_end_is_sgsn ? "BSS" : "SGSN",
+			nsvc->state & NSE_S_ALIVE ? "ALIVE" : "DEAD",
+			nsvc->state & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED",
+			VTY_NEWLINE);
+		if (nsvc->nsi->ll == GPRS_NS_LL_UDP)
+			vty_out(vty, "  remote peer %s:%u%s",
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				ntohs(nsvc->ip.bts_addr.sin_port), VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy,
+      cfg_gbproxy_cmd,
+      "gbproxy",
+      "Configure the Gb proxy")
+{
+	vty->node = GBPROXY_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_bss_local_ip,
+      cfg_nsip_bss_local_ip_cmd,
+      "nsip bss local ip A.B.C.D",
+      "Set the IP address on which we listen for BSS connects")
+{
+	struct in_addr ia;
+
+	inet_aton(argv[0], &ia);
+	g_cfg->nsip_listen_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_bss_local_port,
+      cfg_nsip_bss_local_port_cmd,
+      "nsip bss local port <0-65534>",
+      "Set the UDP port on which we listen for BSS connects")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_listen_port = port;
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_nsip_sgsn_ip,
+      cfg_nsip_sgsn_ip_cmd,
+      "nsip sgsn remote ip A.B.C.D",
+      "Set the IP of the SGSN to which the proxy shall connect")
+{
+	struct in_addr ia;
+
+	inet_aton(argv[0], &ia);
+	g_cfg->nsip_sgsn_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_sgsn_port,
+      cfg_nsip_sgsn_port_cmd,
+      "nsip sgsn remote port <0-65534>",
+      "Set the UDP port of the SGSN to which the proxy shall connect")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_sgsn_port = port;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_sgsn_nsei,
+      cfg_nsip_sgsn_nsei_cmd,
+      "nsip sgsn nsei <0-65534>",
+      "Set the NSEI to be used in the connection with the SGSN")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_sgsn_nsei = port;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_sgsn_nsvci,
+      cfg_nsip_sgsn_nsvci_cmd,
+      "nsip sgsn nsvci <0-65534>",
+      "Set the NSVCI to be used in the connection with the SGSN")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_sgsn_nsvci = port;
+	return CMD_SUCCESS;
+}
+
+
+int gbproxy_vty_init(void)
+{
+	install_element(VIEW_NODE, &show_ns_cmd);
+	install_element(VIEW_NODE, &show_gbproxy_cmd);
+
+	install_element(CONFIG_NODE, &cfg_gbproxy_cmd);
+	install_node(&gbproxy_node, config_write_gbproxy);
+	install_default(GBPROXY_NODE);
+	install_element(GBPROXY_NODE, &cfg_nsip_bss_local_ip_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_bss_local_port_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_ip_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_port_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsei_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsvci_cmd);
+
+	return 0;
+}
+
+int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg)
+{
+	int rc;
+
+	g_cfg = cfg;
+	rc = vty_read_config_file(config_file);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	return 0;
+}
+
diff --git a/openbsc/src/gprs/gprs_bssgp.c b/openbsc/src/gprs/gprs_bssgp.c
new file mode 100644
index 0000000..9fdfd32
--- /dev/null
+++ b/openbsc/src/gprs/gprs_bssgp.c
@@ -0,0 +1,448 @@
+/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */
+
+/* (C) 2009-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_ns.h>
+
+void *bssgp_tall_ctx = NULL;
+
+#define BVC_F_BLOCKED	0x0001
+
+/* The per-BTS context that we keep on the SGSN side of the BSSGP link */
+struct bssgp_bts_ctx {
+	struct llist_head list;
+
+	/* parsed RA ID and Cell ID of the remote BTS */
+	struct gprs_ra_id ra_id;
+	uint16_t cell_id;
+
+	/* NSEI and BVCI of underlying Gb link.  Together they
+	 * uniquely identify a link to a BTS (5.4.4) */
+	uint16_t bvci;
+	uint16_t nsei;
+
+	uint32_t bvc_state;
+
+	/* we might want to add this as a shortcut later, avoiding the NSVC
+	 * lookup for every packet, similar to a routing cache */
+	//struct gprs_nsvc *nsvc;
+};
+static LLIST_HEAD(bts_ctxts);
+
+/* Find a BTS Context based on parsed RA ID and Cell ID */
+struct bssgp_bts_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid)
+{
+	struct bssgp_bts_ctx *bctx;
+
+	llist_for_each_entry(bctx, &bts_ctxts, list) {
+		if (!memcmp(&bctx->ra_id, raid, sizeof(bctx->ra_id)) &&
+		    bctx->cell_id == cid)
+			return bctx;
+	}
+	return NULL;
+}
+
+/* Find a BTS context based on BVCI+NSEI tuple */
+struct bssgp_bts_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei)
+{
+	struct bssgp_bts_ctx *bctx;
+
+	llist_for_each_entry(bctx, &bts_ctxts, list) {
+		if (bctx->nsei == nsei && bctx->bvci == bvci)
+			return bctx;
+	}
+	return NULL;
+}
+
+struct bssgp_bts_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
+{
+	struct bssgp_bts_ctx *ctx;
+
+	ctx = talloc_zero(bssgp_tall_ctx, struct bssgp_bts_ctx);
+	if (!ctx)
+		return NULL;
+	ctx->bvci = bvci;
+	ctx->nsei = nsei;
+	llist_add(&ctx->list, &bts_ctxts);
+
+	return ctx;
+}
+
+/* Chapter 10.4.5: Flow Control BVC ACK */
+static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = ns_bvci;
+
+	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
+	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf)
+{
+	/* 6 octets RAC */
+	gsm48_parse_ra(raid, buf);
+	/* 2 octets CID */
+	return ntohs(*(uint16_t *) (buf+6));
+}
+
+/* Chapter 8.4 BVC-Reset Procedure */
+static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,	
+			      uint16_t ns_bvci)
+{
+	struct bssgp_bts_ctx *bctx;
+	uint16_t nsei = msgb_nsei(msg);
+	uint16_t bvci;
+	int rc;
+
+	bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+	DEBUGPC(DBSSGP, "BVCI=%u, cause=%s\n", bvci,
+		bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
+
+	/* look-up or create the BTS context for this BVC */
+	bctx = btsctx_by_bvci_nsei(bvci, nsei);
+	if (!bctx)
+		bctx = btsctx_alloc(bvci, nsei);
+
+	/* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS
+	 * informs us about its RAC + Cell ID, so we can create a mapping */
+	if (bvci != 0 && bvci != 1) {
+		if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) {
+			LOGP(DBSSGP, LOGL_ERROR, "BSSGP RESET BVCI=%u "
+				"missing mandatory IE\n", bvci);
+			return -EINVAL;
+		}
+		/* actually extract RAC / CID */
+		bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id,
+						TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+		LOGP(DBSSGP, LOGL_NOTICE, "Cell %u-%u-%u-%u CI %u on BVCI %u\n",
+			bctx->ra_id.mcc, bctx->ra_id.mnc, bctx->ra_id.lac,
+			bctx->ra_id.rac, bctx->cell_id, bvci);
+	}
+
+	/* Acknowledge the RESET to the BTS */
+	rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+				  nsei, bvci, ns_bvci);
+	return 0;
+}
+
+/* Uplink unit-data */
+static int bssgp_rx_ul_ud(struct msgb *msg)
+{
+	struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
+	int data_len = msgb_bssgp_len(msg) - sizeof(*budh);
+	struct tlv_parsed tp;
+	int rc;
+
+	DEBUGP(DBSSGP, "BSSGP UL-UD\n");
+
+	/* extract TLLI and parse TLV IEs */
+	msgb_tlli(msg) = ntohl(budh->tlli);
+	rc = bssgp_tlv_parse(&tp, budh->data, data_len);
+
+	/* Cell ID and LLC_PDU are the only mandatory IE */
+	if (!TLVP_PRESENT(&tp, BSSGP_IE_CELL_ID) ||
+	    !TLVP_PRESENT(&tp, BSSGP_IE_LLC_PDU))
+		return -EIO;
+
+	/* FIXME: lookup bssgp_bts_ctx based on BVCI + NSEI */
+
+	/* store pointer to LLC header and CELL ID in msgb->cb */
+	msgb_llch(msg) = TLVP_VAL(&tp, BSSGP_IE_LLC_PDU);
+	msgb_bcid(msg) = TLVP_VAL(&tp, BSSGP_IE_CELL_ID);
+
+	return gprs_llc_rcvmsg(msg, &tp);
+}
+
+static int bssgp_rx_suspend(struct msgb *msg)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	struct tlv_parsed tp;
+	int rc;
+
+	DEBUGP(DBSSGP, "BSSGP SUSPEND\n");
+
+	rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+	if (rc < 0)
+		return rc;
+
+	if (!TLVP_PRESENT(&tp, BSSGP_IE_TLLI) ||
+	    !TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+		return -EIO;
+
+	/* FIXME: pass the SUSPEND request to GMM */
+	/* SEND SUSPEND_ACK or SUSPEND_NACK */
+}
+
+static int bssgp_rx_resume(struct msgb *msg)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	struct tlv_parsed tp;
+	int rc;
+
+	DEBUGP(DBSSGP, "BSSGP RESUME\n");
+
+	rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+	if (rc < 0)
+		return rc;
+
+	if (!TLVP_PRESENT(&tp, BSSGP_IE_TLLI) ||
+	    !TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA) ||
+	    !TLVP_PRESENT(&tp, BSSGP_IE_SUSPEND_REF_NR))
+		return -EIO;
+
+	/* FIXME: pass the RESUME request to GMM */
+	/* SEND RESUME_ACK or RESUME_NACK */
+}
+
+static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp)
+{
+
+	DEBUGP(DBSSGP, "BSSGP FC BVC\n");
+
+	if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS))
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+
+	/* FIXME: actually implement flow control */
+
+	/* Send FLOW_CONTROL_BVC_ACK */
+	return bssgp_tx_fc_bvc_ack(msgb_nsei(msg), *TLVP_VAL(tp, BSSGP_IE_TAG),
+				   msgb_bvci(msg));
+}
+
+/* We expect msgb_bssgph() to point to the BSSGP header */
+int gprs_bssgp_rcvmsg(struct msgb *msg)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	struct tlv_parsed tp;
+	uint8_t pdu_type = bgph->pdu_type;
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	uint16_t bvci;	/* PTP BVCI */
+	uint16_t ns_bvci = msgb_bvci(msg);
+	int rc = 0;
+
+	/* Identifiers from DOWN: NSEI, BVCI (both in msg->cb) */
+
+	/* UNITDATA BSSGP headers have TLLI in front */
+	if (pdu_type != BSSGP_PDUT_UL_UNITDATA &&
+	    pdu_type != BSSGP_PDUT_DL_UNITDATA)
+		rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+	switch (pdu_type) {
+	case BSSGP_PDUT_UL_UNITDATA:
+		/* some LLC data from the MS */
+		rc = bssgp_rx_ul_ud(msg);
+		break;
+	case BSSGP_PDUT_RA_CAPABILITY:
+		/* BSS requests RA capability or IMSI */
+		DEBUGP(DBSSGP, "BSSGP RA CAPABILITY UPDATE\n");
+		/* FIXME: send RA_CAPA_UPDATE_ACK */
+		break;
+	case BSSGP_PDUT_RADIO_STATUS:
+		DEBUGP(DBSSGP, "BSSGP RADIO STATUS\n");
+		/* BSS informs us of some exception */
+		/* FIXME: notify GMM */
+		break;
+	case BSSGP_PDUT_SUSPEND:
+		/* MS wants to suspend */
+		rc = bssgp_rx_suspend(msg);
+		break;
+	case BSSGP_PDUT_RESUME:
+		/* MS wants to resume */
+		rc = bssgp_rx_resume(msg);
+		break;
+	case BSSGP_PDUT_FLUSH_LL:
+		/* BSS informs MS has moved to one cell to other cell */
+		DEBUGP(DBSSGP, "BSSGP FLUSH LL\n");
+		/* FIXME: notify GMM */
+		/* Send FLUSH_LL_ACK */
+		break;
+	case BSSGP_PDUT_LLC_DISCARD:
+		/* BSS informs that some LLC PDU's have been discarded */
+		DEBUGP(DBSSGP, "BSSGP LLC DISCARDED\n");
+		/* FIXME: notify GMM */
+		break;
+	case BSSGP_PDUT_FLOW_CONTROL_BVC:
+		/* BSS informs us of available bandwidth in Gb interface */
+		rc = bssgp_rx_fc_bvc(msg, &tp);
+		break;
+	case BSSGP_PDUT_FLOW_CONTROL_MS:
+		/* BSS informs us of available bandwidth to one MS */
+		DEBUGP(DBSSGP, "BSSGP FC MS\n");
+		/* FIXME: actually implement flow control */
+		/* FIXME: Send FLOW_CONTROL_MS_ACK */
+		break;
+	case BSSGP_PDUT_BVC_BLOCK:
+		/* BSS tells us that BVC shall be blocked */
+		DEBUGP(DBSSGP, "BSSGP BVC BLOCK ");
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI) ||
+		    !TLVP_PRESENT(&tp, BSSGP_IE_CAUSE))
+			goto err_mand_ie;
+		bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+		DEBUGPC(DBSSGP, "BVCI=%u, cause=%s\n", bvci,
+			bssgp_cause_str(*TLVP_VAL(&tp, BSSGP_IE_CAUSE)));
+		/* We always acknowledge the BLOCKing */
+		rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK_ACK,
+					  msgb_nsei(msg), bvci, ns_bvci);
+		break;
+	case BSSGP_PDUT_BVC_UNBLOCK:
+		/* BSS tells us that BVC shall be unblocked */
+		DEBUGP(DBSSGP, "BSSGP BVC UNBLOCK ");
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+			goto err_mand_ie;
+		bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+		DEBUGPC(DBSSGP, "BVCI=%u\n", bvci);
+		/* We always acknowledge the unBLOCKing */
+		rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK,
+					  msgb_nsei(msg), bvci, ns_bvci);
+		break;
+	case BSSGP_PDUT_BVC_RESET:
+		/* BSS tells us that BVC init is required */
+		DEBUGP(DBSSGP, "BSSGP BVC RESET ");
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI) ||
+		    !TLVP_PRESENT(&tp, BSSGP_IE_CAUSE))
+			goto err_mand_ie;
+		rc = bssgp_rx_bvc_reset(msg, &tp, ns_bvci);
+		break;
+	case BSSGP_PDUT_STATUS:
+		/* Some exception has occurred */
+		/* FIXME: notify GMM */
+	case BSSGP_PDUT_DOWNLOAD_BSS_PFC:
+	case BSSGP_PDUT_CREATE_BSS_PFC_ACK:
+	case BSSGP_PDUT_CREATE_BSS_PFC_NACK:
+	case BSSGP_PDUT_MODIFY_BSS_PFC:
+	case BSSGP_PDUT_DELETE_BSS_PFC_ACK:
+		DEBUGP(DBSSGP, "BSSGP PDU type 0x%02x not [yet] implemented\n",
+			pdu_type);
+		break;
+	/* those only exist in the SGSN -> BSS direction */
+	case BSSGP_PDUT_DL_UNITDATA:
+	case BSSGP_PDUT_PAGING_PS:
+	case BSSGP_PDUT_PAGING_CS:
+	case BSSGP_PDUT_RA_CAPA_UPDATE_ACK:
+	case BSSGP_PDUT_SUSPEND_ACK:
+	case BSSGP_PDUT_SUSPEND_NACK:
+	case BSSGP_PDUT_RESUME_ACK:
+	case BSSGP_PDUT_RESUME_NACK:
+	case BSSGP_PDUT_FLUSH_LL_ACK:
+	case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK:
+	case BSSGP_PDUT_FLOW_CONTROL_MS_ACK:
+	case BSSGP_PDUT_BVC_BLOCK_ACK:
+	case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+	case BSSGP_PDUT_SGSN_INVOKE_TRACE:
+		DEBUGP(DBSSGP, "BSSGP PDU type 0x%02x only exists in DL\n",
+			pdu_type);
+		rc = -EINVAL;
+		break;
+	default:
+		DEBUGP(DBSSGP, "BSSGP PDU type 0x%02x unknown\n", pdu_type);
+		break;
+	}
+
+	return rc;
+err_mand_ie:
+	return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+}
+
+/* Entry function from upper level (LLC), asking us to transmit a BSSGP PDU
+ * to a remote MS (identified by TLLI) at a BTS identified by its BVCI and NSEI */
+int gprs_bssgp_tx_dl_ud(struct msgb *msg)
+{
+	struct bssgp_bts_ctx *bctx;
+	struct bssgp_ud_hdr *budh;
+	uint8_t llc_pdu_tlv_hdr_len = 2;
+	uint8_t *llc_pdu_tlv, *qos_profile;
+	uint16_t pdu_lifetime = 1000; /* centi-seconds */
+	uint8_t qos_profile_default[3] = { 0x00, 0x00, 0x21 };
+	uint16_t msg_len = msg->len;
+	uint16_t bvci = msgb_bvci(msg);
+	uint16_t nsei = msgb_nsei(msg);
+
+	/* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */
+	if (bvci < 2) {
+		LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
+			bvci);
+		return -EINVAL;
+	}
+
+	bctx = btsctx_by_bvci_nsei(bvci, nsei);
+	if (!bctx)
+		bctx = btsctx_alloc(bvci, nsei);
+
+	if (msg->len > TVLV_MAX_ONEBYTE)
+		llc_pdu_tlv_hdr_len += 1;
+
+	/* prepend the tag and length of the LLC-PDU TLV */
+	llc_pdu_tlv = msgb_push(msg, llc_pdu_tlv_hdr_len);
+	llc_pdu_tlv[0] = BSSGP_IE_LLC_PDU;
+	if (llc_pdu_tlv_hdr_len > 2) {
+		llc_pdu_tlv[1] = msg_len >> 8;
+		llc_pdu_tlv[2] = msg_len & 0xff;
+	} else {
+		llc_pdu_tlv[1] = msg_len & 0x3f;
+		llc_pdu_tlv[1] |= 0x80;
+	}
+
+	/* FIXME: optional elements */
+
+	/* prepend the pdu lifetime */
+	pdu_lifetime = htons(pdu_lifetime);
+	msgb_tvlv_push(msg, BSSGP_IE_PDU_LIFETIME, 2, (uint8_t *)&pdu_lifetime);
+
+	/* prepend the QoS profile, TLLI and pdu type */
+	budh = (struct bssgp_ud_hdr *) msgb_push(msg, sizeof(*budh));
+	memcpy(budh->qos_profile, qos_profile_default, sizeof(qos_profile_default));
+	budh->tlli = htonl(msgb_tlli(msg));
+	budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
+
+	/* Identifiers down: BVCI, NSEI (in msgb->cb) */
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
diff --git a/openbsc/src/gprs/gprs_bssgp_util.c b/openbsc/src/gprs/gprs_bssgp_util.c
new file mode 100644
index 0000000..d9b5175
--- /dev/null
+++ b/openbsc/src/gprs/gprs_bssgp_util.c
@@ -0,0 +1,119 @@
+/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */
+
+/* (C) 2009-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_ns.h>
+
+struct gprs_ns_inst *bssgp_nsi;
+
+/* BSSGP Protocol specific, not implementation specific */
+/* FIXME: This needs to go into libosmocore after finished */
+
+/* Chapter 11.3.9 / Table 11.10: Cause coding */
+static const struct value_string bssgp_cause_strings[] = {
+	{ BSSGP_CAUSE_PROC_OVERLOAD,	"Processor overload" },
+	{ BSSGP_CAUSE_EQUIP_FAIL,	"Equipment Failure" },
+	{ BSSGP_CAUSE_TRASIT_NET_FAIL,	"Transit netowkr service failure" },
+	{ BSSGP_CAUSE_CAPA_GREATER_0KPBS,"Transmission capacity modified" },
+	{ BSSGP_CAUSE_UNKNOWN_MS,	"Unknown MS" },
+	{ BSSGP_CAUSE_UNKNOWN_BVCI,	"Unknown BVCI" },
+	{ BSSGP_CAUSE_CELL_TRAF_CONG,	"Cell traffic congestion" },
+	{ BSSGP_CAUSE_SGSN_CONG,	"SGSN congestion" },
+	{ BSSGP_CAUSE_OML_INTERV,	"O&M intervention" },
+	{ BSSGP_CAUSE_BVCI_BLOCKED,	"BVCI blocked" },
+	{ BSSGP_CAUSE_PFC_CREATE_FAIL,	"PFC create failure" },
+	{ BSSGP_CAUSE_SEM_INCORR_PDU,	"Semantically incorrect PDU" },
+	{ BSSGP_CAUSE_INV_MAND_INF,	"Invalid mandatory information" },
+	{ BSSGP_CAUSE_MISSING_MAND_IE,	"Missing mandatory IE" },
+	{ BSSGP_CAUSE_MISSING_COND_IE,	"Missing conditional IE" },
+	{ BSSGP_CAUSE_UNEXP_COND_IE,	"Unexpected conditional IE" },
+	{ BSSGP_CAUSE_COND_IE_ERR,	"Conditional IE error" },
+	{ BSSGP_CAUSE_PDU_INCOMP_STATE,	"PDU incompatible with protocol state" },
+	{ BSSGP_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error - unspecified" },
+	{ BSSGP_CAUSE_PDU_INCOMP_FEAT, 	"PDU not compatible with feature set" },
+	{ 0, NULL },
+};
+
+const char *bssgp_cause_str(enum gprs_bssgp_cause cause)
+{
+	return get_value_string(bssgp_cause_strings, cause);
+}
+
+
+struct msgb *bssgp_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(4096, 128, "BSSGP");
+}
+
+/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */
+int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
+			 uint16_t bvci, uint16_t ns_bvci)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	uint16_t _bvci;
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = ns_bvci;
+
+	bgph->pdu_type = pdu_type;
+	_bvci = htons(bvci);
+	msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* Chapter 10.4.14: Status */
+int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+
+	DEBUGPC(DBSSGP, "BSSGP: TX STATUS, cause=%s\n", bssgp_cause_str(cause));
+	msgb_nsei(msg) = msgb_nsei(orig_msg);
+	msgb_bvci(msg) = 0;
+
+	bgph->pdu_type = BSSGP_PDUT_STATUS;
+	msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
+	if (bvci) {
+		uint16_t _bvci = htons(*bvci);
+		msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+	}
+	if (orig_msg)
+		msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR,
+			      msgb_bssgp_len(orig_msg), msgb_bssgph(orig_msg));
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
diff --git a/openbsc/src/gprs/gprs_llc.c b/openbsc/src/gprs/gprs_llc.c
new file mode 100644
index 0000000..9c75a3d
--- /dev/null
+++ b/openbsc/src/gprs/gprs_llc.c
@@ -0,0 +1,549 @@
+/* GPRS LLC protocol implementation as per 3GPP TS 04.64 */
+
+/* (C) 2009-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/timer.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/crc24.h>
+
+/* Section 4.5.2 Logical Link States + Annex C.2 */
+enum gprs_llc_ll_state {
+	GPRS_LLS_UNASSIGNED	= 1,	/* No TLLI yet */
+	GPRS_LLS_ASSIGNED_ADM	= 2,	/* TLLI assigned */
+	GPRS_LLS_LOCAL_EST	= 3,	/* Local Establishment */
+	GPRS_LLS_REMOTE_EST	= 4,	/* Remote Establishment */
+	GPRS_LLS_ABM		= 5,
+	GPRS_LLS_LOCAL_REL	= 6,	/* Local Release */
+	GPRS_LLS_TIMER_REC 	= 7,	/* Timer Recovery */
+};
+
+/* Section 4.7.1: Logical Link Entity: One per DLCI (TLLI + SAPI) */
+struct gprs_llc_lle {
+	struct llist_head list;
+	struct timer_list t200;
+	struct timer_list t201;	/* wait for acknowledgement */
+
+	enum gprs_llc_ll_state state;
+
+	uint32_t tlli;
+	uint32_t sapi;
+
+	uint8_t v_sent;
+	uint8_t v_ack;
+	uint8_t v_recv;
+
+	unsigned int n200;
+	unsigned int retrans_ctr;
+
+	/* over which BSSGP BTS ctx do we need to transmit */
+	uint16_t bvci;
+	uint16_t nsei;
+};
+
+static LLIST_HEAD(gprs_llc_lles);
+void *llc_tall_ctx;
+
+/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */
+static struct gprs_llc_lle *lle_by_tlli_sapi(uint32_t tlli, uint32_t sapi)
+{
+	struct gprs_llc_lle *lle;
+
+	llist_for_each_entry(lle, &gprs_llc_lles, list) {
+		if (lle->tlli == tlli && lle->sapi == sapi)
+			return lle;
+	}
+	return NULL;
+}
+
+static struct gprs_llc_lle *lle_alloc(uint32_t tlli, uint32_t sapi)
+{
+	struct gprs_llc_lle *lle;
+
+	lle = talloc_zero(llc_tall_ctx, struct gprs_llc_lle);
+	if (!lle)
+		return NULL;
+
+	lle->tlli = tlli;
+	lle->sapi = sapi;
+	lle->state = GPRS_LLS_UNASSIGNED;
+	llist_add(&lle->list, &gprs_llc_lles);
+
+	return lle;
+}
+
+enum gprs_llc_cmd {
+	GPRS_LLC_NULL,
+	GPRS_LLC_RR,
+	GPRS_LLC_ACK,
+	GPRS_LLC_RNR,
+	GPRS_LLC_SACK,
+	GPRS_LLC_DM,
+	GPRS_LLC_DISC,
+	GPRS_LLC_UA,
+	GPRS_LLC_SABM,
+	GPRS_LLC_FRMR,
+	GPRS_LLC_XID,
+};
+
+struct gprs_llc_hdr_parsed {
+	uint8_t sapi;
+	uint8_t is_cmd:1,
+		 ack_req:1,
+		 is_encrypted:1;
+	uint32_t seq_rx;
+	uint32_t seq_tx;
+	uint32_t fcs;
+	uint32_t fcs_calc;
+	uint8_t *data;
+	uint16_t data_len;
+	enum gprs_llc_cmd cmd;
+};
+
+#define LLC_ALLOC_SIZE 16384
+#define UI_HDR_LEN	3
+#define N202		4
+#define CRC24_LENGTH	3
+
+static int gprs_llc_fcs(uint8_t *data, unsigned int len)
+{
+	uint32_t fcs_calc;
+
+	fcs_calc = crc24_calc(INIT_CRC24, data, len);
+	fcs_calc = ~fcs_calc;
+	fcs_calc &= 0xffffff;
+
+	return fcs_calc;
+}
+
+static void t200_expired(void *data)
+{
+	struct gprs_llc_lle *lle = data;
+
+	/* 8.5.1.3: Expiry of T200 */
+
+	if (lle->retrans_ctr >= lle->n200) {
+		/* FIXME: LLGM-STATUS-IND, LL-RELEASE-IND/CNF */
+		lle->state = GPRS_LLS_ASSIGNED_ADM;
+	}
+
+	switch (lle->state) {
+	case GPRS_LLS_LOCAL_EST:
+		/* retransmit SABM */
+		/* re-start T200 */
+		lle->retrans_ctr++;
+		break;
+	case GPRS_LLS_LOCAL_REL:
+		/* retransmit DISC */
+		/* re-start T200 */
+		lle->retrans_ctr++;
+		break;
+	}
+
+}
+
+static void t201_expired(void *data)
+{
+	struct gprs_llc_lle *lle = data;
+
+	if (lle->retrans_ctr < lle->n200) {
+		/* transmit apropriate supervisory frame (8.6.4.1) */
+		/* set timer T201 */
+		lle->retrans_ctr++;
+	}
+}
+
+int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, int command,
+		  enum gprs_llc_u_cmd u_cmd, int pf_bit)
+{
+	uint8_t *fcs, *llch;
+	uint8_t addr, ctrl;
+	uint32_t fcs_calc;
+
+	/* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */
+
+	/* Address Field */
+	addr = sapi & 0xf;
+	if (command)
+		addr |= 0x40;
+
+	/* 6.3 Figure 8 */
+	ctrl = 0xe0 | u_cmd;
+	if (pf_bit)
+		ctrl |= 0x10;
+
+	/* prepend LLC UI header */
+	llch = msgb_push(msg, 2);
+	llch[0] = addr;
+	llch[1] = ctrl;
+
+	/* append FCS to end of frame */
+	fcs = msgb_put(msg, 3);
+	fcs_calc = gprs_llc_fcs(llch, fcs - llch);
+	fcs[0] = fcs_calc & 0xff;
+	fcs[1] = (fcs_calc >> 8) & 0xff;
+	fcs[2] = (fcs_calc >> 16) & 0xff;
+
+	/* Identifiers passed down: (BVCI, NSEI) */
+
+	return gprs_bssgp_tx_dl_ud(msg);
+}
+
+/* Send XID response to LLE */
+static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg)
+{
+	/* copy identifiers from LLE to ensure lower layers can route */
+	msgb_tlli(msg) = lle->tlli;
+	msgb_bvci(msg) = lle->bvci;
+	msgb_nsei(msg) = lle->nsei;
+
+	return gprs_llc_tx_u(msg, lle->sapi, 0, GPRS_LLC_U_XID, 1);
+}
+
+/* Transmit a UI frame over the given SAPI */
+int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command)
+{
+	struct gprs_llc_lle *lle;
+	uint8_t *fcs, *llch;
+	uint8_t addr, ctrl[2];
+	uint32_t fcs_calc;
+	uint16_t nu = 0;
+
+	/* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */
+
+	/* look-up or create the LL Entity for this (TLLI, SAPI) tuple */
+	lle = lle_by_tlli_sapi(msgb_tlli(msg), sapi);
+	if (!lle)
+		lle = lle_alloc(msgb_tlli(msg), sapi);
+	/* Update LLE's (BVCI, NSEI) tuple */
+	lle->bvci = msgb_bvci(msg);
+	lle->nsei = msgb_nsei(msg);
+
+	/* Address Field */
+	addr = sapi & 0xf;
+	if (command)
+		addr |= 0x40;
+
+	/* Control Field */
+	ctrl[0] = 0xc0;
+	ctrl[0] |= nu >> 6;
+	ctrl[1] = (nu << 2) & 0xfc;
+	ctrl[1] |= 0x01; /* Protected Mode */
+
+	/* prepend LLC UI header */
+	llch = msgb_push(msg, 3);
+	llch[0] = addr;
+	llch[1] = ctrl[0];
+	llch[2] = ctrl[1];
+
+	/* append FCS to end of frame */
+	fcs = msgb_put(msg, 3);
+	fcs_calc = gprs_llc_fcs(llch, fcs - llch);
+	fcs[0] = fcs_calc & 0xff;
+	fcs[1] = (fcs_calc >> 8) & 0xff;
+	fcs[2] = (fcs_calc >> 16) & 0xff;
+
+	/* Identifiers passed down: (BVCI, NSEI) */
+
+	return gprs_bssgp_tx_dl_ud(msg);
+}
+
+static int gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph)
+{
+	DEBUGP(DGPRS, "LLC SAPI=%u %c %c FCS=0x%06x(%s) ",
+		gph->sapi, gph->is_cmd ? 'C' : 'R', gph->ack_req ? 'A' : ' ',
+		gph->fcs, gph->fcs_calc == gph->fcs ? "correct" : "WRONG");
+
+	if (gph->cmd)
+		DEBUGPC(DGPRS, "CMD=%u ", gph->cmd);
+
+	if (gph->data)
+		DEBUGPC(DGPRS, "DATA ");
+
+	DEBUGPC(DGPRS, "\n");
+}
+static int gprs_llc_hdr_rx(struct gprs_llc_hdr_parsed *gph,
+			   struct gprs_llc_lle *lle)
+{
+	switch (gph->cmd) {
+	case GPRS_LLC_SABM: /* Section 6.4.1.1 */
+		lle->v_sent = lle->v_ack = lle->v_recv = 0;
+		if (lle->state == GPRS_LLS_ASSIGNED_ADM) {
+			/* start re-establishment (8.7.1) */
+		}
+		lle->state = GPRS_LLS_REMOTE_EST;
+		/* FIXME: Send UA */
+		lle->state = GPRS_LLS_ABM;
+		/* FIXME: process data */
+		break;
+	case GPRS_LLC_DISC: /* Section 6.4.1.2 */
+		/* FIXME: Send UA */
+		/* terminate ABM */
+		lle->state = GPRS_LLS_ASSIGNED_ADM;
+		break;
+	case GPRS_LLC_UA: /* Section 6.4.1.3 */
+		if (lle->state == GPRS_LLS_LOCAL_EST)
+			lle->state = GPRS_LLS_ABM;
+		break;
+	case GPRS_LLC_DM: /* Section 6.4.1.4: ABM cannot be performed */
+		if (lle->state == GPRS_LLS_LOCAL_EST)
+			lle->state = GPRS_LLS_ASSIGNED_ADM;
+		break;
+	case GPRS_LLC_FRMR: /* Section 6.4.1.5 */
+		break;
+	case GPRS_LLC_XID: /* Section 6.4.1.6 */
+		/* FIXME: implement XID negotiation using SNDCP */
+		{
+			struct msgb *resp;
+			uint8_t *xid;
+			resp = msgb_alloc_headroom(4096, 1024, "LLC_XID");
+			xid = msgb_put(resp, gph->data_len);
+			memcpy(xid, gph->data, gph->data_len);
+			gprs_llc_tx_xid(lle, resp);
+		}
+		break;
+	}
+
+	return 0;
+}
+
+/* parse a GPRS LLC header, also check for invalid frames */
+static int gprs_llc_hdr_parse(struct gprs_llc_hdr_parsed *ghp,
+			      const uint8_t *llc_hdr, int len)
+{
+	uint8_t *ctrl = llc_hdr+1;
+	int is_sack = 0;
+	unsigned int crc_length;
+	uint32_t fcs_calc;
+
+	if (len <= CRC24_LENGTH)
+		return -EIO;
+
+	crc_length = len - CRC24_LENGTH;
+
+	ghp->ack_req = 0;
+
+	/* Section 5.5: FCS */
+	ghp->fcs = *(llc_hdr + len - 3);
+	ghp->fcs |= *(llc_hdr + len - 2) << 8;
+	ghp->fcs |= *(llc_hdr + len - 1) << 16;
+
+	/* Section 6.2.1: invalid PD field */
+	if (llc_hdr[0] & 0x80)
+		return -EIO;
+
+	/* This only works for the MS->SGSN direction */
+	if (llc_hdr[0] & 0x40)
+		ghp->is_cmd = 0;
+	else
+		ghp->is_cmd = 1;
+
+	ghp->sapi = llc_hdr[0] & 0xf;
+
+	/* Section 6.2.3: check for reserved SAPI */
+	switch (ghp->sapi) {
+	case 0:
+	case 4:
+	case 6:
+	case 0xa:
+	case 0xc:
+	case 0xd:
+	case 0xf:
+		return -EINVAL;
+	}
+
+	if ((ctrl[0] & 0x80) == 0) {
+		/* I (Information transfer + Supervisory) format */
+		uint8_t k;
+
+		ghp->data = ctrl + 3;
+
+		if (ctrl[0] & 0x40)
+			ghp->ack_req = 1;
+
+		ghp->seq_tx  = (ctrl[0] & 0x1f) << 4;
+		ghp->seq_tx |= (ctrl[1] >> 4);
+
+		ghp->seq_rx  = (ctrl[1] & 0x7) << 6;
+		ghp->seq_rx |= (ctrl[2] >> 2);
+
+		switch (ctrl[2] & 0x03) {
+		case 0:
+			ghp->cmd = GPRS_LLC_RR;
+			break;
+		case 1:
+			ghp->cmd = GPRS_LLC_ACK;
+			break;
+		case 2:
+			ghp->cmd = GPRS_LLC_RNR;
+			break;
+		case 3:
+			ghp->cmd = GPRS_LLC_SACK;
+			k = ctrl[3] & 0x1f;
+			ghp->data += 1 + k;
+			break;
+		}
+		ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+	} else if ((ctrl[0] & 0xc0) == 0x80) {
+		/* S (Supervisory) format */
+		ghp->data = NULL;
+		ghp->data_len = 0;
+
+		if (ctrl[0] & 0x20)
+			ghp->ack_req = 1;
+		ghp->seq_rx  = (ctrl[0] & 0x7) << 6;
+		ghp->seq_rx |= (ctrl[1] >> 2);
+
+		switch (ctrl[1] & 0x03) {
+		case 0:
+			ghp->cmd = GPRS_LLC_RR;
+			break;
+		case 1:
+			ghp->cmd = GPRS_LLC_ACK;
+			break;
+		case 2:
+			ghp->cmd = GPRS_LLC_RNR;
+			break;
+		case 3:
+			ghp->cmd = GPRS_LLC_SACK;
+			break;
+		}
+	} else if ((ctrl[0] & 0xe0) == 0xc0) {
+		/* UI (Unconfirmed Inforamtion) format */
+		ghp->data = ctrl + 2;
+		ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+
+		ghp->seq_tx  = (ctrl[0] & 0x7) << 6;
+		ghp->seq_tx |= (ctrl[1] >> 2);
+		if (ctrl[1] & 0x02) {
+			ghp->is_encrypted = 1;
+			/* FIXME: encryption */
+		}
+		if (ctrl[1] & 0x01) {
+			/* FCS over hdr + all inf fields */
+		} else {
+			/* FCS over hdr + N202 octets (4) */
+			if (crc_length > UI_HDR_LEN + N202)
+				crc_length = UI_HDR_LEN + N202;
+		}
+	} else {
+		/* U (Unnumbered) format: 1 1 1 P/F M4 M3 M2 M1 */
+		ghp->data = NULL;
+		ghp->data_len = 0;
+
+		switch (ctrl[0] & 0xf) {
+		case GPRS_LLC_U_NULL_CMD:
+			ghp->cmd = GPRS_LLC_NULL;
+			break;
+		case GPRS_LLC_U_DM_RESP:
+			ghp->cmd = GPRS_LLC_DM;
+			break;
+		case GPRS_LLC_U_DISC_CMD:
+			ghp->cmd = GPRS_LLC_DISC;
+			break;
+		case GPRS_LLC_U_UA_RESP:
+			ghp->cmd = GPRS_LLC_UA;
+			break;
+		case GPRS_LLC_U_SABM_CMD:
+			ghp->cmd = GPRS_LLC_SABM;
+			break;
+		case GPRS_LLC_U_FRMR_RESP:
+			ghp->cmd = GPRS_LLC_FRMR;
+			break;
+		case GPRS_LLC_U_XID:
+			ghp->cmd = GPRS_LLC_XID;
+			ghp->data = ctrl + 1;
+			ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+			break;
+		default:
+			return -EIO;
+		}
+	}
+
+	/* calculate what FCS we expect */
+	ghp->fcs_calc = gprs_llc_fcs(llc_hdr, crc_length);
+
+	/* FIXME: parse sack frame */
+}
+
+/* receive an incoming LLC PDU (BSSGP-UL-UNITDATA-IND, 7.2.4.2) */
+int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv)
+{
+	struct bssgp_ud_hdr *udh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
+	struct gprs_llc_hdr *lh = msgb_llch(msg);
+	struct gprs_llc_hdr_parsed llhp;
+	struct gprs_llc_lle *lle;
+	int rc = 0;
+
+	/* Identifiers from DOWN: NSEI, BVCI, TLLI */
+
+	rc = gprs_llc_hdr_parse(&llhp, lh, TLVP_LEN(tv, BSSGP_IE_LLC_PDU));
+	/* FIXME */
+
+	gprs_llc_hdr_dump(&llhp);
+
+	/* find the LLC Entity for this TLLI+SAPI tuple */
+	lle = lle_by_tlli_sapi(msgb_tlli(msg), llhp.sapi);
+	/* allocate a new LLE if needed */
+	if (!lle)
+		lle = lle_alloc(msgb_tlli(msg), llhp.sapi);
+
+	/* Update LLE's (BVCI, NSEI) tuple */
+	lle->bvci = msgb_bvci(msg);
+	lle->nsei = msgb_nsei(msg);
+
+	rc = gprs_llc_hdr_rx(&llhp, lle);
+	/* FIXME */
+
+	if (llhp.data) {
+		msgb_gmmh(msg) = llhp.data;
+		switch (llhp.sapi) {
+		case GPRS_SAPI_GMM:
+			rc = gsm0408_gprs_rcvmsg(msg);
+			break;
+		case GPRS_SAPI_TOM2:
+		case GPRS_SAPI_TOM8:
+			/* FIXME */
+		case GPRS_SAPI_SNDCP3:
+		case GPRS_SAPI_SNDCP5:
+		case GPRS_SAPI_SNDCP9:
+		case GPRS_SAPI_SNDCP11:
+			/* FIXME */
+		case GPRS_SAPI_SMS:
+			/* FIXME */
+		default:
+			LOGP(DGPRS, LOGL_NOTICE, "Unsupported SAPI %u\n", llhp.sapi);
+			rc = -EINVAL;
+			break;
+		}
+	}
+
+	return rc;
+}
diff --git a/openbsc/src/gprs/gprs_ns.c b/openbsc/src/gprs/gprs_ns.c
new file mode 100644
index 0000000..69c96ca
--- /dev/null
+++ b/openbsc/src/gprs/gprs_ns.c
@@ -0,0 +1,715 @@
+/* GPRS Networks Service (NS) messages on the Gb interfacebvci = msgb_bvci(msg);
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) */
+
+/* (C) 2009-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/* Some introduction into NS:  NS is used typically on top of frame relay,
+ * but in the ip.access world it is encapsulated in UDP packets.  It serves
+ * as an intermediate shim betwen BSSGP and the underlying medium.  It doesn't
+ * do much, apart from providing congestion notification and status indication.
+ *
+ * Terms:
+ * 	NS		Network Service
+ *	NSVC		NS Virtual Connection
+ *	NSEI		NS Entity Identifier
+ *	NSVL		NS Virtual Link
+ *	NSVLI		NS Virtual Link Identifier
+ *	BVC		BSSGP Virtual Connection
+ *	BVCI		BSSGP Virtual Connection Identifier
+ *	NSVCG		NS Virtual Connection Goup
+ *	Blocked		NS-VC cannot be used for user traffic
+ *	Alive		Ability of a NS-VC to provide communication
+ *
+ *  There can be multiple BSSGP virtual connections over one (group of) NSVC's.  BSSGP will
+ * therefore identify the BSSGP virtual connection by a BVCI passed down to NS.
+ * NS then has to firgure out which NSVC's are responsible for this BVCI.
+ * Those mappings are administratively configured.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+
+#define NS_ALLOC_SIZE	1024
+
+static const struct tlv_definition ns_att_tlvdef = {
+	.def = {
+		[NS_IE_CAUSE]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_VCI]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_PDU]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_BVCI]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_NSEI]	= { TLV_TYPE_TvLV, 0 },
+	},
+};
+
+/* Lookup struct gprs_nsvc based on NSVCI */
+static struct gprs_nsvc *nsvc_by_nsvci(struct gprs_ns_inst *nsi,
+					uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc->nsvci == nsvci)
+			return nsvc;
+	}
+	return NULL;
+}
+
+/* Lookup struct gprs_nsvc based on NSVCI */
+static struct gprs_nsvc *nsvc_by_nsei(struct gprs_ns_inst *nsi,
+					uint16_t nsei)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc->nsei == nsei)
+			return nsvc;
+	}
+	return NULL;
+}
+
+
+/* Lookup struct gprs_nsvc based on remote peer socket addr */
+static struct gprs_nsvc *nsvc_by_rem_addr(struct gprs_ns_inst *nsi,
+					  struct sockaddr_in *sin)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (!memcmp(&nsvc->ip.bts_addr, sin, sizeof(*sin)))
+			return nsvc;
+	}
+	return NULL;
+}
+
+static void gprs_ns_timer_cb(void *data);
+
+static struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+
+	nsvc = talloc_zero(nsi, struct gprs_nsvc);
+	nsvc->nsvci = nsvci;
+	/* before RESET procedure: BLOCKED and DEAD */
+	nsvc->state = NSE_S_BLOCKED;
+	nsvc->nsi = nsi;
+	nsvc->timer.cb = gprs_ns_timer_cb;
+	nsvc->timer.data = nsvc;
+
+	llist_add(&nsvc->list, &nsi->gprs_nsvcs);
+
+	return nsvc;
+}
+
+static void ns_dispatch_signal(struct gprs_nsvc *nsvc, unsigned int signal,
+			       uint8_t cause)
+{
+	struct ns_signal_data nssd;
+
+	nssd.nsvc = nsvc;
+	nssd.cause = cause;
+
+	dispatch_signal(SS_NS, signal, &nssd);
+}
+
+/* Section 10.3.2, Table 13 */
+static const struct value_string ns_cause_str[] = {
+	{ NS_CAUSE_TRANSIT_FAIL,	"Transit network failure" },
+	{ NS_CAUSE_OM_INTERVENTION, 	"O&M intervention" },
+	{ NS_CAUSE_EQUIP_FAIL,		"Equipment failure" },
+	{ NS_CAUSE_NSVC_BLOCKED,	"NS-VC blocked" },
+	{ NS_CAUSE_NSVC_UNKNOWN,	"NS-VC unknown" },
+	{ NS_CAUSE_BVCI_UNKNOWN,	"BVCI unknown" },
+	{ NS_CAUSE_SEM_INCORR_PDU,	"Semantically incorrect PDU" },
+	{ NS_CAUSE_PDU_INCOMP_PSTATE,	"PDU not compatible with protocol state" },
+	{ NS_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error, unspecified" },
+	{ NS_CAUSE_INVAL_ESSENT_IE,	"Invalid essential IE" },
+	{ NS_CAUSE_MISSING_ESSENT_IE,	"Missing essential IE" },
+	{ 0, NULL }
+};
+
+const char *gprs_ns_cause_str(enum ns_cause cause)
+{
+	return get_value_string(ns_cause_str, cause);
+}
+
+static int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg);
+
+static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	int ret;
+
+	switch (nsvc->nsi->ll) {
+	case GPRS_NS_LL_UDP:
+		ret = nsip_sendmsg(nsvc, msg);
+		break;
+	default:
+		LOGP(DNS, LOGL_ERROR, "unsupported NS linklayer %u\n", nsvc->nsi->ll);
+		msgb_free(msg);
+		ret = -EIO;
+		break;
+	}
+	return ret;
+}
+
+static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+
+	if (!msg)
+		return -ENOMEM;
+
+	nsh = (struct gprs_ns_hdr *) msgb_put(msg, sizeof(*nsh));
+
+	nsh->pdu_type = pdu_type;
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci = htons(nsvc->nsvci);
+	uint16_t nsei = htons(nsvc->nsei);
+
+	if (!msg)
+		return -ENOMEM;
+
+	nsh = (struct gprs_ns_hdr *) msgb_put(msg, sizeof(*nsh));
+	nsh->pdu_type = NS_PDUT_RESET;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+	msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei);
+
+	return gprs_ns_tx(nsvc, msg);
+
+}
+
+int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci = htons(nsvc->nsvci);
+
+	if (!msg)
+		return -ENOMEM;
+
+	/* be conservative and mark it as blocked even now! */
+	nsvc->state |= NSE_S_BLOCKED;
+
+	nsh = (struct gprs_ns_hdr *) msgb_put(msg, sizeof(*nsh));
+	nsh->pdu_type = NS_PDUT_BLOCK;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc)
+{
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK);
+}
+
+#define NS_ALIVE_RETRIES  10	/* after 3 failed retransmit we declare BTS as dead */
+
+static const uint8_t timer_mode_tout[_NSVC_TIMER_NR] = {
+	[NSVC_TIMER_TNS_RESET]	= 60,
+	[NSVC_TIMER_TNS_ALIVE]	= 3,
+	[NSVC_TIMER_TNS_TEST]	= 30,
+};
+
+static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode)
+{
+	nsvc->alive_retries = 0;
+
+	if (bsc_timer_pending(&nsvc->timer))
+		bsc_del_timer(&nsvc->timer);
+
+	nsvc->timer_mode = mode;
+	bsc_schedule_timer(&nsvc->timer, timer_mode_tout[mode], 0);
+}
+
+static void gprs_ns_timer_cb(void *data)
+{
+	struct gprs_nsvc *nsvc = data;
+
+	switch (nsvc->timer_mode) {
+	case NSVC_TIMER_TNS_ALIVE:
+		/* Tns-alive case: we expired without response ! */
+		nsvc->alive_retries++;
+		if (nsvc->alive_retries > NS_ALIVE_RETRIES) {
+			/* mark as dead and blocked */
+			nsvc->state = NSE_S_BLOCKED;
+			DEBUGP(DNS, "NSEI=%u Tns-alive expired more then "
+				"%u times, blocking NS-VC\n", nsvc->nsei,
+				NS_ALIVE_RETRIES);
+			/* FIXME: inform higher layers */
+			return;
+		}
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+		break;
+	case NSVC_TIMER_TNS_TEST:
+		/* Tns-test case: send NS-ALIVE PDU */
+		gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE);
+		/* start Tns-alive timer */
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+		break;
+	case NSVC_TIMER_TNS_RESET:
+		/* Chapter 7.3: Re-send the RESET */
+		gprs_ns_tx_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET);
+		break;
+	case _NSVC_TIMER_NR:
+		break;
+	}
+}
+
+/* Section 9.2.6 */
+static int gprs_ns_tx_reset_ack(struct gprs_nsvc *nsvc)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS");
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci, nsei;
+
+	if (!msg)
+		return -ENOMEM;
+
+	nsvci = htons(nsvc->nsvci);
+	nsei = htons(nsvc->nsei);
+
+	nsh = (struct gprs_ns_hdr *) msgb_put(msg, sizeof(*nsh));
+
+	nsh->pdu_type = NS_PDUT_RESET_ACK;
+
+	DEBUGP(DNS, "NSEI=%u Tx NS RESET ACK (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
+	msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+/* Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive */
+int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
+{
+	struct gprs_nsvc *nsvc;
+	struct gprs_ns_hdr *nsh;
+	uint16_t bvci = msgb_bvci(msg);
+
+	nsvc = nsvc_by_nsei(nsi, msgb_nsei(msg));
+	if (!nsvc) {
+		LOGP(DNS, LOGL_ERROR, "Unable to resolve NSEI %u "
+			"to NS-VC!\n", msgb_nsei(msg));
+		return -EINVAL;
+	}
+
+	if (!(nsvc->state & NSE_S_ALIVE)) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u is not alive, cannot send\n",
+			nsvc->nsei);
+		return -EBUSY;
+	}
+	if (nsvc->state & NSE_S_BLOCKED) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u is blocked, cannot send\n",
+			nsvc->nsei);
+		return -EBUSY;
+	}
+
+	nsh = (struct gprs_ns_hdr *) msgb_push(msg, sizeof(*nsh) + 3);
+	if (!nsh) {
+		LOGP(DNS, LOGL_ERROR, "Not enough headroom for NS header\n");
+		return -EIO;
+	}
+
+	nsh->pdu_type = NS_PDUT_UNITDATA;
+	/* spare octet in data[0] */
+	nsh->data[1] = bvci >> 8;
+	nsh->data[2] = bvci & 0xff;
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+/* Section 9.2.10: receive side */
+static int gprs_ns_rx_unitdata(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h;
+	uint16_t bvci;
+
+	/* spare octet in data[0] */
+	bvci = nsh->data[1] << 8 | nsh->data[2];
+	msgb_bssgph(msg) = &nsh->data[3];
+	msgb_bvci(msg) = bvci;
+
+	/* call upper layer (BSSGP) */
+	return nsvc->nsi->cb(GPRS_NS_EVT_UNIT_DATA, nsvc, msg, bvci);
+}
+
+/* Section 9.2.7 */
+static int gprs_ns_rx_status(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t cause;
+	int rc;
+
+	DEBUGP(DNS, "NSEI=%u NS STATUS ", nsvc->nsei);
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, msgb_l2len(msg), 0, 0);
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE)) {
+		DEBUGPC(DNS, "missing cause IE\n");
+		return -EINVAL;
+	}
+
+	cause = *TLVP_VAL(&tp, NS_IE_CAUSE);
+	DEBUGPC(DNS, "cause=%s\n", gprs_ns_cause_str(cause));
+
+	return 0;
+}
+
+/* Section 7.3 */
+static int gprs_ns_rx_reset(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t *cause;
+	uint16_t *nsvci, *nsei;
+	int rc;
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, msgb_l2len(msg), 0, 0);
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+	    !TLVP_PRESENT(&tp, NS_IE_VCI) ||
+	    !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+		/* FIXME: respond with NS_CAUSE_MISSING_ESSENT_IE */
+		LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+		return -EINVAL;
+	}
+
+	cause = (uint8_t *) TLVP_VAL(&tp, NS_IE_CAUSE);
+	nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
+	nsei = (uint16_t *) TLVP_VAL(&tp, NS_IE_NSEI);
+
+	DEBUGP(DNS, "NSEI=%u NS RESET (NSVCI=%u, cause=%s)\n",
+		nsvc->nsvci, nsvc->nsei, gprs_ns_cause_str(*cause));
+
+	nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE;
+	nsvc->nsei = ntohs(*nsei);
+	nsvc->nsvci = ntohs(*nsvci);
+
+	/* mark the NS-VC as blocked and alive */
+	/* start the test procedure */
+	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+
+	/* inform interested parties about the fact that this NSVC
+	 * has received RESET */
+	ns_dispatch_signal(nsvc, S_NS_RESET, *cause);
+
+	return gprs_ns_tx_reset_ack(nsvc);
+}
+
+static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t *cause;
+	int rc;
+
+	DEBUGP(DNS, "NSEI=%u Rx NS BLOCK\n", nsvc->nsei);
+
+	nsvc->state |= NSE_S_BLOCKED;
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, msgb_l2len(msg), 0, 0);
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+	    !TLVP_PRESENT(&tp, NS_IE_VCI)) {
+		/* FIXME: respond with NS_CAUSE_MISSING_ESSENT_IE */
+		LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+		return -EINVAL;
+	}
+
+	cause = (uint8_t *) TLVP_VAL(&tp, NS_IE_CAUSE);
+	//nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
+
+	ns_dispatch_signal(nsvc, S_NS_BLOCK, *cause);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_BLOCK_ACK);
+}
+
+/* main entry point, here incoming NS frames enter */
+int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
+		   struct sockaddr_in *saddr)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct gprs_nsvc *nsvc;
+	int rc = 0;
+
+	/* look up the NSVC based on source address */
+	nsvc = nsvc_by_rem_addr(nsi, saddr);
+	if (!nsvc) {
+		/* Only the RESET procedure creates a new NSVC */
+		if (nsh->pdu_type != NS_PDUT_RESET) {
+			LOGP(DNS, LOGL_INFO, "Ignoring NS PDU type 0x%0x "
+				"from %s for non-existing NS-VC\n",
+				nsh->pdu_type, inet_ntoa(saddr->sin_addr));
+			//gprs_ns_tx_reset(nsvc, NS_CAUSE_NSVC_UNKNOWN);
+			return -EIO;
+		}
+		LOGP(DNS, LOGL_INFO, "Creating NS-VC for BSS at %s:%u\n",
+			inet_ntoa(saddr->sin_addr), ntohs(saddr->sin_port));
+		nsvc = nsvc_create(nsi, 0xffff);
+		nsvc->ip.bts_addr = *saddr;
+	} else
+		msgb_nsei(msg) = nsvc->nsei;
+
+	switch (nsh->pdu_type) {
+	case NS_PDUT_ALIVE:
+		/* remote end inquires whether we're still alive,
+		 * we need to respond with ALIVE_ACK */
+		rc = gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE_ACK);
+		break;
+	case NS_PDUT_ALIVE_ACK:
+		/* stop Tns-alive */
+		bsc_del_timer(&nsvc->timer);
+		/* start Tns-test */
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
+		if (nsvc->remote_end_is_sgsn) {
+			/* FIXME: this should be one level higher */
+			if (nsvc->state & NSE_S_BLOCKED)
+				rc = gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK);
+		}
+		break;
+	case NS_PDUT_UNITDATA:
+		/* actual user data */
+		rc = gprs_ns_rx_unitdata(nsvc, msg);
+		break;
+	case NS_PDUT_STATUS:
+		rc = gprs_ns_rx_status(nsvc, msg);
+		break;
+	case NS_PDUT_RESET:
+		rc = gprs_ns_rx_reset(nsvc, msg);
+		break;
+	case NS_PDUT_RESET_ACK:
+		DEBUGP(DNS, "NSEI=%u Rx NS RESET ACK\n", nsvc->nsei);
+		/* mark remote NS-VC as blocked + active */
+		nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		if (nsvc->remote_end_is_sgsn) {
+			/* stop RESET timer */
+			bsc_del_timer(&nsvc->timer);
+			/* send ALIVE PDU */
+			rc = gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE);
+			nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+			/* mark local state as BLOCKED + ALIVE */
+			nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		}
+		break;
+	case NS_PDUT_UNBLOCK:
+		/* Section 7.2: unblocking procedure */
+		DEBUGP(DNS, "NSEI=%u Rx NS UNBLOCK\n", nsvc->nsei);
+		nsvc->state &= ~NSE_S_BLOCKED;
+		ns_dispatch_signal(nsvc, S_NS_UNBLOCK, 0);
+		rc = gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK);
+		break;
+	case NS_PDUT_UNBLOCK_ACK:
+		DEBUGP(DNS, "NSEI=%u Rx NS UNBLOCK ACK\n", nsvc->nsei);
+		/* mark remote NS-VC as unblocked + active */
+		nsvc->remote_state = NSE_S_ALIVE;
+		if (nsvc->remote_end_is_sgsn)
+			nsvc->state = NSE_S_ALIVE;
+		break;
+	case NS_PDUT_BLOCK:
+		rc = gprs_ns_rx_block(nsvc, msg);
+		break;
+	case NS_PDUT_BLOCK_ACK:
+		DEBUGP(DNS, "NSEI=%u Rx NS BLOCK ACK\n", nsvc->nsei);
+		/* mark remote NS-VC as blocked + active */
+		nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		break;
+	default:
+		DEBUGP(DNS, "NSEI=%u Rx Unknown NS PDU type 0x%02x\n",
+			nsvc->nsei, nsh->pdu_type);
+		rc = -EINVAL;
+		break;
+	}
+	return rc;
+}
+
+struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb)
+{
+	struct gprs_ns_inst *nsi = talloc_zero(tall_bsc_ctx, struct gprs_ns_inst);
+
+	nsi->cb = cb;
+	INIT_LLIST_HEAD(&nsi->gprs_nsvcs);
+
+	return nsi;
+}
+
+void gprs_ns_destroy(struct gprs_ns_inst *nsi)
+{
+	/* FIXME: clear all timers */
+
+	/* recursively free the NSI and all its NSVCs */
+	talloc_free(nsi);
+}
+
+
+/* NS-over-IP code, according to 3GPP TS 48.016 Chapter 6.2
+ * We don't support Size Procedure, Configuration Procedure, ChangeWeight Procedure */
+
+/* Read a single NS-over-IP message */
+static struct msgb *read_nsip_msg(struct bsc_fd *bfd, int *error,
+				  struct sockaddr_in *saddr)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Abis/IP/GPRS-NS");
+	int ret = 0;
+	socklen_t saddr_len = sizeof(*saddr);
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0,
+			(struct sockaddr *)saddr, &saddr_len);
+	if (ret < 0) {
+		LOGP(DNS, LOGL_ERROR, "recv error %s during NSIP recv\n",
+			strerror(errno));
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	} else if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msg->l2h = msg->data;
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+static int handle_nsip_read(struct bsc_fd *bfd)
+{
+	int error;
+	struct sockaddr_in saddr;
+	struct gprs_ns_inst *nsi = bfd->data;
+	struct msgb *msg = read_nsip_msg(bfd, &error, &saddr);
+
+	if (!msg)
+		return error;
+
+	return gprs_ns_rcvmsg(nsi, msg, &saddr);
+}
+
+static int handle_nsip_write(struct bsc_fd *bfd)
+{
+	/* FIXME: actually send the data here instead of nsip_sendmsg() */
+	return -EIO;
+}
+
+int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	int rc;
+	struct gprs_ns_inst *nsi = nsvc->nsi;
+	struct sockaddr_in *daddr = &nsvc->ip.bts_addr;
+
+	rc = sendto(nsi->nsip.fd.fd, msg->data, msg->len, 0,
+		  (struct sockaddr *)daddr, sizeof(*daddr));
+
+	talloc_free(msg);
+
+	return rc;
+}
+
+/* UDP Port 23000 carries the LLC-in-BSSGP-in-NS protocol stack */
+static int nsip_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_nsip_read(bfd);
+	if (what & BSC_FD_WRITE)
+		rc = handle_nsip_write(bfd);
+
+	return rc;
+}
+
+
+/* FIXME: this is currently in input/ipaccess.c */
+extern int make_sock(struct bsc_fd *bfd, int proto, uint16_t port,
+		     int (*cb)(struct bsc_fd *fd, unsigned int what));
+
+/* Listen for incoming GPRS packets */
+int nsip_listen(struct gprs_ns_inst *nsi, uint16_t udp_port)
+{
+	int ret;
+
+	ret = make_sock(&nsi->nsip.fd, IPPROTO_UDP, udp_port, nsip_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	nsi->ll = GPRS_NS_LL_UDP;
+	nsi->nsip.fd.data = nsi;
+
+	return ret;
+}
+
+/* Establish a connection (from the BSS) to the SGSN */
+struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi,
+				struct sockaddr_in *dest, uint16_t nsei,
+				uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_rem_addr(nsi, dest);
+	if (!nsvc) {
+		nsvc = nsvc_create(nsi, nsvci);
+		nsvc->ip.bts_addr = *dest;
+	}
+	nsvc->nsei = nsei;
+	nsvc->nsvci = nsvci;
+	nsvc->remote_end_is_sgsn = 1;
+
+	/* Initiate a RESET procedure */
+	if (gprs_ns_tx_reset(nsvc, NS_CAUSE_OM_INTERVENTION) < 0) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u, error resetting NS-VC\n",
+			nsei);
+	}
+	/* run a timer and re-transmit the reset request? */
+	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET);
+
+	return nsvc;
+}
diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c
new file mode 100644
index 0000000..ba46719
--- /dev/null
+++ b/openbsc/src/gprs/gprs_sgsn.c
@@ -0,0 +1,96 @@
+/* GPRS SGSN functionality */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+
+static LLIST_HEAD(sgsn_mm_ctxts);
+
+static int ra_id_equals(const struct gprs_ra_id *id1,
+			const struct gprs_ra_id *id2)
+{
+	return (id1->mcc == id2->mcc && id1->mnc == id2->mnc &&
+		id1->lac == id2->lac && id1->rac == id2->rac);
+}
+
+/* look-up a SGSN MM context based on TLLI + RAI */
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
+					const struct gprs_ra_id *raid)
+{
+	struct sgsn_mm_ctx *ctx;
+
+	llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+		if (tlli == ctx->tlli &&
+		    ra_id_equals(raid, &ctx->ra))
+			return ctx;
+	}
+	return NULL;
+}
+
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t p_tmsi)
+{
+	struct sgsn_mm_ctx *ctx;
+
+	llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+		if (p_tmsi == ctx->p_tmsi)
+			return ctx;
+	}
+	return NULL;
+}
+
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi)
+{
+	struct sgsn_mm_ctx *ctx;
+
+	llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+		if (!strcmp(imsi, ctx->imsi))
+			return ctx;
+	}
+	return NULL;
+
+}
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
+					const struct gprs_ra_id *raid)
+{
+	struct sgsn_mm_ctx *ctx = talloc_zero(NULL, struct sgsn_mm_ctx);
+
+	if (!ctx)
+		return NULL;
+
+	memcpy(&ctx->ra, raid, sizeof(ctx->ra));
+	ctx->tlli = tlli;
+	ctx->mm_state = GMM_DEREGISTERED;
+
+	llist_add(&ctx->list, &sgsn_mm_ctxts);
+
+	return ctx;
+}
diff --git a/openbsc/src/gprs/gprs_sndcp.c b/openbsc/src/gprs/gprs_sndcp.c
new file mode 100644
index 0000000..0d1a390
--- /dev/null
+++ b/openbsc/src/gprs/gprs_sndcp.c
@@ -0,0 +1,70 @@
+/* GPRS SNDCP protocol implementation as per 3GPP TS 04.65 */
+
+/* (C) 2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/timer.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+
+/* Chapter 7.2: SN-PDU Formats */
+struct sndcp_common_hdr {
+	/* octet 1 */
+	uint8_t nsapi:4;
+	uint8_t more:1;
+	uint8_t type:1;
+	uint8_t first:1;
+	uint8_t spare:1;
+	/* octet 2 */
+	uint8_t pcomp;
+	uint8_t dcomp;
+};
+
+struct sndcp_udata_hdr {
+	/* octet 3 */
+	uint8_t npdu_high:4;
+	uint8_t seg_nr:4;
+	/* octet 4 */
+	uint8_t npdu_low;
+};
+
+/* Entry point for the LL-UNITDATA.indication */
+int sndcp_unitdata_ind(struct msgb *msg, uint8_t sapi, uint8_t *hdr, uint8_t len)
+{
+	struct sndcp_udata_hdr *suh;
+	uint16_t npdu;
+
+	if (suh->type == 0) {
+		LOGP(DGPRS, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n");
+		return -EINVAL;
+	}
+
+	npdu = (suh->npdu_high << 8) | suh->npdu_low;
+}
+
diff --git a/openbsc/src/gprs/gsm_04_08_gprs.c b/openbsc/src/gprs/gsm_04_08_gprs.c
new file mode 100644
index 0000000..4a42113
--- /dev/null
+++ b/openbsc/src/gprs/gsm_04_08_gprs.c
@@ -0,0 +1,762 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2009-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/db.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/signal.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/paging.h>
+#include <openbsc/transaction.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_sgsn.h>
+
+/* 10.5.5.14 GPRS MM Cause / Table 10.5.147 */
+struct value_string gmm_cause_names[] = {
+	/* FIXME */
+	{ GMM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" },
+	{ GMM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" },
+	{ GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL,
+			"Message type non-existant or not implemented" },
+	{ GMM_CAUSE_MSGT_INCOMP_P_STATE,
+			"Message type not compatible with protocol state" },
+	{ GMM_CAUSE_IE_NOTEXIST_NOTIMPL,
+			"Information element non-existent or not implemented" },
+	{ GMM_CAUSE_COND_IE_ERR, "Conditional IE error" },
+	{ GMM_CAUSE_MSG_INCOMP_P_STATE,
+				"Message not compatible with protocol state " },
+	{ GMM_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" },
+	{ 0, NULL }
+};
+
+/* 10.5.6.6 SM Cause / Table 10.5.157 */
+struct value_string gsm_cause_names[] = {
+	{ GSM_CAUSE_INSUFF_RSRC, "Insufficient resources" },
+	{ GSM_CAUSE_MISSING_APN, "Missing or unknown APN" },
+	{ GSM_CAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" },
+	{ GSM_CAUSE_AUTH_FAILED, "User Authentication failed" },
+	{ GSM_CAUSE_ACT_REJ_GGSN, "Activation rejected by GGSN" },
+	{ GSM_CAUSE_ACT_REJ_UNSPEC, "Activation rejected, unspecified" },
+	{ GSM_CAUSE_SERV_OPT_NOTSUPP, "Service option not supported" },
+	{ GSM_CAUSE_REQ_SERV_OPT_NOTSUB,
+				"Requested service option not subscribed" },
+	{ GSM_CAUSE_SERV_OPT_TEMP_OOO,
+				"Service option temporarily out of order" },
+	{ GSM_CAUSE_NSAPI_IN_USE, "NSAPI already used" },
+	{ GSM_CAUSE_DEACT_REGULAR, "Regular deactivation" },
+	{ GSM_CAUSE_QOS_NOT_ACCEPTED, "QoS not accepted" },
+	{ GSM_CAUSE_NET_FAIL, "Network Failure" },
+	{ GSM_CAUSE_REACT_RQD, "Reactivation required" },
+	{ GSM_CAUSE_FEATURE_NOTSUPP, "Feature not supported " },
+	{ GSM_CAUSE_INVALID_TRANS_ID, "Invalid transaction identifier" },
+	{ GSM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" },
+	{ GSM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" },
+	{ GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL,
+			"Message type non-existant or not implemented" },
+	{ GSM_CAUSE_MSGT_INCOMP_P_STATE,
+			"Message type not compatible with protocol state" },
+	{ GSM_CAUSE_IE_NOTEXIST_NOTIMPL,
+			"Information element non-existent or not implemented" },
+	{ GSM_CAUSE_COND_IE_ERR, "Conditional IE error" },
+	{ GSM_CAUSE_MSG_INCOMP_P_STATE,
+				"Message not compatible with protocol state " },
+	{ GSM_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" },
+	{ 0, NULL }
+};
+
+static const char *att_name(uint8_t type)
+{
+	switch (type) {
+	case GPRS_ATT_T_ATTACH:
+		return "GPRS attach";
+	case GPRS_ATT_T_ATT_WHILE_IMSI:
+		return "GPRS attach while IMSI attached";
+	case GPRS_ATT_T_COMBINED:
+		return "Combined GPRS/IMSI attach";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *upd_name(uint8_t type)
+{
+	switch (type) {
+	case GPRS_UPD_T_RA:
+		return "RA updating";
+	case GPRS_UPD_T_RA_LA:
+		return "combined RA/LA updating";
+	case GPRS_UPD_T_RA_LA_IMSI_ATT:
+		return "combined RA/LA updating + IMSI attach";
+	case GPRS_UPD_T_PERIODIC:
+		return "periodic updating";
+	}
+	return "unknown";
+}
+
+/* Send a message through the underlying layer */
+static int gsm48_gmm_sendmsg(struct msgb *msg, int command)
+{
+	/* caller needs to provide TLLI, BVCI and NSEI */
+	return gprs_llc_tx_ui(msg, GPRS_SAPI_GMM, command);
+}
+
+/* copy identifiers from old message to new message, this
+ * is required so lower layers can route it correctly */
+static void gmm_copy_id(struct msgb *msg, const struct msgb *old)
+{
+	msgb_tlli(msg) = msgb_tlli(old);
+	msgb_bvci(msg) = msgb_bvci(old);
+	msgb_nsei(msg) = msgb_nsei(old);
+}
+
+static struct gsm48_qos default_qos = {
+	.delay_class = 4,	/* best effort */
+	.reliab_class = GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT,
+	.peak_tput = GSM48_QOS_PEAK_TPUT_32000bps,
+	.preced_class = GSM48_QOS_PC_NORMAL,
+	.mean_tput = GSM48_QOS_MEAN_TPUT_BEST_EFFORT,
+	.traf_class = GSM48_QOS_TC_INTERACTIVE,
+	.deliv_order = GSM48_QOS_DO_UNORDERED,
+	.deliv_err_sdu = GSM48_QOS_ERRSDU_YES,
+	.max_sdu_size = GSM48_QOS_MAXSDU_1520,
+	.max_bitrate_up = GSM48_QOS_MBRATE_63k,
+	.max_bitrate_down = GSM48_QOS_MBRATE_63k,
+	.resid_ber = GSM48_QOS_RBER_5e_2,
+	.sdu_err_ratio = GSM48_QOS_SERR_1e_2,
+	.handling_prio = 3,
+	.xfer_delay = 0x10,	/* 200ms */
+	.guar_bitrate_up = GSM48_QOS_MBRATE_0k,
+	.guar_bitrate_down = GSM48_QOS_MBRATE_0k,
+	.sig_ind = 0,	/* not optimised for signalling */
+	.max_bitrate_down_ext = 0,	/* use octet 9 */
+	.guar_bitrate_down_ext = 0,	/* use octet 13 */
+};
+
+/* Chapter 9.4.2: Attach accept */
+static int gsm48_tx_gmm_att_ack(struct msgb *old_msg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_attach_ack *aa;
+	struct gprs_ra_id ra_id;
+
+	DEBUGP(DMM, "<- GPRS ATTACH ACCEPT\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_ATTACH_ACK;
+
+	aa = (struct gsm48_attach_ack *) msgb_put(msg, sizeof(*aa));
+	aa->force_stby = 0;	/* not indicated */
+	aa->att_result = 1;	/* GPRS only */
+	aa->ra_upd_timer = GPRS_TMR_MINUTE | 10;
+	aa->radio_prio = 4;	/* lowest */
+	bssgp_parse_cell_id(&ra_id, msgb_bcid(old_msg));
+	gsm48_construct_ra(aa->ra_id.digits, &ra_id);
+
+	/* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Chapter 9.4.5: Attach reject */
+static int gsm48_tx_gmm_att_rej(struct msgb *old_msg, uint8_t gmm_cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "<- GPRS ATTACH REJECT\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_ATTACH_REJ;
+	gh->data[0] = gmm_cause;
+
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Transmit Chapter 9.4.12 Identity Request */
+static int gsm48_tx_gmm_id_req(struct msgb *old_msg, uint8_t id_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "-> GPRS IDENTITY REQUEST: mi_type=%02x\n", id_type);
+
+	gmm_copy_id(msg, old_msg);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_ID_REQ;
+	/* 10.5.5.9 ID type 2 + identity type and 10.5.5.7 'force to standby' IE */
+	gh->data[0] = id_type & 0xf;
+
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Check if we can already authorize a subscriber */
+static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx, struct msgb *msg)
+{
+	if (strlen(ctx->imei) && strlen(ctx->imsi)) {
+		ctx->mm_state = GMM_REGISTERED_NORMAL;
+		return gsm48_tx_gmm_att_ack(msg);
+	} 
+	if (!strlen(ctx->imei))
+		return gsm48_tx_gmm_id_req(msg, GSM_MI_TYPE_IMEI);
+
+	if (!strlen(ctx->imsi))
+		return gsm48_tx_gmm_id_req(msg, GSM_MI_TYPE_IMSI);
+
+	return 0;
+}
+
+/* Parse Chapter 9.4.13 Identity Response */
+static int gsm48_rx_gmm_id_resp(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+	struct gprs_ra_id ra_id;
+	struct sgsn_mm_ctx *ctx;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
+	DEBUGP(DMM, "GMM IDENTITY RESPONSE: mi_type=0x%02x MI(%s) ",
+		mi_type, mi_string);
+
+	bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+	ctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id);
+	if (!ctx) {
+		DEBUGP(DMM, "from unknown TLLI 0x%08x?!?\n", msgb_tlli(msg));
+		return -EINVAL;
+	}
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		/* we already have a mm context with current TLLI, but no
+		 * P-TMSI / IMSI yet.  What we now need to do is to fill
+		 * this initial context with data from the HLR */
+		strncpy(ctx->imsi, mi_string, sizeof(ctx->imei));
+		break;
+	case GSM_MI_TYPE_IMEI:
+		strncpy(ctx->imei, mi_string, sizeof(ctx->imei));
+		break;
+	case GSM_MI_TYPE_IMEISV:
+		break;
+	}
+
+	DEBUGPC(DMM, "\n");
+	/* Check if we can let the mobile station enter */
+	return gsm48_gmm_authorize(ctx, msg);
+}
+
+static void attach_rej_cb(void *data)
+{
+	struct sgsn_mm_ctx *ctx = data;
+
+	/* FIXME: determine through which BTS/TRX to send this */
+	//gsm48_tx_gmm_att_rej(ctx->tlli, GMM_CAUSE_MS_ID_NOT_DERIVED);
+	ctx->mm_state = GMM_DEREGISTERED;
+	/* FIXME: release the context */
+}
+
+static void schedule_reject(struct sgsn_mm_ctx *ctx)
+{
+	ctx->T = 3370;
+	ctx->timer.cb = attach_rej_cb;
+	ctx->timer.data = ctx;
+	bsc_schedule_timer(&ctx->timer, 6, 0);
+}
+
+/* Section 9.4.1 Attach request */
+static int gsm48_rx_gmm_att_req(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t *cur = gh->data, *msnc, *mi, *old_ra_info;
+	uint8_t msnc_len, att_type, mi_len, mi_type;
+	uint16_t drx_par;
+	uint32_t tmsi;
+	char mi_string[GSM48_MI_SIZE];
+	struct gprs_ra_id ra_id;
+	uint16_t cid;
+	struct sgsn_mm_ctx *ctx;
+
+	DEBUGP(DMM, "GMM ATTACH REQUEST ");
+
+	/* As per TS 04.08 Chapter 4.7.1.4, the attach request arrives either
+	 * with a foreign TLLI (P-TMSI that was allocated to the MS before),
+	 * or with random TLLI. */
+
+	cid = bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+
+	/* MS network capability 10.5.5.12 */
+	msnc_len = *cur++;
+	msnc = cur;
+	if (msnc_len > 2)
+		goto err_inval;
+	cur += msnc_len;
+
+	/* aTTACH Type 10.5.5.2 */
+	att_type = *cur++ & 0x0f;
+
+	/* DRX parameter 10.5.5.6 */
+	drx_par = *cur++;
+	drx_par |= *cur++ << 8;
+
+	/* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */
+	mi_len = *cur++;
+	mi = cur;
+	if (mi_len > 8)
+		goto err_inval;
+	mi_type = *mi & GSM_MI_TYPE_MASK;
+	cur += mi_len;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+
+	DEBUGPC(DMM, "MI(%s) type=\"%s\" ", mi_string, att_name(att_type));
+
+	/* Old routing area identification 10.5.5.15 */
+	old_ra_info = cur;
+	cur += 6;
+
+	/* MS Radio Access Capability 10.5.5.12a */
+
+	/* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status */
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		/* Try to find MM context based on IMSI */
+		ctx = sgsn_mm_ctx_by_imsi(mi_string);
+		if (!ctx) {
+#if 0
+			return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_IMSI_UNKNOWN);
+#else
+			/* As a temorary hack, we simply assume that the IMSI exists */
+			ctx = sgsn_mm_ctx_alloc(0, &ra_id);
+			if (!ctx)
+				return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_NET_FAIL);
+			strncpy(ctx->imsi, mi_string, sizeof(ctx->imsi));
+#endif
+		}
+		/* FIXME: Start some timer */
+		ctx->mm_state = GMM_COMMON_PROC_INIT;
+		ctx->tlli = msgb_tlli(msg);
+		break;
+	case GSM_MI_TYPE_TMSI:
+		tmsi = strtoul(mi_string, NULL, 10);
+		/* Try to find MM context based on P-TMSI */
+		ctx = sgsn_mm_ctx_by_ptmsi(tmsi);
+		if (!ctx) {
+			ctx = sgsn_mm_ctx_alloc(msgb_tlli(msg), &ra_id);
+			/* FIXME: Start some timer */
+			ctx->mm_state = GMM_COMMON_PROC_INIT;
+			ctx->tlli = msgb_tlli(msg);
+		}
+		break;
+	default:
+		return 0;
+	}
+	/* Update MM Context with currient RA and Cell ID */
+	ctx->ra = ra_id;
+	ctx->cell_id = cid;
+
+	/* FIXME: allocate a new P-TMSI (+ P-TMSI signature) */
+	/* FIXME: update the TLLI with the new local TLLI based on the P-TMSI */
+
+	DEBUGPC(DMM, "\n");
+
+	return ctx ? gsm48_gmm_authorize(ctx, msg) : 0;
+
+err_inval:
+	DEBUGPC(DMM, "\n");
+	return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_SEM_INCORR_MSG);
+}
+
+/* Chapter 9.4.15: Routing area update accept */
+static int gsm48_tx_gmm_ra_upd_ack(struct msgb *old_msg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_ra_upd_ack *rua;
+	struct gprs_ra_id ra_id;
+
+	DEBUGP(DMM, "<- ROUTING AREA UPDATE ACCEPT\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_RA_UPD_ACK;
+
+	rua = (struct gsm48_ra_upd_ack *) msgb_put(msg, sizeof(*rua));
+	rua->force_stby = 0;	/* not indicated */
+	rua->upd_result = 0;	/* RA updated */
+	rua->ra_upd_timer = GPRS_TMR_MINUTE | 10;
+
+	bssgp_parse_cell_id(&ra_id, msgb_bcid(old_msg));
+	gsm48_construct_ra(rua->ra_id.digits, &ra_id);
+
+	/* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Chapter 9.4.17: Routing area update reject */
+static int gsm48_tx_gmm_ra_upd_rej(struct msgb *old_msg, uint8_t cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "<- ROUTING AREA UPDATE REJECT\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2);
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_RA_UPD_REJ;
+	gh->data[0] = cause;
+	gh->data[1] = 0; /* ? */
+
+	/* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Chapter 9.4.14: Routing area update request */
+static int gsm48_rx_gmm_ra_upd_req(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	struct sgsn_mm_ctx *mmctx;
+	uint8_t *cur = gh->data;
+	struct gprs_ra_id old_ra_id;
+	uint8_t upd_type;
+
+	/* Update Type 10.5.5.18 */
+	upd_type = *cur++ & 0x0f;
+
+	DEBUGP(DMM, "GMM RA UPDATE REQUEST type=\"%s\" ", upd_name(upd_type));
+
+	/* Old routing area identification 10.5.5.15 */
+	gsm48_parse_ra(&old_ra_id, cur);
+	cur += 6;
+
+	/* MS Radio Access Capability 10.5.5.12a */
+
+	/* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status,
+	 * DRX parameter, MS network capability */
+
+	switch (upd_type) {
+	case GPRS_UPD_T_RA_LA:
+	case GPRS_UPD_T_RA_LA_IMSI_ATT:
+		DEBUGPC(DMM, " unsupported in Mode III, is your SI13 corrupt?\n");
+		return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_PROTO_ERR_UNSPEC);
+		break;
+	case GPRS_UPD_T_RA:
+	case GPRS_UPD_T_PERIODIC:
+		break;
+	}
+
+	/* Look-up the MM context based on old RA-ID and TLLI */
+	mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &old_ra_id);
+	if (!mmctx || mmctx->mm_state == GMM_DEREGISTERED) {
+		/* The MS has to perform GPRS attach */
+		DEBUGPC(DMM, " REJECT\n");
+		return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_IMPL_DETACHED);
+	}
+
+	/* Update the MM context with the new RA-ID */
+	bssgp_parse_cell_id(&mmctx->ra, msgb_bcid(msg));
+	/* Update the MM context with the new TLLI */
+	mmctx->tlli = msgb_tlli(msg);
+	/* FIXME: Update the MM context with the MS radio acc capabilities */
+	/* FIXME: Update the MM context with the MS network capabilities */
+
+	DEBUGPC(DMM, " ACCEPT\n");
+	return gsm48_tx_gmm_ra_upd_ack(msg);
+}
+
+static int gsm48_rx_gmm_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DMM, "GPRS MM STATUS (cause: %s)\n",
+		get_value_string(gmm_cause_names, gh->data[0]));
+
+	return 0;
+}
+
+/* GPRS Mobility Management */
+static int gsm0408_rcv_gmm(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	int rc;
+
+	switch (gh->msg_type) {
+	case GSM48_MT_GMM_RA_UPD_REQ:
+		rc = gsm48_rx_gmm_ra_upd_req(msg);
+		break;
+	case GSM48_MT_GMM_ATTACH_REQ:
+		rc = gsm48_rx_gmm_att_req(msg);
+		break;
+	case GSM48_MT_GMM_ID_RESP:
+		rc = gsm48_rx_gmm_id_resp(msg);
+		break;
+	case GSM48_MT_GMM_STATUS:
+		rc = gsm48_rx_gmm_status(msg);
+		break;
+	case GSM48_MT_GMM_RA_UPD_COMPL:
+		/* only in case SGSN offered new P-TMSI */
+	case GSM48_MT_GMM_ATTACH_COMPL:
+		/* only in case SGSN offered new P-TMSI */
+	case GSM48_MT_GMM_DETACH_REQ:
+	case GSM48_MT_GMM_PTMSI_REALL_COMPL:
+	case GSM48_MT_GMM_AUTH_CIPH_RESP:
+		DEBUGP(DMM, "Unimplemented GSM 04.08 GMM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 GMM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+static void msgb_put_pdp_addr_ipv4(struct msgb *msg, uint32_t ipaddr)
+{
+	uint8_t v[6];
+
+	v[0] = PDP_TYPE_ORG_IETF;
+	v[1] = PDP_TYPE_N_IETF_IPv4;
+	*(uint32_t *)(v+2) = htonl(ipaddr);
+
+	msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v);
+}
+
+static void msgb_put_pdp_addr_ppp(struct msgb *msg)
+{
+	uint8_t v[2];
+
+	v[0] = PDP_TYPE_ORG_ETSI;
+	v[1] = PDP_TYPE_N_ETSI_PPP;
+
+	msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v);
+}
+
+/* Section 9.5.2: Ativate PDP Context Accept */
+static int gsm48_tx_gsm_act_pdp_acc(struct msgb *old_msg, struct gsm48_act_pdp_ctx_req *req)
+{
+	struct gsm48_hdr *old_gh = (struct gsm48_hdr *) msgb_gmmh(old_msg);
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_act_pdp_ctx_ack *act_ack;
+	struct gsm48_hdr *gh;
+	uint8_t transaction_id = ((old_gh->proto_discr >> 4) ^ 0x8); /* flip */
+
+	DEBUGP(DMM, "<- ACTIVATE PDP CONTEXT ACK\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+	gh->msg_type = GSM48_MT_GSM_ACT_PDP_ACK;
+
+	/* Negotiated LLC SAPI */
+	msgb_v_put(msg, req->req_llc_sapi);
+	/* copy QoS parameters from original request */
+	msgb_lv_put(msg, sizeof(default_qos), (uint8_t *)&default_qos);
+	/* Radio priority 10.5.7.2 */
+	msgb_v_put(msg, 4);
+	/* PDP address */
+	msgb_put_pdp_addr_ipv4(msg, 0x01020304);
+	/* Optional: Protocol configuration options */
+	/* Optional: Packet Flow Identifier */
+
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Section 9.5.9: Deactivate PDP Context Accept */
+static int gsm48_tx_gsm_deact_pdp_acc(struct msgb *old_msg)
+{
+	struct gsm48_hdr *old_gh = (struct gsm48_hdr *) msgb_gmmh(old_msg);
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	uint8_t transaction_id = ((old_gh->proto_discr >> 4) ^ 0x8); /* flip */
+
+	DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT ACK\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+	gh->msg_type = GSM48_MT_GSM_DEACT_PDP_ACK;
+
+	return gsm48_gmm_sendmsg(msg, 0);
+}
+
+/* Section 9.5.1: Activate PDP Context Request */
+static int gsm48_rx_gsm_act_pdp_req(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	struct gsm48_act_pdp_ctx_req *act_req = (struct gsm48_act_pdp_ctx_req *) gh->data;
+	uint8_t *pdp_addr_lv = act_req->data;
+	uint8_t req_qos_len, req_pdpa_len;
+	uint8_t *req_qos, *req_pdpa;
+	struct tlv_parsed tp;
+
+	DEBUGP(DMM, "ACTIVATE PDP CONTEXT REQ: ");
+	req_qos_len = act_req->data[0];
+	req_qos = act_req->data + 1;	/* 10.5.6.5 */
+	req_pdpa_len = act_req->data[1 + req_qos_len];
+	req_pdpa = act_req->data + 1 + req_qos_len + 1;	/* 10.5.6.4 */
+
+	switch (req_pdpa[0] & 0xf) {
+	case 0x0:
+		DEBUGPC(DMM, "ETSI ");
+		break;
+	case 0x1:
+		DEBUGPC(DMM, "IETF ");
+		break;
+	case 0xf:
+		DEBUGPC(DMM, "Empty ");
+		break;
+	}
+
+	switch (req_pdpa[1]) {
+	case 0x21:
+		DEBUGPC(DMM, "IPv4 ");
+		if (req_pdpa_len >= 6) {
+			struct in_addr ia;
+			ia.s_addr = ntohl(*((uint32_t *) (req_pdpa+2)));
+			DEBUGPC(DMM, "%s ", inet_ntoa(ia));
+		}
+		break;
+	case 0x57:
+		DEBUGPC(DMM, "IPv6 ");
+		if (req_pdpa_len >= 18) {
+			/* FIXME: print IPv6 address */
+		}
+		break;
+	default:	
+		DEBUGPC(DMM, "0x%02x ", req_pdpa[1]);
+		break;
+	}
+
+	/* FIXME: parse TLV for AP name and protocol config options */
+	if (TLVP_PRESENT(&tp, GSM48_IE_GSM_APN)) {}
+	if (TLVP_PRESENT(&tp, GSM48_IE_GSM_PROTO_CONF_OPT)) {}
+
+	return gsm48_tx_gsm_act_pdp_acc(msg, act_req);
+}
+
+/* Section 9.5.8: Deactivate PDP Context Request */
+static int gsm48_rx_gsm_deact_pdp_req(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+
+	DEBUGP(DMM, "DEACTIVATE PDP CONTEXT REQ (cause: %s)\n",
+		get_value_string(gsm_cause_names, gh->data[0]));
+
+	return gsm48_tx_gsm_deact_pdp_acc(msg);
+}
+
+static int gsm48_rx_gsm_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DMM, "GPRS SM STATUS (cause: %s)\n",
+		get_value_string(gsm_cause_names, gh->data[0]));
+
+	return 0;
+}
+
+/* GPRS Session Management */
+static int gsm0408_rcv_gsm(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	int rc;
+
+	switch (gh->msg_type) {
+	case GSM48_MT_GSM_ACT_PDP_REQ:
+		rc = gsm48_rx_gsm_act_pdp_req(msg);
+		break;
+	case GSM48_MT_GSM_DEACT_PDP_REQ:
+		rc = gsm48_rx_gsm_deact_pdp_req(msg);
+	case GSM48_MT_GSM_STATUS:
+		rc = gsm48_rx_gsm_status(msg);
+		break;
+	case GSM48_MT_GSM_REQ_PDP_ACT_REJ:
+	case GSM48_MT_GSM_ACT_AA_PDP_REQ:
+	case GSM48_MT_GSM_DEACT_AA_PDP_REQ:
+		DEBUGP(DMM, "Unimplemented GSM 04.08 GSM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 GSM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+
+	}
+
+	return rc;
+}
+
+/* Main entry point for incoming 04.08 GPRS messages */
+int gsm0408_gprs_rcvmsg(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t pdisc = gh->proto_discr & 0x0f;
+	int rc = -EINVAL;
+
+	switch (pdisc) {
+	case GSM48_PDISC_MM_GPRS:
+		rc = gsm0408_rcv_gmm(msg);
+		break;
+	case GSM48_PDISC_SM_GPRS:
+		rc = gsm0408_rcv_gsm(msg);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 discriminator 0x%02x\n",
+			pdisc);
+		break;
+	}
+
+	return rc;
+}
diff --git a/openbsc/src/gprs/osmo_gbproxy.cfg b/openbsc/src/gprs/osmo_gbproxy.cfg
new file mode 100644
index 0000000..f2ef141
--- /dev/null
+++ b/openbsc/src/gprs/osmo_gbproxy.cfg
@@ -0,0 +1,13 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+!
+line vty
+ no login
+!
+gbproxy
+  nsip bss local port 23000
+  nsip sgsn remote ip 192.168.100.239
+  nsip sgsn remote port 23000
+  nsip sgsn nsei 1
+  nsip sgsn nsvci 11
diff --git a/openbsc/src/gprs/osmo_sgsn.cfg b/openbsc/src/gprs/osmo_sgsn.cfg
new file mode 100644
index 0000000..f39e853
--- /dev/null
+++ b/openbsc/src/gprs/osmo_sgsn.cfg
@@ -0,0 +1,9 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+!
+line vty
+ no login
+!
+sgsn
+  nsip local port 23000
diff --git a/openbsc/src/gprs/sgsn_main.c b/openbsc/src/gprs/sgsn_main.c
new file mode 100644
index 0000000..15f760d
--- /dev/null
+++ b/openbsc/src/gprs/sgsn_main.c
@@ -0,0 +1,143 @@
+/* GPRS SGSN Implementation */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/telnet_interface.h>
+#include <openbsc/vty.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+
+#include "../../bscconfig.h"
+
+/* this is here for the vty... it will never be called */
+void subscr_put() { abort(); }
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+void *tall_bsc_ctx;
+
+struct gprs_ns_inst *sgsn_nsi;
+
+const char *openbsc_version = "Osmocom NSIP Proxy " PACKAGE_VERSION;
+const char *openbsc_copyright =
+	"Copyright (C) 2010 Harald Welte and On-Waves\n"
+	"Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\n"
+	"Dieter Spaar, Andreas Eversberg, Holger Freyther\n\n"
+	"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
+	"This is free software: you are free to change and redistribute it.\n"
+	"There is NO WARRANTY, to the extent permitted by law.\n";
+
+static char *config_file = "osmo_sgsn.cfg";
+static struct sgsn_config sgcfg;
+
+/* call-back function for the NS protocol */
+static int sgsn_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+		      struct msgb *msg, u_int16_t bvci)
+{
+	int rc = 0;
+
+	switch (event) {
+	case GPRS_NS_EVT_UNIT_DATA:
+		/* hand the message into the BSSGP implementation */
+		rc = gprs_bssgp_rcvmsg(msg);
+		break;
+	default:
+		LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event);
+		if (msg)
+			talloc_free(msg);
+		rc = -EIO;
+		break;
+	}
+	return rc;
+}
+
+/* NSI that BSSGP uses when transmitting on NS */
+extern struct gprs_ns_inst *bssgp_nsi;
+
+int main(int argc, char **argv)
+{
+	struct gsm_network dummy_network;
+	struct log_target *stderr_target;
+	struct sockaddr_in sin;
+	int rc;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 0, "osmo_sgsn");
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	telnet_init(&dummy_network, 4245);
+	rc = sgsn_parse_config(config_file, &sgcfg);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n");
+		exit(2);
+	}
+
+	sgsn_nsi = gprs_ns_instantiate(&sgsn_ns_cb);
+	if (!sgsn_nsi) {
+		LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+		exit(1);
+	}
+	bssgp_nsi = sgcfg.nsi = sgsn_nsi;
+	nsip_listen(sgsn_nsi, sgcfg.nsip_listen_port);
+
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
+struct gsm_network;
+int bsc_vty_init(struct gsm_network *dummy)
+{
+	cmd_init(1);
+	vty_init();
+
+	openbsc_vty_add_cmds();
+        sgsn_vty_init();
+	return 0;
+}
+
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
new file mode 100644
index 0000000..ec18fcb
--- /dev/null
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -0,0 +1,146 @@
+/*
+ * (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_ns.h>
+
+#include <vty/command.h>
+#include <vty/vty.h>
+
+static struct sgsn_config *g_cfg = NULL;
+
+static struct cmd_node sgsn_node = {
+	SGSN_NODE,
+	"%s(sgsn)#",
+	1,
+};
+
+static int config_write_sgsn(struct vty *vty)
+{
+	struct in_addr ia;
+
+	vty_out(vty, "sgsn%s", VTY_NEWLINE);
+
+	if (g_cfg->nsip_listen_ip) {
+		ia.s_addr = htonl(g_cfg->nsip_listen_ip);
+		vty_out(vty, "  nsip local ip %s%s", inet_ntoa(ia),
+			VTY_NEWLINE);
+	}
+	vty_out(vty, "  nsip local port %u%s", g_cfg->nsip_listen_port,
+		VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn",
+      SHOW_STR "Display information about the SGSN")
+{
+	/* FIXME: iterate over list of NS-VC's and display their state */
+	struct gprs_ns_inst *nsi = g_cfg->nsi;
+	struct gprs_nsvc *nsvc;
+
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		vty_out(vty, "NSEI %5u, NS-VC %5u, %s-mode, %s %s%s",
+			nsvc->nsei, nsvc->nsvci,
+			nsvc->remote_end_is_sgsn ? "BSS" : "SGSN",
+			nsvc->state & NSE_S_ALIVE ? "ALIVE" : "DEAD",
+			nsvc->state & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED",
+			VTY_NEWLINE);
+		if (nsvc->nsi->ll == GPRS_NS_LL_UDP)
+			vty_out(vty, "  remote peer %s:%u%s",
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				ntohs(nsvc->ip.bts_addr.sin_port), VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_sgsn,
+      cfg_sgsn_cmd,
+      "sgsn",
+      "Configure the SGSN")
+{
+	vty->node = SGSN_NODE;
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_nsip_local_ip,
+      cfg_nsip_local_ip_cmd,
+      "nsip local ip A.B.C.D",
+      "Set the IP address on which we listen for BSS connects")
+{
+	struct in_addr ia;
+
+	inet_aton(argv[0], &ia);
+	g_cfg->nsip_listen_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_local_port,
+      cfg_nsip_local_port_cmd,
+      "nsip local port <0-65534>",
+      "Set the UDP port on which we listen for BSS connects")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_listen_port = port;
+	return CMD_SUCCESS;
+}
+
+
+
+
+int sgsn_vty_init(void)
+{
+	install_element(VIEW_NODE, &show_sgsn_cmd);
+
+	install_element(CONFIG_NODE, &cfg_sgsn_cmd);
+	install_node(&sgsn_node, config_write_sgsn);
+	install_default(SGSN_NODE);
+	install_element(SGSN_NODE, &cfg_nsip_local_ip_cmd);
+	install_element(SGSN_NODE, &cfg_nsip_local_port_cmd);
+
+	return 0;
+}
+
+int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg)
+{
+	int rc;
+
+	g_cfg = cfg;
+	rc = vty_read_config_file(config_file);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/gsm_04_08.c b/openbsc/src/gsm_04_08.c
new file mode 100644
index 0000000..b0e5541
--- /dev/null
+++ b/openbsc/src/gsm_04_08.c
@@ -0,0 +1,2936 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <openbsc/db.h>
+#include <osmocore/msgb.h>
+#include <osmocore/bitvec.h>
+#include <osmocore/tlv.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/rtp_proxy.h>
+#include <osmocore/talloc.h>
+#include <osmocore/gsm48.h>
+#include <openbsc/transaction.h>
+#include <openbsc/ussd.h>
+#include <openbsc/silent_call.h>
+
+void *tall_locop_ctx;
+
+int gsm0408_loc_upd_acc(struct gsm_lchan *lchan, u_int32_t tmsi);
+static int gsm48_tx_simple(struct gsm_lchan *lchan,
+			   u_int8_t pdisc, u_int8_t msg_type);
+static void schedule_reject(struct gsm_subscriber_connection *conn);
+
+struct gsm_lai {
+	u_int16_t mcc;
+	u_int16_t mnc;
+	u_int16_t lac;
+};
+
+static u_int32_t new_callref = 0x80000001;
+
+static int authorize_subscriber(struct gsm_loc_updating_operation *loc,
+				struct gsm_subscriber *subscriber)
+{
+	if (!subscriber)
+		return 0;
+
+	/*
+	 * Do not send accept yet as more information should arrive. Some
+	 * phones will not send us the information and we will have to check
+	 * what we want to do with that.
+	 */
+	if (loc && (loc->waiting_for_imsi || loc->waiting_for_imei))
+		return 0;
+
+	switch (subscriber->net->auth_policy) {
+	case GSM_AUTH_POLICY_CLOSED:
+		return subscriber->authorized;
+	case GSM_AUTH_POLICY_TOKEN:
+		if (subscriber->authorized)
+			return subscriber->authorized;
+		return (subscriber->flags & GSM_SUBSCRIBER_FIRST_CONTACT);
+	case GSM_AUTH_POLICY_ACCEPT_ALL:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static void release_loc_updating_req(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->loc_operation)
+		return;
+
+	bsc_del_timer(&conn->loc_operation->updating_timer);
+	talloc_free(conn->loc_operation);
+	conn->loc_operation = 0;
+	put_subscr_con(conn);
+}
+
+static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn)
+{
+	use_subscr_con(conn)
+	release_loc_updating_req(conn);
+
+	conn->loc_operation = talloc_zero(tall_locop_ctx,
+					   struct gsm_loc_updating_operation);
+}
+
+static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	if (authorize_subscriber(conn->loc_operation, conn->subscr)) {
+		int rc;
+
+		db_subscriber_alloc_tmsi(conn->subscr);
+		release_loc_updating_req(conn);
+		rc = gsm0408_loc_upd_acc(msg->lchan, conn->subscr->tmsi);
+		if (msg->lchan->ts->trx->bts->network->send_mm_info) {
+			/* send MM INFO with network name */
+			rc = gsm48_tx_mm_info(msg->lchan);
+		}
+
+		/* call subscr_update after putting the loc_upd_acc
+		 * in the transmit queue, since S_SUBSCR_ATTACHED might
+		 * trigger further action like SMS delivery */
+		subscr_update(conn->subscr, msg->trx->bts,
+			      GSM_SUBSCRIBER_UPDATE_ATTACHED);
+
+		/* try to close channel ASAP */
+		lchan_auto_release(conn->lchan);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int gsm0408_handle_lchan_signal(unsigned int subsys, unsigned int signal,
+					void *handler_data, void *signal_data)
+{
+	struct gsm_trans *trans, *temp;
+
+	if (subsys != SS_LCHAN || signal != S_LCHAN_UNEXPECTED_RELEASE)
+		return 0;
+
+	/*
+	 * Cancel any outstanding location updating request
+	 * operation taking place on the lchan.
+	 */
+	struct gsm_lchan *lchan = (struct gsm_lchan *)signal_data;
+	if (!lchan)
+		return 0;
+
+	release_loc_updating_req(&lchan->conn);
+
+	/* Free all transactions that are associated with the released lchan */
+	/* FIXME: this is not neccessarily the right thing to do, we should
+	 * only set trans->lchan to NULL and wait for another lchan to be
+	 * established to the same MM entity (phone/subscriber) */
+	llist_for_each_entry_safe(trans, temp, &lchan->ts->trx->bts->network->trans_list, entry) {
+		if (trans->conn && trans->conn->lchan == lchan)
+			trans_free(trans);
+	}
+
+	return 0;
+}
+
+/* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */
+int gsm0408_loc_upd_rej(struct gsm_lchan *lchan, u_int8_t cause)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	
+	msg->lchan = lchan;
+	conn = &lchan->conn;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_LOC_UPD_REJECT;
+	gh->data[0] = cause;
+
+	LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT "
+	     "LAC=%u BTS=%u\n", conn->subscr ?
+	     			subscr_name(conn->subscr) : "unknown",
+	     lchan->ts->trx->bts->location_area_code, lchan->ts->trx->bts->nr);
+
+	counter_inc(bts->network->stats.loc_upd_resp.reject);
+	
+	return gsm48_sendmsg(msg, NULL);
+}
+
+/* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */
+int gsm0408_loc_upd_acc(struct gsm_lchan *lchan, u_int32_t tmsi)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_loc_area_id *lai;
+	u_int8_t *mid;
+	
+	msg->lchan = lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_LOC_UPD_ACCEPT;
+
+	lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai));
+	gsm48_generate_lai(lai, bts->network->country_code,
+		     bts->network->network_code, bts->location_area_code);
+
+	mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+	gsm48_generate_mid_from_tmsi(mid, tmsi);
+
+	DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n");
+
+	counter_inc(bts->network->stats.loc_upd_resp.accept);
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+/* Transmit Chapter 9.2.10 Identity Request */
+static int mm_tx_identity_req(struct gsm_lchan *lchan, u_int8_t id_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	msg->lchan = lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_ID_REQ;
+	gh->data[0] = id_type;
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+
+/* Parse Chapter 9.2.11 Identity Response */
+static int mm_rx_id_resp(struct msgb *msg)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm_lchan *lchan = msg->lchan;
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct gsm_network *net = bts->network;
+	u_int8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
+	DEBUGP(DMM, "IDENTITY RESPONSE: mi_type=0x%02x MI(%s)\n",
+		mi_type, mi_string);
+
+	conn = &lchan->conn;
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		/* look up subscriber based on IMSI, create if not found */
+		if (!conn->subscr) {
+			conn->subscr = subscr_get_by_imsi(net, mi_string);
+			if (!conn->subscr)
+				conn->subscr = db_create_subscriber(net, mi_string);
+		}
+		if (conn->loc_operation)
+			conn->loc_operation->waiting_for_imsi = 0;
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* update subscribe <-> IMEI mapping */
+		if (conn->subscr) {
+			db_subscriber_assoc_imei(conn->subscr, mi_string);
+			db_sync_equipment(&conn->subscr->equipment);
+		}
+		if (conn->loc_operation)
+			conn->loc_operation->waiting_for_imei = 0;
+		break;
+	}
+
+	/* Check if we can let the mobile station enter */
+	return gsm0408_authorize(conn, msg);
+}
+
+
+static void loc_upd_rej_cb(void *data)
+{
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm_lchan *lchan = conn->lchan;
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+
+	release_loc_updating_req(conn);
+	gsm0408_loc_upd_rej(lchan, bts->network->reject_cause);
+	lchan_auto_release(lchan);
+}
+
+static void schedule_reject(struct gsm_subscriber_connection *conn)
+{
+	conn->loc_operation->updating_timer.cb = loc_upd_rej_cb;
+	conn->loc_operation->updating_timer.data = conn;
+	bsc_schedule_timer(&conn->loc_operation->updating_timer, 5, 0);
+}
+
+static const char *lupd_name(u_int8_t type)
+{
+	switch (type) {
+	case GSM48_LUPD_NORMAL:
+		return "NORMAL";
+	case GSM48_LUPD_PERIODIC:
+		return "PEROIDOC";
+	case GSM48_LUPD_IMSI_ATT:
+		return "IMSI ATTACH";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+/* Chapter 9.2.15: Receive Location Updating Request */
+static int mm_rx_loc_upd_req(struct msgb *msg)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_loc_upd_req *lu;
+	struct gsm_subscriber *subscr = NULL;
+	struct gsm_lchan *lchan = msg->lchan;
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	int rc;
+
+ 	lu = (struct gsm48_loc_upd_req *) gh->data;
+	conn = &lchan->conn;
+
+	mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
+
+	DEBUGPC(DMM, "mi_type=0x%02x MI(%s) type=%s ", mi_type, mi_string,
+		lupd_name(lu->type));
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len);
+
+	switch (lu->type) {
+	case GSM48_LUPD_NORMAL:
+		counter_inc(bts->network->stats.loc_upd_type.normal);
+		break;
+	case GSM48_LUPD_IMSI_ATT:
+		counter_inc(bts->network->stats.loc_upd_type.attach);
+		break;
+	case GSM48_LUPD_PERIODIC:
+		counter_inc(bts->network->stats.loc_upd_type.periodic);
+		break;
+	}
+
+	/*
+	 * Pseudo Spoof detection: Just drop a second/concurrent
+	 * location updating request.
+	 */
+	if (conn->loc_operation) {
+		DEBUGPC(DMM, "ignoring request due an existing one: %p.\n",
+			conn->loc_operation);
+		gsm0408_loc_upd_rej(lchan, GSM48_REJECT_PROTOCOL_ERROR);
+		return 0;
+	}
+
+	allocate_loc_updating_req(&lchan->conn);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		DEBUGPC(DMM, "\n");
+		/* we always want the IMEI, too */
+		rc = mm_tx_identity_req(lchan, GSM_MI_TYPE_IMEI);
+		conn->loc_operation->waiting_for_imei = 1;
+
+		/* look up subscriber based on IMSI, create if not found */
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		if (!subscr) {
+			subscr = db_create_subscriber(bts->network, mi_string);
+		}
+		break;
+	case GSM_MI_TYPE_TMSI:
+		DEBUGPC(DMM, "\n");
+		/* we always want the IMEI, too */
+		rc = mm_tx_identity_req(lchan, GSM_MI_TYPE_IMEI);
+		conn->loc_operation->waiting_for_imei = 1;
+
+		/* look up the subscriber based on TMSI, request IMSI if it fails */
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		if (!subscr) {
+			/* send IDENTITY REQUEST message to get IMSI */
+			rc = mm_tx_identity_req(lchan, GSM_MI_TYPE_IMSI);
+			conn->loc_operation->waiting_for_imsi = 1;
+		}
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* no sim card... FIXME: what to do ? */
+		DEBUGPC(DMM, "unimplemented mobile identity type\n");
+		break;
+	default:	
+		DEBUGPC(DMM, "unknown mobile identity type\n");
+		break;
+	}
+
+	/* schedule the reject timer */
+	schedule_reject(conn);
+
+	if (!subscr) {
+		DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n");
+		/* FIXME: request id? close channel? */
+		return -EINVAL;
+	}
+
+	conn->subscr = subscr;
+	conn->subscr->equipment.classmark1 = lu->classmark1;
+
+	/* check if we can let the subscriber into our network immediately
+	 * or if we need to wait for identity responses. */
+	return gsm0408_authorize(conn, msg);
+}
+
+#if 0
+static u_int8_t to_bcd8(u_int8_t val)
+{
+       return ((val / 10) << 4) | (val % 10);
+}
+#endif
+
+/* Section 9.2.15a */
+int gsm48_tx_mm_info(struct gsm_lchan *lchan)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm_network *net = lchan->ts->trx->bts->network;
+	u_int8_t *ptr8;
+	int name_len, name_pad;
+#if 0
+	time_t cur_t;
+	struct tm* cur_time;
+	int tz15min;
+#endif
+
+	msg->lchan = lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_INFO;
+
+	if (net->name_long) {
+#if 0
+		name_len = strlen(net->name_long);
+		/* 10.5.3.5a */
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_LONG;
+		ptr8[1] = name_len*2 +1;
+		ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+		ptr16 = (u_int16_t *) msgb_put(msg, name_len*2);
+		for (i = 0; i < name_len; i++)
+			ptr16[i] = htons(net->name_long[i]);
+
+		/* FIXME: Use Cell Broadcast, not UCS-2, since
+		 * UCS-2 is only supported by later revisions of the spec */
+#endif
+		name_len = (strlen(net->name_long)*7)/8;
+		name_pad = (8 - strlen(net->name_long)*7)%8;
+		if (name_pad > 0)
+			name_len++;
+		/* 10.5.3.5a */
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_LONG;
+		ptr8[1] = name_len +1;
+		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+		ptr8 = msgb_put(msg, name_len);
+		gsm_7bit_encode(ptr8, net->name_long);
+
+	}
+
+	if (net->name_short) {
+#if 0
+		name_len = strlen(net->name_short);
+		/* 10.5.3.5a */
+		ptr8 = (u_int8_t *) msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_SHORT;
+		ptr8[1] = name_len*2 + 1;
+		ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+		ptr16 = (u_int16_t *) msgb_put(msg, name_len*2);
+		for (i = 0; i < name_len; i++)
+			ptr16[i] = htons(net->name_short[i]);
+#endif
+		name_len = (strlen(net->name_short)*7)/8;
+		name_pad = (8 - strlen(net->name_short)*7)%8;
+		if (name_pad > 0)
+			name_len++;
+		/* 10.5.3.5a */
+		ptr8 = (u_int8_t *) msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_SHORT;
+		ptr8[1] = name_len +1;
+		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+		ptr8 = msgb_put(msg, name_len);
+		gsm_7bit_encode(ptr8, net->name_short);
+
+	}
+
+#if 0
+	/* Section 10.5.3.9 */
+	cur_t = time(NULL);
+	cur_time = gmtime(&cur_t);
+	ptr8 = msgb_put(msg, 8);
+	ptr8[0] = GSM48_IE_NET_TIME_TZ;
+	ptr8[1] = to_bcd8(cur_time->tm_year % 100);
+	ptr8[2] = to_bcd8(cur_time->tm_mon);
+	ptr8[3] = to_bcd8(cur_time->tm_mday);
+	ptr8[4] = to_bcd8(cur_time->tm_hour);
+	ptr8[5] = to_bcd8(cur_time->tm_min);
+	ptr8[6] = to_bcd8(cur_time->tm_sec);
+	/* 02.42: coded as BCD encoded signed value in units of 15 minutes */
+	tz15min = (cur_time->tm_gmtoff)/(60*15);
+	ptr8[7] = to_bcd8(tz15min);
+	if (tz15min < 0)
+		ptr8[7] |= 0x80;
+#endif
+
+	DEBUGP(DMM, "-> MM INFO\n");
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+/* Section 9.2.2 */
+int gsm48_tx_mm_auth_req(struct gsm_lchan *lchan, u_int8_t *rand, int key_seq)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_auth_req *ar = (struct gsm48_auth_req *) msgb_put(msg, sizeof(*ar));
+
+	DEBUGP(DMM, "-> AUTH REQ (rand = %s)\n", hexdump(rand, 16));
+
+	msg->lchan = lchan;
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_AUTH_REQ;
+
+	ar->key_seq = key_seq;
+
+	/* 16 bytes RAND parameters */
+	if (rand)
+		memcpy(ar->rand, rand, 16);
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+/* Section 9.2.1 */
+int gsm48_tx_mm_auth_rej(struct gsm_lchan *lchan)
+{
+	DEBUGP(DMM, "-> AUTH REJECT\n");
+	return gsm48_tx_simple(lchan, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ);
+}
+
+static int gsm48_tx_mm_serv_ack(struct gsm_lchan *lchan)
+{
+	DEBUGP(DMM, "-> CM SERVICE ACK\n");
+	return gsm48_tx_simple(lchan, GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_ACC);
+}
+
+/* 9.2.6 CM service reject */
+static int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn,
+				enum gsm48_reject_value value)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+
+	msg->lchan = conn->lchan;
+	use_subscr_con(conn);
+
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_CM_SERV_REJ;
+	gh->data[0] = value;
+	DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value);
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+/*
+ * Handle CM Service Requests
+ * a) Verify that the packet is long enough to contain the information
+ *    we require otherwsie reject with INCORRECT_MESSAGE
+ * b) Try to parse the TMSI. If we do not have one reject
+ * c) Check that we know the subscriber with the TMSI otherwise reject
+ *    with a HLR cause
+ * d) Set the subscriber on the gsm_lchan and accept
+ */
+static int gsm48_rx_mm_serv_req(struct msgb *msg)
+{
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm_subscriber *subscr;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_service_request *req =
+			(struct gsm48_service_request *)gh->data;
+	/* unfortunately in Phase1 the classmar2 length is variable */
+	u_int8_t classmark2_len = gh->data[1];
+	u_int8_t *classmark2 = gh->data+2;
+	u_int8_t mi_len = *(classmark2 + classmark2_len);
+	u_int8_t *mi = (classmark2 + classmark2_len + 1);
+
+	DEBUGP(DMM, "<- CM SERVICE REQUEST ");
+	if (msg->data_len < sizeof(struct gsm48_service_request*)) {
+		DEBUGPC(DMM, "wrong sized message\n");
+		return gsm48_tx_mm_serv_rej(&msg->lchan->conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	if (msg->data_len < req->mi_len + 6) {
+		DEBUGPC(DMM, "does not fit in packet\n");
+		return gsm48_tx_mm_serv_rej(&msg->lchan->conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	mi_type = mi[0] & GSM_MI_TYPE_MASK;
+	if (mi_type != GSM_MI_TYPE_TMSI) {
+		DEBUGPC(DMM, "mi_type is not TMSI: %d\n", mi_type);
+		return gsm48_tx_mm_serv_rej(&msg->lchan->conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+	DEBUGPC(DMM, "serv_type=0x%02x mi_type=0x%02x M(%s)\n",
+		req->cm_service_type, mi_type, mi_string);
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len));
+
+	if (is_siemens_bts(bts))
+		send_siemens_mrpci(msg->lchan, classmark2-1);
+
+	subscr = subscr_get_by_tmsi(bts->network,
+				    tmsi_from_string(mi_string));
+
+	/* FIXME: if we don't know the TMSI, inquire abit IMSI and allocate new TMSI */
+	if (!subscr)
+		return gsm48_tx_mm_serv_rej(&msg->lchan->conn,
+					    GSM48_REJECT_IMSI_UNKNOWN_IN_HLR);
+
+	if (!msg->lchan->conn.subscr)
+		msg->lchan->conn.subscr = subscr;
+	else if (msg->lchan->conn.subscr == subscr)
+		subscr_put(subscr); /* lchan already has a ref, don't need another one */
+	else {
+		DEBUGP(DMM, "<- CM Channel already owned by someone else?\n");
+		subscr_put(subscr);
+	}
+
+	subscr->equipment.classmark2_len = classmark2_len;
+	memcpy(subscr->equipment.classmark2, classmark2, classmark2_len);
+	db_sync_equipment(&subscr->equipment);
+
+	return gsm48_tx_mm_serv_ack(msg->lchan);
+}
+
+static int gsm48_rx_mm_imsi_detach_ind(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_imsi_detach_ind *idi =
+				(struct gsm48_imsi_detach_ind *) gh->data;
+	u_int8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm_subscriber *subscr = NULL;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len);
+	DEBUGP(DMM, "IMSI DETACH INDICATION: mi_type=0x%02x MI(%s): ",
+		mi_type, mi_string);
+
+	counter_inc(bts->network->stats.loc_upd_type.detach);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* no sim card... FIXME: what to do ? */
+		DEBUGPC(DMM, "unimplemented mobile identity type\n");
+		break;
+	default:	
+		DEBUGPC(DMM, "unknown mobile identity type\n");
+		break;
+	}
+
+	if (subscr) {
+		subscr_update(subscr, msg->trx->bts,
+				GSM_SUBSCRIBER_UPDATE_DETACHED);
+		DEBUGP(DMM, "Subscriber: %s\n", subscr_name(subscr));
+
+		subscr->equipment.classmark1 = idi->classmark1;
+		db_sync_equipment(&subscr->equipment);
+
+		subscr_put(subscr);
+	} else
+		DEBUGP(DMM, "Unknown Subscriber ?!?\n");
+
+	/* FIXME: iterate over all transactions and release them,
+	 * imagine an IMSI DETACH happening during an active call! */
+
+	/* subscriber is detached: should we release lchan? */
+	lchan_auto_release(msg->lchan);
+
+	return 0;
+}
+
+static int gsm48_rx_mm_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DMM, "MM STATUS (reject cause 0x%02x)\n", gh->data[0]);
+
+	return 0;
+}
+
+/* Receive a GSM 04.08 Mobility Management (MM) message */
+static int gsm0408_rcv_mm(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (gh->msg_type & 0xbf) {
+	case GSM48_MT_MM_LOC_UPD_REQUEST:
+		DEBUGP(DMM, "LOCATION UPDATING REQUEST: ");
+		rc = mm_rx_loc_upd_req(msg);
+		break;
+	case GSM48_MT_MM_ID_RESP:
+		rc = mm_rx_id_resp(msg);
+		break;
+	case GSM48_MT_MM_CM_SERV_REQ:
+		rc = gsm48_rx_mm_serv_req(msg);
+		break;
+	case GSM48_MT_MM_STATUS:
+		rc = gsm48_rx_mm_status(msg);
+		break;
+	case GSM48_MT_MM_TMSI_REALL_COMPL:
+		DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
+		       msg->lchan->conn.subscr ?
+				subscr_name(msg->lchan->conn.subscr) :
+				"unknown subscriber");
+		break;
+	case GSM48_MT_MM_IMSI_DETACH_IND:
+		rc = gsm48_rx_mm_imsi_detach_ind(msg);
+		break;
+	case GSM48_MT_MM_CM_REEST_REQ:
+		DEBUGP(DMM, "CM REESTABLISH REQUEST: Not implemented\n");
+		break;
+	case GSM48_MT_MM_AUTH_RESP:
+		DEBUGP(DMM, "AUTHENTICATION RESPONSE: Not implemented\n");
+		break;
+	default:
+		LOGP(DMM, LOGL_NOTICE, "Unknown GSM 04.08 MM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+/* Receive a PAGING RESPONSE message from the MS */
+static int gsm48_rx_rr_pag_resp(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t *classmark2_lv = gh->data + 1;
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm_subscriber *subscr = NULL;
+	int rc = 0;
+
+	gsm48_paging_extract_mi(msg, mi_string, &mi_type);
+	DEBUGP(DRR, "PAGING RESPONSE: mi_type=0x%02x MI(%s)\n",
+		mi_type, mi_string);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		break;
+	}
+
+	if (!subscr) {
+		DEBUGP(DRR, "<- Can't find any subscriber for this ID\n");
+		/* FIXME: request id? close channel? */
+		return -EINVAL;
+	}
+	DEBUGP(DRR, "<- Channel was requested by %s\n",
+		subscr->name && strlen(subscr->name) ? subscr->name : subscr->imsi);
+
+	subscr->equipment.classmark2_len = *classmark2_lv;
+	memcpy(subscr->equipment.classmark2, classmark2_lv+1, *classmark2_lv);
+	db_sync_equipment(&subscr->equipment);
+
+	rc = gsm48_handle_paging_resp(msg, subscr);
+	return rc;
+}
+
+static int gsm48_rx_rr_classmark(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm_subscriber *subscr = msg->lchan->conn.subscr;
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	u_int8_t cm2_len, cm3_len = 0;
+	u_int8_t *cm2, *cm3 = NULL;
+
+	DEBUGP(DRR, "CLASSMARK CHANGE ");
+
+	/* classmark 2 */
+	cm2_len = gh->data[0];
+	cm2 = &gh->data[1];
+	DEBUGPC(DRR, "CM2(len=%u) ", cm2_len);
+
+	if (payload_len > cm2_len + 1) {
+		/* we must have a classmark3 */
+		if (gh->data[cm2_len+1] != 0x20) {
+			DEBUGPC(DRR, "ERR CM3 TAG\n");
+			return -EINVAL;
+		}
+		if (cm2_len > 3) {
+			DEBUGPC(DRR, "CM2 too long!\n");
+			return -EINVAL;
+		}
+		
+		cm3_len = gh->data[cm2_len+2];
+		cm3 = &gh->data[cm2_len+3];
+		if (cm3_len > 14) {
+			DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len);
+			return -EINVAL;
+		}
+		DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len);
+	}
+	if (subscr) {
+		subscr->equipment.classmark2_len = cm2_len;
+		memcpy(subscr->equipment.classmark2, cm2, cm2_len);
+		if (cm3) {
+			subscr->equipment.classmark3_len = cm3_len;
+			memcpy(subscr->equipment.classmark3, cm3, cm3_len);
+		}
+		db_sync_equipment(&subscr->equipment);
+	}
+
+	return 0;
+}
+
+static int gsm48_rx_rr_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "STATUS rr_cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	return 0;
+}
+
+static int gsm48_rx_rr_meas_rep(struct msgb *msg)
+{
+	struct gsm_meas_rep *meas_rep = lchan_next_meas_rep(msg->lchan);
+
+	/* This shouldn't actually end up here, as RSL treats
+	 * L3 Info of 08.58 MEASUREMENT REPORT different by calling
+	 * directly into gsm48_parse_meas_rep */
+	DEBUGP(DMEAS, "DIRECT GSM48 MEASUREMENT REPORT ?!? ");
+	gsm48_parse_meas_rep(meas_rep, msg);
+
+	return 0;
+}
+
+static int gsm48_rx_rr_app_info(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t apdu_id_flags;
+	u_int8_t apdu_len;
+	u_int8_t *apdu_data;
+
+	apdu_id_flags = gh->data[0];
+	apdu_len = gh->data[1];
+	apdu_data = gh->data+2;
+	
+	DEBUGP(DNM, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s",
+		apdu_id_flags, apdu_len, hexdump(apdu_data, apdu_len));
+
+	return db_apdu_blob_store(msg->lchan->conn.subscr, apdu_id_flags, apdu_len, apdu_data);
+}
+
+/* Chapter 9.1.16 Handover complete */
+static int gsm48_rx_rr_ho_compl(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "HANDOVER COMPLETE cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, msg->lchan);
+	/* FIXME: release old channel */
+
+	return 0;
+}
+
+/* Chapter 9.1.17 Handover Failure */
+static int gsm48_rx_rr_ho_fail(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "HANDOVER FAILED cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, msg->lchan);
+	/* FIXME: release allocated new channel */
+
+	return 0;
+}
+
+/* Receive a GSM 04.08 Radio Resource (RR) message */
+static int gsm0408_rcv_rr(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (gh->msg_type) {
+	case GSM48_MT_RR_CLSM_CHG:
+		rc = gsm48_rx_rr_classmark(msg);
+		break;
+	case GSM48_MT_RR_GPRS_SUSP_REQ:
+		DEBUGP(DRR, "GRPS SUSPEND REQUEST\n");
+		break;
+	case GSM48_MT_RR_PAG_RESP:
+		rc = gsm48_rx_rr_pag_resp(msg);
+		break;
+	case GSM48_MT_RR_CHAN_MODE_MODIF_ACK:
+		rc = gsm48_rx_rr_modif_ack(msg);
+		break;
+	case GSM48_MT_RR_STATUS:
+		rc = gsm48_rx_rr_status(msg);
+		break;
+	case GSM48_MT_RR_MEAS_REP:
+		rc = gsm48_rx_rr_meas_rep(msg);
+		break;
+	case GSM48_MT_RR_APP_INFO:
+		rc = gsm48_rx_rr_app_info(msg);
+		break;
+	case GSM48_MT_RR_CIPH_M_COMPL:
+		DEBUGP(DRR, "CIPHERING MODE COMPLETE\n");
+		/* FIXME: check for MI (if any) */
+		break;
+	case GSM48_MT_RR_HANDO_COMPL:
+		rc = gsm48_rx_rr_ho_compl(msg);
+		break;
+	case GSM48_MT_RR_HANDO_FAIL:
+		rc = gsm48_rx_rr_ho_fail(msg);
+		break;
+	default:
+		LOGP(DRR, LOGL_NOTICE, "Unimplemented "
+			"GSM 04.08 RR msg type 0x%02x\n", gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+int gsm48_send_rr_app_info(struct gsm_lchan *lchan, u_int8_t apdu_id,
+			   u_int8_t apdu_len, const u_int8_t *apdu)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	msg->lchan = lchan;
+	
+	DEBUGP(DRR, "TX APPLICATION INFO id=0x%02x, len=%u\n",
+		apdu_id, apdu_len);
+	
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2 + apdu_len);
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_APP_INFO;
+	gh->data[0] = apdu_id;
+	gh->data[1] = apdu_len;
+	memcpy(gh->data+2, apdu, apdu_len);
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+/* Call Control */
+
+/* The entire call control code is written in accordance with Figure 7.10c
+ * for 'very early assignment', i.e. we allocate a TCH/F during IMMEDIATE
+ * ASSIGN, then first use that TCH/F for signalling and later MODE MODIFY
+ * it for voice */
+
+static void new_cc_state(struct gsm_trans *trans, int state)
+{
+	if (state > 31 || state < 0)
+		return;
+
+	DEBUGP(DCC, "new state %s -> %s\n",
+		gsm48_cc_state_name(trans->cc.state),
+		gsm48_cc_state_name(state));
+
+	trans->cc.state = state;
+}
+
+static int gsm48_cc_tx_status(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	u_int8_t *cause, *call_state;
+
+	gh->msg_type = GSM48_MT_CC_STATUS;
+
+	cause = msgb_put(msg, 3);
+	cause[0] = 2;
+	cause[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_USER;
+	cause[2] = 0x80 | 30;	/* response to status inquiry */
+
+	call_state = msgb_put(msg, 1);
+	call_state[0] = 0xc0 | 0x00;
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_tx_simple(struct gsm_lchan *lchan,
+			   u_int8_t pdisc, u_int8_t msg_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	msg->lchan = lchan;
+
+	gh->proto_discr = pdisc;
+	gh->msg_type = msg_type;
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+static void gsm48_stop_cc_timer(struct gsm_trans *trans)
+{
+	if (bsc_timer_pending(&trans->cc.timer)) {
+		DEBUGP(DCC, "stopping pending timer T%x\n", trans->cc.Tcurrent);
+		bsc_del_timer(&trans->cc.timer);
+		trans->cc.Tcurrent = 0;
+	}
+}
+
+static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans,
+			int msg_type, struct gsm_mncc *mncc)
+{
+	struct msgb *msg;
+
+	if (trans)
+		if (trans->conn)
+			DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+				"Sending '%s' to MNCC.\n",
+				trans->conn->lchan->ts->trx->bts->nr,
+				trans->conn->lchan->ts->trx->nr,
+				trans->conn->lchan->ts->nr, trans->transaction_id,
+				(trans->subscr)?(trans->subscr->extension):"-",
+				get_mncc_name(msg_type));
+		else
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Sending '%s' to MNCC.\n",
+				(trans->subscr)?(trans->subscr->extension):"-",
+				get_mncc_name(msg_type));
+	else
+		DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) "
+			"Sending '%s' to MNCC.\n", get_mncc_name(msg_type));
+
+	mncc->msg_type = msg_type;
+	
+	msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC");
+	if (!msg)
+		return -ENOMEM;
+	memcpy(msg->data, mncc, sizeof(struct gsm_mncc));
+	msgb_enqueue(&net->upqueue, msg);
+
+	return 0;
+}
+
+int mncc_release_ind(struct gsm_network *net, struct gsm_trans *trans,
+		     u_int32_t callref, int location, int value)
+{
+	struct gsm_mncc rel;
+
+	memset(&rel, 0, sizeof(rel));
+	rel.callref = callref;
+	mncc_set_cause(&rel, location, value);
+	return mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+}
+
+/* Call Control Specific transaction release.
+ * gets called by trans_free, DO NOT CALL YOURSELF! */
+void _gsm48_cc_trans_free(struct gsm_trans *trans)
+{
+	gsm48_stop_cc_timer(trans);
+
+	/* send release to L4, if callref still exists */
+	if (trans->callref) {
+		/* Ressource unavailable */
+		mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				 GSM48_CAUSE_LOC_PRN_S_LU,
+				 GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+	}
+	if (trans->cc.state != GSM_CSTATE_NULL)
+		new_cc_state(trans, GSM_CSTATE_NULL);
+	if (trans->conn)
+		trau_mux_unmap(&trans->conn->lchan->ts->e1_link, trans->callref);
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg);
+
+/* call-back from paging the B-end of the connection */
+static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event,
+			      struct msgb *msg, void *_lchan, void *param)
+{
+	struct gsm_lchan *lchan = _lchan;
+	struct gsm_subscriber *subscr = param;
+	struct gsm_trans *transt, *tmp;
+	struct gsm_network *net;
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	if (!subscr)
+		return -EINVAL;
+	net = subscr->net;
+	if (!net) {
+		DEBUGP(DCC, "Error Network not set!\n");
+		return -EINVAL;
+	}
+
+	/* check all tranactions (without lchan) for subscriber */
+	llist_for_each_entry_safe(transt, tmp, &net->trans_list, entry) {
+		if (transt->subscr != subscr || transt->conn)
+			continue;
+		switch (event) {
+		case GSM_PAGING_SUCCEEDED:
+			if (!lchan) // paranoid
+				break;
+			DEBUGP(DCC, "Paging subscr %s succeeded!\n",
+				subscr->extension);
+			/* Assign lchan */
+			if (!transt->conn) {
+				transt->conn = &lchan->conn;
+				use_subscr_con(transt->conn);
+			}
+			/* send SETUP request to called party */
+			gsm48_cc_tx_setup(transt, &transt->cc.msg);
+			break;
+		case GSM_PAGING_EXPIRED:
+			DEBUGP(DCC, "Paging subscr %s expired!\n",
+				subscr->extension);
+			/* Temporarily out of order */
+			mncc_release_ind(transt->subscr->net, transt,
+					 transt->callref,
+					 GSM48_CAUSE_LOC_PRN_S_LU,
+					 GSM48_CC_CAUSE_DEST_OOO);
+			transt->callref = 0;
+			trans_free(transt);
+			break;
+		}
+	}
+	return 0;
+}
+
+static int tch_recv_mncc(struct gsm_network *net, u_int32_t callref, int enable);
+
+/* some other part of the code sends us a signal */
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+				 void *handler_data, void *signal_data)
+{
+	struct gsm_lchan *lchan = signal_data;
+	int rc;
+	struct gsm_network *net;
+	struct gsm_trans *trans;
+
+	if (subsys != SS_ABISIP)
+		return 0;
+
+	/* in case we use direct BTS-to-BTS RTP */
+	if (ipacc_rtp_direct)
+		return 0;
+
+	switch (signal) {
+	case S_ABISIP_CRCX_ACK:
+		/* check if any transactions on this lchan still have
+		 * a tch_recv_mncc request pending */
+		net = lchan->ts->trx->bts->network;
+		llist_for_each_entry(trans, &net->trans_list, entry) {
+			if (trans->conn && trans->conn->lchan == lchan && trans->tch_recv) {
+				DEBUGP(DCC, "pending tch_recv_mncc request\n");
+				tch_recv_mncc(net, trans->callref, 1);
+			}
+		}
+		break;
+	}
+
+	return 0;
+}
+
+/* map two ipaccess RTP streams onto each other */
+static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct gsm_bts *remote_bts = remote_lchan->ts->trx->bts;
+	int rc;
+
+	DEBUGP(DCC, "Setting up TCH map between (bts=%u,trx=%u,ts=%u) and (bts=%u,trx=%u,ts=%u)\n",
+		bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		remote_bts->nr, remote_lchan->ts->trx->nr, remote_lchan->ts->nr);
+
+	if (bts->type != remote_bts->type) {
+		DEBUGP(DCC, "Cannot switch calls between different BTS types yet\n");
+		return -EINVAL;
+	}
+
+	// todo: map between different bts types
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		if (!ipacc_rtp_direct) {
+			/* connect the TCH's to our RTP proxy */
+			rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+			if (rc < 0)
+				return rc;
+			rc = rsl_ipacc_mdcx_to_rtpsock(remote_lchan);
+#warning do we need a check of rc here?
+
+			/* connect them with each other */
+			rtp_socket_proxy(lchan->abis_ip.rtp_socket,
+					 remote_lchan->abis_ip.rtp_socket);
+		} else {
+			/* directly connect TCH RTP streams to each other */
+			rc = rsl_ipacc_mdcx(lchan, remote_lchan->abis_ip.bound_ip,
+						remote_lchan->abis_ip.bound_port,
+						remote_lchan->abis_ip.rtp_payload2);
+			if (rc < 0)
+				return rc;
+			rc = rsl_ipacc_mdcx(remote_lchan, lchan->abis_ip.bound_ip,
+						lchan->abis_ip.bound_port,
+						lchan->abis_ip.rtp_payload2);
+		}
+		break;
+	case GSM_BTS_TYPE_BS11:
+		trau_mux_map_lchan(lchan, remote_lchan);
+		break;
+	default:
+		DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* bridge channels of two transactions */
+static int tch_bridge(struct gsm_network *net, u_int32_t *refs)
+{
+	struct gsm_trans *trans1 = trans_find_by_callref(net, refs[0]);
+	struct gsm_trans *trans2 = trans_find_by_callref(net, refs[1]);
+
+	if (!trans1 || !trans2)
+		return -EIO;
+
+	if (!trans1->conn || !trans2->conn)
+		return -EIO;
+
+	/* through-connect channel */
+	return tch_map(trans1->conn->lchan, trans2->conn->lchan);
+}
+
+/* enable receive of channels to MNCC upqueue */
+static int tch_recv_mncc(struct gsm_network *net, u_int32_t callref, int enable)
+{
+	struct gsm_trans *trans;
+	struct gsm_lchan *lchan;
+	struct gsm_bts *bts;
+	int rc;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, callref);
+	if (!trans)
+		return -EIO;
+	if (!trans->conn)
+		return 0;
+	lchan = trans->conn->lchan;
+	bts = lchan->ts->trx->bts;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		if (ipacc_rtp_direct) {
+			DEBUGP(DCC, "Error: RTP proxy is disabled\n");
+			return -EINVAL;
+		}
+		/* in case, we don't have a RTP socket yet, we note this
+		 * in the transaction and try later */
+		if (!lchan->abis_ip.rtp_socket) {
+			trans->tch_recv = enable;
+			DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable);
+			return 0;
+		}
+		if (enable) {
+			/* connect the TCH's to our RTP proxy */
+			rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+			if (rc < 0)
+				return rc;
+			/* assign socket to application interface */
+			rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+				net, callref);
+		} else
+			rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+				net, 0);
+		break;
+	case GSM_BTS_TYPE_BS11:
+		if (enable)
+			return trau_recv_lchan(lchan, callref);
+		return trau_mux_unmap(NULL, callref);
+		break;
+	default:
+		DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
+{
+	DEBUGP(DCC, "-> STATUS ENQ\n");
+	return gsm48_cc_tx_status(trans, msg);
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
+
+static void gsm48_cc_timeout(void *arg)
+{
+	struct gsm_trans *trans = arg;
+	int disconnect = 0, release = 0;
+	int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER;
+	int mo_location = GSM48_CAUSE_LOC_USER;
+	int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+	int l4_location = GSM48_CAUSE_LOC_PRN_S_LU;
+	struct gsm_mncc mo_rel, l4_rel;
+
+	memset(&mo_rel, 0, sizeof(struct gsm_mncc));
+	mo_rel.callref = trans->callref;
+	memset(&l4_rel, 0, sizeof(struct gsm_mncc));
+	l4_rel.callref = trans->callref;
+
+	switch(trans->cc.Tcurrent) {
+	case 0x303:
+		release = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x310:
+		disconnect = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x313:
+		disconnect = 1;
+		/* unknown, did not find it in the specs */
+		break;
+	case 0x301:
+		disconnect = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x308:
+		if (!trans->cc.T308_second) {
+			/* restart T308 a second time */
+			gsm48_cc_tx_release(trans, &trans->cc.msg);
+			trans->cc.T308_second = 1;
+			break; /* stay in release state */
+		}
+		trans_free(trans);
+		return;
+//		release = 1;
+//		l4_cause = 14;
+//		break;
+	case 0x306:
+		release = 1;
+		mo_cause = trans->cc.msg.cause.value;
+		mo_location = trans->cc.msg.cause.location;
+		break;
+	case 0x323:
+		disconnect = 1;
+		break;
+	default:
+		release = 1;
+	}
+
+	if (release && trans->callref) {
+		/* process release towards layer 4 */
+		mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				 l4_location, l4_cause);
+		trans->callref = 0;
+	}
+
+	if (disconnect && trans->callref) {
+		/* process disconnect towards layer 4 */
+		mncc_set_cause(&l4_rel, l4_location, l4_cause);
+		mncc_recvmsg(trans->subscr->net, trans, MNCC_DISC_IND, &l4_rel);
+	}
+
+	/* process disconnect towards mobile station */
+	if (disconnect || release) {
+		mncc_set_cause(&mo_rel, mo_location, mo_cause);
+		mo_rel.cause.diag[0] = ((trans->cc.Tcurrent & 0xf00) >> 8) + '0';
+		mo_rel.cause.diag[1] = ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0';
+		mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0';
+		mo_rel.cause.diag_len = 3;
+
+		if (disconnect)
+			gsm48_cc_tx_disconnect(trans, &mo_rel);
+		if (release)
+			gsm48_cc_tx_release(trans, &mo_rel);
+	}
+
+}
+
+static void gsm48_start_cc_timer(struct gsm_trans *trans, int current,
+				 int sec, int micro)
+{
+	DEBUGP(DCC, "starting timer T%x with %d seconds\n", current, sec);
+	trans->cc.timer.cb = gsm48_cc_timeout;
+	trans->cc.timer.data = trans;
+	bsc_schedule_timer(&trans->cc.timer, sec, micro);
+	trans->cc.Tcurrent = current;
+}
+
+static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type & 0xbf;
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc setup;
+
+	memset(&setup, 0, sizeof(struct gsm_mncc));
+	setup.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* emergency setup is identified by msg_type */
+	if (msg_type == GSM48_MT_CC_EMERG_SETUP)
+		setup.emergency = 1;
+
+	/* use subscriber as calling party number */
+	if (trans->subscr) {
+		setup.fields |= MNCC_F_CALLING;
+		strncpy(setup.calling.number, trans->subscr->extension,
+			sizeof(setup.calling.number)-1);
+		strncpy(setup.imsi, trans->subscr->imsi,
+			sizeof(setup.imsi)-1);
+	}
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		setup.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&setup.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		setup.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&setup.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* called party bcd number */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+		setup.fields |= MNCC_F_CALLED;
+		gsm48_decode_called(&setup.called,
+			      TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		setup.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&setup.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		setup.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&setup.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+	/* CLIR suppression */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_SUPP))
+		setup.clir.sup = 1;
+	/* CLIR invocation */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_INVOC))
+		setup.clir.inv = 1;
+	/* cc cap */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+		setup.fields |= MNCC_F_CCCAP;
+		gsm48_decode_cccap(&setup.cccap,
+			     TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_INITIATED);
+
+	LOGP(DCC, LOGL_INFO, "Subscriber %s (%s) sends SETUP to %s\n",
+	     subscr_name(trans->subscr), trans->subscr->extension,
+	     setup.called.number);
+
+	/* indicate setup to MNCC */
+	mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_IND, &setup);
+
+	/* MNCC code will modify the channel asynchronously, we should
+	 * ipaccess-bind only after the modification has been made to the
+	 * lchan->tch_mode */
+	return 0;
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm_mncc *setup = arg;
+	int rc, trans_id;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	/* transaction id must not be assigned */
+	if (trans->transaction_id != 0xff) { /* unasssigned */
+		DEBUGP(DCC, "TX Setup with assigned transaction. "
+			"This is not allowed!\n");
+		/* Temporarily out of order */
+		rc = mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				      GSM48_CAUSE_LOC_PRN_S_LU,
+				      GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+	
+	/* Get free transaction_id */
+	trans_id = trans_assign_trans_id(trans->subscr, GSM48_PDISC_CC, 0);
+	if (trans_id < 0) {
+		/* no free transaction ID */
+		rc = mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				      GSM48_CAUSE_LOC_PRN_S_LU,
+				      GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+	trans->transaction_id = trans_id;
+
+	gh->msg_type = GSM48_MT_CC_SETUP;
+
+	gsm48_start_cc_timer(trans, 0x303, GSM48_T303);
+
+	/* bearer capability */
+	if (setup->fields & MNCC_F_BEARER_CAP)
+		gsm48_encode_bearer_cap(msg, 0, &setup->bearer_cap);
+	/* facility */
+	if (setup->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &setup->facility);
+	/* progress */
+	if (setup->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &setup->progress);
+	/* calling party BCD number */
+	if (setup->fields & MNCC_F_CALLING)
+		gsm48_encode_calling(msg, &setup->calling);
+	/* called party BCD number */
+	if (setup->fields & MNCC_F_CALLED)
+		gsm48_encode_called(msg, &setup->called);
+	/* user-user */
+	if (setup->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &setup->useruser);
+	/* redirecting party BCD number */
+	if (setup->fields & MNCC_F_REDIRECTING)
+		gsm48_encode_redirecting(msg, &setup->redirecting);
+	/* signal */
+	if (setup->fields & MNCC_F_SIGNAL)
+		gsm48_encode_signal(msg, setup->signal);
+	
+	new_cc_state(trans, GSM_CSTATE_CALL_PRESENT);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc call_conf;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x310, GSM48_T310);
+
+	memset(&call_conf, 0, sizeof(struct gsm_mncc));
+	call_conf.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+#if 0
+	/* repeat */
+	if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR))
+		call_conf.repeat = 1;
+	if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ))
+		call_conf.repeat = 2;
+#endif
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		call_conf.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&call_conf.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		call_conf.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&call_conf.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* cc cap */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+		call_conf.fields |= MNCC_F_CCCAP;
+		gsm48_decode_cccap(&call_conf.cccap,
+			     TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_CALL_CONF_IND,
+			    &call_conf);
+}
+
+static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *proceeding = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CALL_PROC;
+
+	new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC);
+
+	/* bearer capability */
+	if (proceeding->fields & MNCC_F_BEARER_CAP)
+		gsm48_encode_bearer_cap(msg, 0, &proceeding->bearer_cap);
+	/* facility */
+	if (proceeding->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &proceeding->facility);
+	/* progress */
+	if (proceeding->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &proceeding->progress);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc alerting;
+	
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x301, GSM48_T301);
+
+	memset(&alerting, 0, sizeof(struct gsm_mncc));
+	alerting.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		alerting.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&alerting.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+
+	/* progress */
+	if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+		alerting.fields |= MNCC_F_PROGRESS;
+		gsm48_decode_progress(&alerting.progress,
+				TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		alerting.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&alerting.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_ALERT_IND,
+			    &alerting);
+}
+
+static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *alerting = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_ALERTING;
+
+	/* facility */
+	if (alerting->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &alerting->facility);
+	/* progress */
+	if (alerting->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &alerting->progress);
+	/* user-user */
+	if (alerting->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &alerting->useruser);
+
+	new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED);
+	
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *progress = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_PROGRESS;
+
+	/* progress */
+	gsm48_encode_progress(msg, 1, &progress->progress);
+	/* user-user */
+	if (progress->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &progress->useruser);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *connect = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CONNECT;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x313, GSM48_T313);
+
+	/* facility */
+	if (connect->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &connect->facility);
+	/* progress */
+	if (connect->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &connect->progress);
+	/* connected number */
+	if (connect->fields & MNCC_F_CONNECTED)
+		gsm48_encode_connected(msg, &connect->connected);
+	/* user-user */
+	if (connect->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &connect->useruser);
+
+	new_cc_state(trans, GSM_CSTATE_CONNECT_IND);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc connect;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&connect, 0, sizeof(struct gsm_mncc));
+	connect.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* use subscriber as connected party number */
+	if (trans->subscr) {
+		connect.fields |= MNCC_F_CONNECTED;
+		strncpy(connect.connected.number, trans->subscr->extension,
+			sizeof(connect.connected.number)-1);
+		strncpy(connect.imsi, trans->subscr->imsi,
+			sizeof(connect.imsi)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		connect.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&connect.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		connect.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&connect.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		connect.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&connect.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_CNF, &connect);
+}
+
+
+static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc connect_ack;
+
+	gsm48_stop_cc_timer(trans);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+	
+	memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+	connect_ack.callref = trans->callref;
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_COMPL_IND,
+			    &connect_ack);
+}
+
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CONNECT_ACK;
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc disc;
+
+	gsm48_stop_cc_timer(trans);
+
+	new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ);
+
+	memset(&disc, 0, sizeof(struct gsm_mncc));
+	disc.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_CAUSE, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		disc.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&disc.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		disc.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&disc.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		disc.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&disc.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		disc.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&disc.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_DISC_IND, &disc);
+
+}
+
+static struct gsm_mncc_cause default_cause = {
+	.location	= GSM48_CAUSE_LOC_PRN_S_LU,
+	.coding		= 0,
+	.rec		= 0,
+	.rec_val	= 0,
+	.value		= GSM48_CC_CAUSE_NORMAL_UNSPEC,
+	.diag_len	= 0,
+	.diag		= { 0 },
+};
+
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *disc = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_DISCONNECT;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x306, GSM48_T306);
+
+	/* cause */
+	if (disc->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &disc->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	/* facility */
+	if (disc->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &disc->facility);
+	/* progress */
+	if (disc->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &disc->progress);
+	/* user-user */
+	if (disc->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &disc->useruser);
+
+	/* store disconnect cause for T306 expiry */
+	memcpy(&trans->cc.msg, disc, sizeof(struct gsm_mncc));
+
+	new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc rel;
+	int rc;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		rel.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&rel.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		rel.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&rel.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		rel.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&rel.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		rel.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&rel.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) {
+		/* release collision 5.4.5 */
+		rc = mncc_recvmsg(trans->subscr->net, trans, MNCC_REL_CNF, &rel);
+	} else {
+		rc = gsm48_tx_simple(msg->lchan,
+				     GSM48_PDISC_CC | (trans->transaction_id << 4),
+				     GSM48_MT_CC_RELEASE_COMPL);
+		rc = mncc_recvmsg(trans->subscr->net, trans, MNCC_REL_IND, &rel);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_NULL);
+
+	trans->callref = 0;
+	trans_free(trans);
+
+	return rc;
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *rel = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RELEASE;
+
+	trans->callref = 0;
+	
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x308, GSM48_T308);
+
+	/* cause */
+	if (rel->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 0, &rel->cause);
+	/* facility */
+	if (rel->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &rel->facility);
+	/* user-user */
+	if (rel->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+	trans->cc.T308_second = 0;
+	memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc));
+
+	if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
+		new_cc_state(trans, GSM_CSTATE_RELEASE_REQ);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc rel;
+	int rc = 0;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		rel.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&rel.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		rel.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&rel.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		rel.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&rel.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		rel.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&rel.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	if (trans->callref) {
+		switch (trans->cc.state) {
+		case GSM_CSTATE_CALL_PRESENT:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REJ_IND, &rel);
+			break;
+		case GSM_CSTATE_RELEASE_REQ:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REL_CNF, &rel);
+			/* FIXME: in case of multiple calls, we can't simply
+			 * hang up here ! */
+			lchan_auto_release(msg->lchan);
+			break;
+		default:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REL_IND, &rel);
+		}
+	}
+
+	trans->callref = 0;
+	trans_free(trans);
+
+	return rc;
+}
+
+static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *rel = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RELEASE_COMPL;
+
+	trans->callref = 0;
+	
+	gsm48_stop_cc_timer(trans);
+
+	/* cause */
+	if (rel->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 0, &rel->cause);
+	/* facility */
+	if (rel->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &rel->facility);
+	/* user-user */
+	if (rel->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+	trans_free(trans);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc fac;
+
+	memset(&fac, 0, sizeof(struct gsm_mncc));
+	fac.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_FACILITY, 0);
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		fac.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&fac.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		fac.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&fac.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_FACILITY_IND, &fac);
+}
+
+static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *fac = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_FACILITY;
+
+	/* facility */
+	gsm48_encode_facility(msg, 1, &fac->facility);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_hold(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc hold;
+
+	memset(&hold, 0, sizeof(struct gsm_mncc));
+	hold.callref = trans->callref;
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_HOLD_IND, &hold);
+}
+
+static int gsm48_cc_tx_hold_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_HOLD_ACK;
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *hold_rej = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_HOLD_REJ;
+
+	/* cause */
+	if (hold_rej->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &hold_rej->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_retrieve(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc retrieve;
+
+	memset(&retrieve, 0, sizeof(struct gsm_mncc));
+	retrieve.callref = trans->callref;
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_RETRIEVE_IND,
+			    &retrieve);
+}
+
+static int gsm48_cc_tx_retrieve_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RETR_ACK;
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *retrieve_rej = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RETR_REJ;
+
+	/* cause */
+	if (retrieve_rej->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &retrieve_rej->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_start_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc dtmf;
+
+	memset(&dtmf, 0, sizeof(struct gsm_mncc));
+	dtmf.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* keypad facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) {
+		dtmf.fields |= MNCC_F_KEYPAD;
+		gsm48_decode_keypad(&dtmf.keypad,
+			      TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_START_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_tx_start_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *dtmf = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_START_DTMF_ACK;
+
+	/* keypad */
+	if (dtmf->fields & MNCC_F_KEYPAD)
+		gsm48_encode_keypad(msg, dtmf->keypad);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *dtmf = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_START_DTMF_REJ;
+
+	/* cause */
+	if (dtmf->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &dtmf->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_STOP_DTMF_ACK;
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_stop_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc dtmf;
+
+	memset(&dtmf, 0, sizeof(struct gsm_mncc));
+	dtmf.callref = trans->callref;
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_STOP_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_IND, &modify);
+}
+
+static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY;
+
+	gsm48_start_cc_timer(trans, 0x323, GSM48_T323);
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+	new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_CNF, &modify);
+}
+
+static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY_COMPL;
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= GSM48_IE_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		modify.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&modify.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_REJ, &modify);
+}
+
+static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY_REJECT;
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+	/* cause */
+	gsm48_encode_cause(msg, 1, &modify->cause);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *notify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_NOTIFY;
+
+	/* notify */
+	gsm48_encode_notify(msg, notify->notify);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+//	struct tlv_parsed tp;
+	struct gsm_mncc notify;
+
+	memset(&notify, 0, sizeof(struct gsm_mncc));
+	notify.callref = trans->callref;
+//	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len);
+	if (payload_len >= 1)
+		gsm48_decode_notify(&notify.notify, gh->data);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_NOTIFY_IND, &notify);
+}
+
+static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *user = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_USER_INFO;
+
+	/* user-user */
+	if (user->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 1, &user->useruser);
+	/* more data */
+	if (user->more)
+		gsm48_encode_more(msg);
+
+	return gsm48_sendmsg(msg, trans);
+}
+
+static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc user;
+
+	memset(&user, 0, sizeof(struct gsm_mncc));
+	user.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_USER_USER, 0);
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		user.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&user.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* more data */
+	if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA))
+		user.more = 1;
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_USERINFO_IND, &user);
+}
+
+static int _gsm48_lchan_modify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *mode = arg;
+
+	return gsm48_lchan_modify(trans->conn->lchan, mode->lchan_mode);
+}
+
+static struct downstate {
+	u_int32_t	states;
+	int		type;
+	int		(*rout) (struct gsm_trans *trans, void *arg);
+} downstatelist[] = {
+	/* mobile originating call establishment */
+	{SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */
+	 MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc},
+	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */
+	 MNCC_ALERT_REQ, gsm48_cc_tx_alerting},
+	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */
+	 MNCC_SETUP_RSP, gsm48_cc_tx_connect},
+	{SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.4.2 */
+	 MNCC_PROGRESS_REQ, gsm48_cc_tx_progress},
+	/* mobile terminating call establishment */
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */
+	 MNCC_SETUP_REQ, gsm48_cc_tx_setup},
+	{SBIT(GSM_CSTATE_CONNECT_REQUEST),
+	 MNCC_SETUP_COMPL_REQ, gsm48_cc_tx_connect_ack},
+	 /* signalling during call */
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_NOTIFY_REQ, gsm48_cc_tx_notify},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ),
+	 MNCC_FACILITY_REQ, gsm48_cc_tx_facility},
+	{ALL_STATES,
+	 MNCC_START_DTMF_RSP, gsm48_cc_tx_start_dtmf_ack},
+	{ALL_STATES,
+	 MNCC_START_DTMF_REJ, gsm48_cc_tx_start_dtmf_rej},
+	{ALL_STATES,
+	 MNCC_STOP_DTMF_RSP, gsm48_cc_tx_stop_dtmf_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_HOLD_CNF, gsm48_cc_tx_hold_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_HOLD_REJ, gsm48_cc_tx_hold_rej},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_RETRIEVE_CNF, gsm48_cc_tx_retrieve_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_RETRIEVE_REJ, gsm48_cc_tx_retrieve_rej},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_MODIFY_REQ, gsm48_cc_tx_modify},
+	{SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+	 MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete},
+	{SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+	 MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo},
+	/* clearing */
+	{SBIT(GSM_CSTATE_INITIATED),
+	 MNCC_REJ_REQ, gsm48_cc_tx_release_compl},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) - SBIT(GSM_CSTATE_RELEASE_REQ) - SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.4 */
+	 MNCC_DISC_REQ, gsm48_cc_tx_disconnect},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+	 MNCC_REL_REQ, gsm48_cc_tx_release},
+	/* special */
+	{ALL_STATES,
+	 MNCC_LCHAN_MODIFY, _gsm48_lchan_modify},
+};
+
+#define DOWNSLLEN \
+	(sizeof(downstatelist) / sizeof(struct downstate))
+
+
+int mncc_send(struct gsm_network *net, int msg_type, void *arg)
+{
+	int i, rc = 0;
+	struct gsm_trans *trans = NULL, *transt;
+	struct gsm_lchan *lchan = NULL;
+	struct gsm_bts *bts = NULL;
+	struct gsm_mncc *data = arg, rel;
+
+	/* handle special messages */
+	switch(msg_type) {
+	case MNCC_BRIDGE:
+		return tch_bridge(net, arg);
+	case MNCC_FRAME_DROP:
+		return tch_recv_mncc(net, data->callref, 0);
+	case MNCC_FRAME_RECV:
+		return tch_recv_mncc(net, data->callref, 1);
+	case GSM_TCHF_FRAME:
+		/* Find callref */
+		trans = trans_find_by_callref(net, data->callref);
+		if (!trans)
+			return -EIO;
+		if (!trans->conn)
+			return 0;
+		if (trans->conn->lchan->type != GSM_LCHAN_TCH_F)
+			return 0;
+		bts = trans->conn->lchan->ts->trx->bts;
+		switch (bts->type) {
+		case GSM_BTS_TYPE_NANOBTS:
+			if (!trans->conn->lchan->abis_ip.rtp_socket)
+				return 0;
+			return rtp_send_frame(trans->conn->lchan->abis_ip.rtp_socket, arg);
+		case GSM_BTS_TYPE_BS11:
+			return trau_send_frame(trans->conn->lchan, arg);
+		default:
+			DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		}
+		return -EINVAL;
+	}
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = data->callref;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, data->callref);
+
+	/* Callref unknown */
+	if (!trans) {
+		struct gsm_subscriber *subscr;
+
+		if (msg_type != MNCC_SETUP_REQ) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"unknown callref %d\n", data->called.number,
+				get_mncc_name(msg_type), data->callref);
+			/* Invalid call reference */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_INVAL_TRANS_ID);
+		}
+		if (!data->called.number[0] && !data->imsi[0]) {
+			DEBUGP(DCC, "(bts - trx - ts - ti) "
+				"Received '%s' from MNCC with "
+				"no number or IMSI\n", get_mncc_name(msg_type));
+			/* Invalid number */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_INV_NR_FORMAT);
+		}
+		/* New transaction due to setup, find subscriber */
+		if (data->called.number[0])
+			subscr = subscr_get_by_extension(net,
+							data->called.number);
+		else
+			subscr = subscr_get_by_imsi(net, data->imsi);
+		/* If subscriber is not found */
+		if (!subscr) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"unknown subscriber %s\n", data->called.number,
+				get_mncc_name(msg_type), data->called.number);
+			/* Unknown subscriber */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_UNASSIGNED_NR);
+		}
+		/* If subscriber is not "attached" */
+		if (!subscr->lac) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"detached subscriber %s\n", data->called.number,
+				get_mncc_name(msg_type), data->called.number);
+			subscr_put(subscr);
+			/* Temporarily out of order */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_DEST_OOO);
+		}
+		/* Create transaction */
+		trans = trans_alloc(subscr, GSM48_PDISC_CC, 0xff, data->callref);
+		if (!trans) {
+			DEBUGP(DCC, "No memory for trans.\n");
+			subscr_put(subscr);
+			/* Ressource unavailable */
+			mncc_release_ind(net, NULL, data->callref,
+					 GSM48_CAUSE_LOC_PRN_S_LU,
+					 GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+			return -ENOMEM;
+		}
+		/* Find lchan */
+		lchan = lchan_for_subscr(subscr);
+
+		/* If subscriber has no lchan */
+		if (!lchan) {
+			/* find transaction with this subscriber already paging */
+			llist_for_each_entry(transt, &net->trans_list, entry) {
+				/* Transaction of our lchan? */
+				if (transt == trans ||
+				    transt->subscr != subscr)
+					continue;
+				DEBUGP(DCC, "(bts %d trx - ts - ti -- sub %s) "
+					"Received '%s' from MNCC with "
+					"unallocated channel, paging already "
+					"started.\n", bts->nr,
+					data->called.number,
+					get_mncc_name(msg_type));
+				subscr_put(subscr);
+				trans_free(trans);
+				return 0;
+			}
+			/* store setup informations until paging was successfull */
+			memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
+			/* Trigger paging */
+			paging_request(net, subscr, RSL_CHANNEED_TCH_F,
+					setup_trig_pag_evt, subscr);
+			subscr_put(subscr);
+			return 0;
+		}
+		/* Assign lchan */
+		trans->conn = &lchan->conn;
+		use_subscr_con(trans->conn);
+		subscr_put(subscr);
+	}
+
+	if (trans->conn)
+		lchan = trans->conn->lchan;
+
+	/* if paging did not respond yet */
+	if (!lchan) {
+		DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+			"Received '%s' from MNCC in paging state\n",
+			(trans->subscr)?(trans->subscr->extension):"-",
+			get_mncc_name(msg_type));
+		mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_NORM_CALL_CLEAR);
+		if (msg_type == MNCC_REL_REQ)
+			rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
+		else
+			rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+
+	DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x sub %s) "
+		"Received '%s' from MNCC in state %d (%s)\n",
+		lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		trans->transaction_id,
+		(trans->conn->subscr)?(trans->conn->subscr->extension):"-",
+		get_mncc_name(msg_type), trans->cc.state,
+		gsm48_cc_state_name(trans->cc.state));
+
+	/* Find function for current state and message */
+	for (i = 0; i < DOWNSLLEN; i++)
+		if ((msg_type == downstatelist[i].type)
+		 && ((1 << trans->cc.state) & downstatelist[i].states))
+			break;
+	if (i == DOWNSLLEN) {
+		DEBUGP(DCC, "Message unhandled at this state.\n");
+		return 0;
+	}
+
+	rc = downstatelist[i].rout(trans, arg);
+
+	return rc;
+}
+
+
+static struct datastate {
+	u_int32_t	states;
+	int		type;
+	int		(*rout) (struct gsm_trans *trans, struct msgb *msg);
+} datastatelist[] = {
+	/* mobile originating call establishment */
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+	 GSM48_MT_CC_SETUP, gsm48_cc_rx_setup},
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+	 GSM48_MT_CC_EMERG_SETUP, gsm48_cc_rx_setup},
+	{SBIT(GSM_CSTATE_CONNECT_IND), /* 5.2.1.2 */
+	 GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack},
+	/* mobile terminating call establishment */
+	{SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.2 */
+	 GSM48_MT_CC_CALL_CONF, gsm48_cc_rx_call_conf},
+	{SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* ???? | 5.2.2.3.2 */
+	 GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting},
+	{SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) | SBIT(GSM_CSTATE_CALL_RECEIVED), /* (5.2.2.6) | 5.2.2.6 | 5.2.2.6 */
+	 GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect},
+	 /* signalling during call */
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL),
+	 GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify},
+	{ALL_STATES,
+	 GSM48_MT_CC_START_DTMF, gsm48_cc_rx_start_dtmf},
+	{ALL_STATES,
+	 GSM48_MT_CC_STOP_DTMF, gsm48_cc_rx_stop_dtmf},
+	{ALL_STATES,
+	 GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_HOLD, gsm48_cc_rx_hold},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_RETR, gsm48_cc_rx_retrieve},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify},
+	{SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+	 GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete},
+	{SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+	 GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo},
+	/* clearing */
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+	 GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL), /* 5.4.4.1.2.2 */
+	 GSM48_MT_CC_RELEASE, gsm48_cc_rx_release},
+	{ALL_STATES, /* 5.4.3.4 */
+	 GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl},
+};
+
+#define DATASLLEN \
+	(sizeof(datastatelist) / sizeof(struct datastate))
+
+static int gsm0408_rcv_cc(struct msgb *msg)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type & 0xbf;
+	u_int8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; /* flip */
+	struct gsm_lchan *lchan = msg->lchan;
+	struct gsm_trans *trans = NULL;
+	int i, rc = 0;
+
+	if (msg_type & 0x80) {
+		DEBUGP(DCC, "MSG 0x%2x not defined for PD error\n", msg_type);
+		return -EINVAL;
+	}
+
+	conn = &lchan->conn;
+
+	/* Find transaction */
+	trans = trans_find_by_id(conn->subscr, GSM48_PDISC_CC, transaction_id);
+
+	DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+		"Received '%s' from MS in state %d (%s)\n",
+		lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		transaction_id, (conn->subscr)?(conn->subscr->extension):"-",
+		gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0,
+		gsm48_cc_state_name(trans?(trans->cc.state):0));
+
+	/* Create transaction */
+	if (!trans) {
+		DEBUGP(DCC, "Unknown transaction ID %x, "
+			"creating new trans.\n", transaction_id);
+		/* Create transaction */
+		trans = trans_alloc(conn->subscr, GSM48_PDISC_CC,
+				    transaction_id, new_callref++);
+		if (!trans) {
+			DEBUGP(DCC, "No memory for trans.\n");
+			rc = gsm48_tx_simple(msg->lchan,
+					     GSM48_PDISC_CC | (transaction_id << 4),
+					     GSM48_MT_CC_RELEASE_COMPL);
+			return -ENOMEM;
+		}
+		/* Assign transaction */
+		trans->conn = &lchan->conn;
+		use_subscr_con(trans->conn);
+	}
+
+	/* find function for current state and message */
+	for (i = 0; i < DATASLLEN; i++)
+		if ((msg_type == datastatelist[i].type)
+		 && ((1 << trans->cc.state) & datastatelist[i].states))
+			break;
+	if (i == DATASLLEN) {
+		DEBUGP(DCC, "Message unhandled at this state.\n");
+		return 0;
+	}
+
+	rc = datastatelist[i].rout(trans, msg);
+
+	return rc;
+}
+
+/* here we pass in a msgb from the RSL->RLL.  We expect the l3 pointer to be set */
+int gsm0408_rcvmsg(struct msgb *msg, u_int8_t link_id)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t pdisc = gh->proto_discr & 0x0f;
+	int rc = 0;
+
+	if (silent_call_reroute(msg))
+		return silent_call_rx(msg);
+	
+	switch (pdisc) {
+	case GSM48_PDISC_CC:
+		rc = gsm0408_rcv_cc(msg);
+		break;
+	case GSM48_PDISC_MM:
+		rc = gsm0408_rcv_mm(msg);
+		break;
+	case GSM48_PDISC_RR:
+		rc = gsm0408_rcv_rr(msg);
+		break;
+	case GSM48_PDISC_SMS:
+		rc = gsm0411_rcv_sms(msg, link_id);
+		break;
+	case GSM48_PDISC_MM_GPRS:
+	case GSM48_PDISC_SM_GPRS:
+		LOGP(DRLL, LOGL_NOTICE, "Unimplemented "
+			"GSM 04.08 discriminator 0x%02x\n", pdisc);
+		break;
+	case GSM48_PDISC_NC_SS:
+		rc = handle_rcv_ussd(msg);
+		break;
+	default:
+		LOGP(DRLL, LOGL_NOTICE, "Unknown "
+			"GSM 04.08 discriminator 0x%02x\n", pdisc);
+		break;
+	}
+
+	return rc;
+}
+
+/* dequeue messages to layer 4 */
+int bsc_upqueue(struct gsm_network *net)
+{
+	struct gsm_mncc *mncc;
+	struct msgb *msg;
+	int work = 0;
+
+	if (net)
+		while ((msg = msgb_dequeue(&net->upqueue))) {
+			mncc = (struct gsm_mncc *)msg->data;
+			if (net->mncc_recv)
+				net->mncc_recv(net, mncc->msg_type, mncc);
+			work = 1; /* work done */
+			talloc_free(msg);
+		}
+
+	return work;
+}
+
+/*
+ * This will be ran by the linker when loading the DSO. We use it to
+ * do system initialization, e.g. registration of signal handlers.
+ */
+static __attribute__((constructor)) void on_dso_load_0408(void)
+{
+	register_signal_handler(SS_LCHAN, gsm0408_handle_lchan_signal, NULL);
+	register_signal_handler(SS_ABISIP, handle_abisip_signal, NULL);
+}
diff --git a/openbsc/src/gsm_04_08_utils.c b/openbsc/src/gsm_04_08_utils.c
new file mode 100644
index 0000000..0ee06ca
--- /dev/null
+++ b/openbsc/src/gsm_04_08_utils.c
@@ -0,0 +1,575 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0
+ * utility functions
+ */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/gsm48.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+
+/* should ip.access BTS use direct RTP streams between each other (1),
+ * or should OpenBSC always act as RTP relay/proxy in between (0) ? */
+int ipacc_rtp_direct = 1;
+
+int gsm48_sendmsg(struct msgb *msg, struct gsm_trans *trans)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data;
+
+	/* if we get passed a transaction reference, do some common
+	 * work that the caller no longer has to do */
+	if (trans) {
+		gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
+		msg->lchan = trans->conn->lchan;
+	}
+
+	if (msg->lchan) {
+		msg->trx = msg->lchan->ts->trx;
+
+		if ((gh->proto_discr & GSM48_PDISC_MASK) == GSM48_PDISC_CC)
+			DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x) "
+				"Sending '%s' to MS.\n", msg->trx->bts->nr,
+				msg->trx->nr, msg->lchan->ts->nr,
+				gh->proto_discr & 0xf0,
+				gsm48_cc_msg_name(gh->msg_type));
+		else
+			DEBUGP(DCC, "(bts %d trx %d ts %d pd %02x) "
+				"Sending 0x%02x to MS.\n", msg->trx->bts->nr,
+				msg->trx->nr, msg->lchan->ts->nr,
+				gh->proto_discr, gh->msg_type);
+	}
+
+	msg->l3h = msg->data;
+
+	return rsl_data_request(msg, 0);
+}
+
+/* Section 9.1.8 / Table 9.9 */
+struct chreq {
+	u_int8_t val;
+	u_int8_t mask;
+	enum chreq_type type;
+};
+
+/* If SYSTEM INFORMATION TYPE 4 NECI bit == 1 */
+static const struct chreq chreq_type_neci1[] = {
+	{ 0xa0, 0xe0, CHREQ_T_EMERG_CALL },
+	{ 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_F },
+	{ 0x68, 0xfc, CHREQ_T_CALL_REEST_TCH_H },
+	{ 0x6c, 0xfc, CHREQ_T_CALL_REEST_TCH_H_DBL },
+	{ 0xe0, 0xe0, CHREQ_T_SDCCH },
+	{ 0x40, 0xf0, CHREQ_T_VOICE_CALL_TCH_H },
+	{ 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H },
+	{ 0x00, 0xf0, CHREQ_T_LOCATION_UPD },
+	{ 0x10, 0xf0, CHREQ_T_SDCCH },
+	{ 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI1 },
+	{ 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F },
+	{ 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH },
+	{ 0x67, 0xff, CHREQ_T_LMU },
+	{ 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH },
+	{ 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH },
+	{ 0x63,	0xff, CHREQ_T_RESERVED_SDCCH },
+	{ 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE },
+};
+
+/* If SYSTEM INFORMATION TYPE 4 NECI bit == 0 */
+static const struct chreq chreq_type_neci0[] = {
+	{ 0xa0, 0xe0, CHREQ_T_EMERG_CALL },
+	{ 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_H },
+	{ 0xe0, 0xe0, CHREQ_T_TCH_F },
+	{ 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H },
+	{ 0x00, 0xe0, CHREQ_T_LOCATION_UPD },
+	{ 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI0 },
+	{ 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F },
+	{ 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH },
+	{ 0x67, 0xff, CHREQ_T_LMU },
+	{ 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH },
+	{ 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH },
+	{ 0x63,	0xff, CHREQ_T_RESERVED_SDCCH },
+	{ 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE },
+};
+
+static const enum gsm_chan_t ctype_by_chreq[] = {
+	[CHREQ_T_EMERG_CALL]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_CALL_REEST_TCH_F]	= GSM_LCHAN_TCH_F,
+	[CHREQ_T_CALL_REEST_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_CALL_REEST_TCH_H_DBL]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_SDCCH]			= GSM_LCHAN_SDCCH,
+	[CHREQ_T_TCH_F]			= GSM_LCHAN_TCH_F,
+	[CHREQ_T_VOICE_CALL_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_DATA_CALL_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_LOCATION_UPD]		= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_ANY_NECI1]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_ANY_NECI0]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_TCH_F]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_PAG_R_TCH_FH]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_LMU]			= GSM_LCHAN_SDCCH,
+	[CHREQ_T_RESERVED_SDCCH]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_RESERVED_IGNORE]	= GSM_LCHAN_UNKNOWN,
+};
+
+static const enum gsm_chreq_reason_t reason_by_chreq[] = {
+	[CHREQ_T_EMERG_CALL]		= GSM_CHREQ_REASON_EMERG,
+	[CHREQ_T_CALL_REEST_TCH_F]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_CALL_REEST_TCH_H]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_CALL_REEST_TCH_H_DBL]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_SDCCH]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_TCH_F]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_VOICE_CALL_TCH_H]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_DATA_CALL_TCH_H]	= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_LOCATION_UPD]		= GSM_CHREQ_REASON_LOCATION_UPD,
+	[CHREQ_T_PAG_R_ANY_NECI1]	= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_ANY_NECI0]	= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_TCH_F]		= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_TCH_FH]		= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_LMU]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_RESERVED_SDCCH]	= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_RESERVED_IGNORE]	= GSM_CHREQ_REASON_OTHER,
+};
+
+enum gsm_chan_t get_ctype_by_chreq(struct gsm_bts *bts, u_int8_t ra, int neci)
+{
+	int i;
+	int length;
+	const struct chreq *chreq;
+
+	if (neci) {
+		chreq = chreq_type_neci1;
+		length = ARRAY_SIZE(chreq_type_neci1);
+	} else {
+		chreq = chreq_type_neci0;
+		length = ARRAY_SIZE(chreq_type_neci0);
+	}
+
+
+	for (i = 0; i < length; i++) {
+		const struct chreq *chr = &chreq[i];
+		if ((ra & chr->mask) == chr->val)
+			return ctype_by_chreq[chr->type];
+	}
+	LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra);
+	return GSM_LCHAN_SDCCH;
+}
+
+enum gsm_chreq_reason_t get_reason_by_chreq(struct gsm_bts *bts, u_int8_t ra, int neci)
+{
+	int i;
+	int length;
+	const struct chreq *chreq;
+
+	if (neci) {
+		chreq = chreq_type_neci1;
+		length = ARRAY_SIZE(chreq_type_neci1);
+	} else {
+		chreq = chreq_type_neci0;
+		length = ARRAY_SIZE(chreq_type_neci0);
+	}
+
+	for (i = 0; i < length; i++) {
+		const struct chreq *chr = &chreq[i];
+		if ((ra & chr->mask) == chr->val)
+			return reason_by_chreq[chr->type];
+	}
+	LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST REASON 0x%02x\n", ra);
+	return GSM_CHREQ_REASON_OTHER;
+}
+
+/* 7.1.7 and 9.1.7: RR CHANnel RELease */
+int gsm48_send_rr_release(struct gsm_lchan *lchan)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	u_int8_t *cause;
+
+	msg->lchan = lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CHAN_REL;
+
+	cause = msgb_put(msg, 1);
+	cause[0] = GSM48_RR_CAUSE_NORMAL;
+
+	DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n",
+		lchan->nr, lchan->type);
+
+	/* Send actual release request to MS */
+	gsm48_sendmsg(msg, NULL);
+	/* FIXME: Start Timer T3109 */
+
+	/* Deactivate the SACCH on the BTS side */
+	return rsl_deact_sacch(lchan);
+}
+
+int send_siemens_mrpci(struct gsm_lchan *lchan,
+		       u_int8_t *classmark2_lv)
+{
+	struct rsl_mrpci mrpci;
+
+	if (classmark2_lv[0] < 2)
+		return -EINVAL;
+
+	mrpci.power_class = classmark2_lv[1] & 0x7;
+	mrpci.vgcs_capable = classmark2_lv[2] & (1 << 1);
+	mrpci.vbs_capable = classmark2_lv[2] & (1 <<2);
+	mrpci.gsm_phase = (classmark2_lv[1]) >> 5 & 0x3;
+
+	return rsl_siemens_mrpci(lchan, &mrpci);
+}
+
+int gsm48_paging_extract_mi(struct msgb *msg, char *mi_string, u_int8_t *mi_type)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t *classmark2_lv = gh->data + 1;
+	u_int8_t *mi_lv = gh->data + 2 + *classmark2_lv;
+	*mi_type = mi_lv[1] & GSM_MI_TYPE_MASK;
+
+	return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv);
+}
+
+int gsm48_handle_paging_resp(struct msgb *msg, struct gsm_subscriber *subscr)
+{
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t *classmark2_lv = gh->data + 1;
+	struct paging_signal_data sig_data;
+
+	if (is_siemens_bts(bts))
+		send_siemens_mrpci(msg->lchan, classmark2_lv);
+
+	if (!msg->lchan->conn.subscr) {
+		msg->lchan->conn.subscr = subscr;
+	} else if (msg->lchan->conn.subscr != subscr) {
+		LOGP(DRR, LOGL_ERROR, "<- Channel already owned by someone else?\n");
+		subscr_put(subscr);
+		return -EINVAL;
+	} else {
+		DEBUGP(DRR, "<- Channel already owned by us\n");
+		subscr_put(subscr);
+		subscr = msg->lchan->conn.subscr;
+	}
+
+	sig_data.subscr = subscr;
+	sig_data.bts	= msg->lchan->ts->trx->bts;
+	sig_data.lchan	= msg->lchan;
+
+	bts->network->stats.paging.completed++;
+
+	dispatch_signal(SS_PAGING, S_PAGING_SUCCEEDED, &sig_data);
+
+	/* Stop paging on the bts we received the paging response */
+	paging_request_stop(msg->trx->bts, subscr, msg->lchan);
+	return 0;
+}
+
+/* Chapter 9.1.9: Ciphering Mode Command */
+int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	u_int8_t ciph_mod_set;
+
+	msg->lchan = lchan;
+
+	DEBUGP(DRR, "TX CIPHERING MODE CMD\n");
+
+	if (lchan->encr.alg_id <= RSL_ENC_ALG_A5(0))
+		ciph_mod_set = 0;
+	else
+		ciph_mod_set = (lchan->encr.alg_id-2)<<1 | 1;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CIPH_M_CMD;
+	gh->data[0] = (want_imeisv & 0x1) << 4 | (ciph_mod_set & 0xf);
+
+	return rsl_encryption_cmd(msg);
+}
+
+static void gsm48_cell_desc(struct gsm48_cell_desc *cd,
+			    const struct gsm_bts *bts)
+{
+	cd->ncc = (bts->bsic >> 3 & 0x7);
+	cd->bcc = (bts->bsic & 0x7);
+	cd->arfcn_hi = bts->c0->arfcn >> 8;
+	cd->arfcn_lo = bts->c0->arfcn & 0xff;
+}
+
+static void gsm48_chan_desc(struct gsm48_chan_desc *cd,
+			    const struct gsm_lchan *lchan)
+{
+	u_int16_t arfcn = lchan->ts->trx->arfcn & 0x3ff;
+
+	cd->chan_nr = lchan2chan_nr(lchan);
+	cd->h0.tsc = lchan->ts->trx->bts->tsc;
+	cd->h0.h = 0;
+	cd->h0.arfcn_high = arfcn >> 8;
+	cd->h0.arfcn_low = arfcn & 0xff;
+}
+
+/* Chapter 9.1.15: Handover Command */
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
+		      u_int8_t power_command, u_int8_t ho_ref)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_ho_cmd *ho =
+		(struct gsm48_ho_cmd *) msgb_put(msg, sizeof(*ho));
+
+	msg->lchan = old_lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_HANDO_CMD;
+
+	/* mandatory bits */
+	gsm48_cell_desc(&ho->cell_desc, new_lchan->ts->trx->bts);
+	gsm48_chan_desc(&ho->chan_desc, new_lchan);
+	ho->ho_ref = ho_ref;
+	ho->power_command = power_command;
+
+	/* FIXME: optional bits for type of synchronization? */
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+/* Chapter 9.1.2: Assignment Command */
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, u_int8_t power_command)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_ass_cmd *ass =
+		(struct gsm48_ass_cmd *) msgb_put(msg, sizeof(*ass));
+
+	DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode);
+
+	msg->lchan = dest_lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_ASS_CMD;
+
+	/*
+	 * fill the channel information element, this code
+	 * should probably be shared with rsl_rx_chan_rqd(),
+	 * gsm48_tx_chan_mode_modify. But beware that 10.5.2.5
+	 * 10.5.2.5.a have slightly different semantic for
+	 * the chan_desc. But as long as multi-slot configurations
+	 * are not used we seem to be fine.
+	 */
+	gsm48_chan_desc(&ass->chan_desc, lchan);
+	ass->power_command = power_command;
+
+	msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode);
+
+	/* in case of multi rate we need to attach a config */
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		if (lchan->mr_conf.ver == 0) {
+			LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec "
+				"without multirate config.\n");
+		} else {
+			u_int8_t *data = msgb_put(msg, 4);
+			data[0] = GSM48_IE_MUL_RATE_CFG;
+			data[1] = 0x2;
+			memcpy(&data[2], &lchan->mr_conf, 2);
+		}
+	}
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+/* 9.1.5 Channel mode modify: Modify the mode on the MS side */
+int gsm48_tx_chan_mode_modify(struct gsm_lchan *lchan, u_int8_t mode)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_chan_mode_modify *cmm =
+		(struct gsm48_chan_mode_modify *) msgb_put(msg, sizeof(*cmm));
+	u_int16_t arfcn = lchan->ts->trx->arfcn & 0x3ff;
+
+	DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", mode);
+
+	lchan->tch_mode = mode;
+	msg->lchan = lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF;
+
+	/* fill the channel information element, this code
+	 * should probably be shared with rsl_rx_chan_rqd() */
+	cmm->chan_desc.chan_nr = lchan2chan_nr(lchan);
+	cmm->chan_desc.h0.tsc = lchan->ts->trx->bts->tsc;
+	cmm->chan_desc.h0.h = 0;
+	cmm->chan_desc.h0.arfcn_high = arfcn >> 8;
+	cmm->chan_desc.h0.arfcn_low = arfcn & 0xff;
+	cmm->mode = mode;
+
+	/* in case of multi rate we need to attach a config */
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		if (lchan->mr_conf.ver == 0) {
+			LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec "
+				"without multirate config.\n");
+		} else {
+			u_int8_t *data = msgb_put(msg, 4);
+			data[0] = GSM48_IE_MUL_RATE_CFG;
+			data[1] = 0x2;
+			memcpy(&data[2], &lchan->mr_conf, 2);
+		}
+	}
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+int gsm48_lchan_modify(struct gsm_lchan *lchan, u_int8_t lchan_mode)
+{
+	int rc;
+
+	rc = gsm48_tx_chan_mode_modify(lchan, lchan_mode);
+	if (rc < 0)
+		return rc;
+
+	return rc;
+}
+
+int gsm48_rx_rr_modif_ack(struct msgb *msg)
+{
+	int rc;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_chan_mode_modify *mod =
+				(struct gsm48_chan_mode_modify *) gh->data;
+
+	DEBUGP(DRR, "CHANNEL MODE MODIFY ACK\n");
+
+	if (mod->mode != msg->lchan->tch_mode) {
+		LOGP(DRR, LOGL_ERROR, "CHANNEL MODE change failed. Wanted: %d Got: %d\n",
+			msg->lchan->tch_mode, mod->mode);
+		return -1;
+	}
+
+	/* update the channel type */
+	switch (mod->mode) {
+	case GSM48_CMODE_SIGN:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
+		break;
+	case GSM48_CMODE_SPEECH_V1:
+	case GSM48_CMODE_SPEECH_EFR:
+	case GSM48_CMODE_SPEECH_AMR:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+		break;
+	case GSM48_CMODE_DATA_14k5:
+	case GSM48_CMODE_DATA_12k0:
+	case GSM48_CMODE_DATA_6k0:
+	case GSM48_CMODE_DATA_3k6:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_DATA;
+		break;
+	}
+
+	/* We've successfully modified the MS side of the channel,
+	 * now go on to modify the BTS side of the channel */
+	rc = rsl_chan_mode_modify_req(msg->lchan);
+
+	/* FIXME: we not only need to do this after mode modify, but
+	 * also after channel activation */
+	if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN)
+		rsl_ipacc_crcx(msg->lchan);
+	return rc;
+}
+
+int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	u_int8_t *data = gh->data;
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct bitvec *nbv = &bts->si_common.neigh_list;
+	struct gsm_meas_rep_cell *mrc;
+
+	if (gh->msg_type != GSM48_MT_RR_MEAS_REP)
+		return -EINVAL;
+
+	if (data[0] & 0x80)
+		rep->flags |= MEAS_REP_F_BA1;
+	if (data[0] & 0x40)
+		rep->flags |= MEAS_REP_F_UL_DTX;
+	if ((data[1] & 0x40) == 0x00)
+		rep->flags |= MEAS_REP_F_DL_VALID;
+
+	rep->dl.full.rx_lev = data[0] & 0x3f;
+	rep->dl.sub.rx_lev = data[1] & 0x3f;
+	rep->dl.full.rx_qual = (data[3] >> 4) & 0x7;
+	rep->dl.sub.rx_qual = (data[3] >> 1) & 0x7;
+
+	rep->num_cell = ((data[3] >> 6) & 0x3) | ((data[2] & 0x01) << 2);
+	if (rep->num_cell < 1 || rep->num_cell > 6)
+		return 0;
+
+	/* an encoding nightmare in perfection */
+	mrc = &rep->cell[0];
+	mrc->rxlev = data[3] & 0x3f;
+	mrc->neigh_idx = data[4] >> 3;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5);
+	if (rep->num_cell < 2)
+		return 0;
+
+	mrc = &rep->cell[1];
+	mrc->rxlev = ((data[5] & 0x1f) << 1) | (data[6] >> 7);
+	mrc->neigh_idx = (data[6] >> 2) & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4);
+	if (rep->num_cell < 3)
+		return 0;
+
+	mrc = &rep->cell[2];
+	mrc->rxlev = ((data[7] & 0x0f) << 2) | (data[8] >> 6);
+	mrc->neigh_idx = (data[8] >> 1) & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3);
+	if (rep->num_cell < 4)
+		return 0;
+
+	mrc = &rep->cell[3];
+	mrc->rxlev = ((data[9] & 0x07) << 3) | (data[10] >> 5);
+	mrc->neigh_idx = data[10] & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = data[11] >> 2;
+	if (rep->num_cell < 5)
+		return 0;
+
+	mrc = &rep->cell[4];
+	mrc->rxlev = ((data[11] & 0x03) << 4) | (data[12] >> 4);
+	mrc->neigh_idx = ((data[12] & 0xf) << 1) | (data[13] >> 7);
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = (data[13] >> 1) & 0x3f;
+	if (rep->num_cell < 6)
+		return 0;
+
+	mrc = &rep->cell[5];
+	mrc->rxlev = ((data[13] & 0x01) << 5) | (data[14] >> 3);
+	mrc->neigh_idx = ((data[14] & 0x07) << 2) | (data[15] >> 6);
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = data[15] & 0x3f;
+
+	return 0;
+}
diff --git a/openbsc/src/gsm_04_11.c b/openbsc/src/gsm_04_11.c
new file mode 100644
index 0000000..3492f1f
--- /dev/null
+++ b/openbsc/src/gsm_04_11.c
@@ -0,0 +1,1238 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/chan_alloc.h>
+
+#define GSM411_ALLOC_SIZE	1024
+#define GSM411_ALLOC_HEADROOM	128
+
+#define UM_SAPI_SMS 3	/* See GSM 04.05/04.06 */
+
+void *tall_gsms_ctx;
+static u_int32_t new_callref = 0x40000001;
+
+static const struct value_string cp_cause_strs[] = {
+	{ GSM411_CP_CAUSE_NET_FAIL,	"Network Failure" },
+	{ GSM411_CP_CAUSE_CONGESTION,	"Congestion" },
+	{ GSM411_CP_CAUSE_INV_TRANS_ID,	"Invalid Transaction ID" },
+	{ GSM411_CP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
+	{ GSM411_CP_CAUSE_INV_MAND_INF,	"Invalid Mandatory Information" },
+	{ GSM411_CP_CAUSE_MSGTYPE_NOTEXIST, "Message Type doesn't exist" },
+	{ GSM411_CP_CAUSE_MSG_INCOMP_STATE,
+				"Message incompatible with protocol state" },
+	{ GSM411_CP_CAUSE_IE_NOTEXIST,	"IE does not exist" },
+	{ GSM411_CP_CAUSE_PROTOCOL_ERR,	"Protocol Error" },
+	{ 0, 0 }
+};
+
+static const struct value_string rp_cause_strs[] = {
+	{ GSM411_RP_CAUSE_MO_NUM_UNASSIGNED, "(MO) Number not assigned" },
+	{ GSM411_RP_CAUSE_MO_OP_DET_BARR, "(MO) Operator determined barring" },
+	{ GSM411_RP_CAUSE_MO_CALL_BARRED, "(MO) Call barred" },
+	{ GSM411_RP_CAUSE_MO_SMS_REJECTED, "(MO) SMS rejected" },
+	{ GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER, "(MO) Destination out of order" },
+	{ GSM411_RP_CAUSE_MO_UNIDENTIFIED_SUBSCR, "(MO) Unidentified subscriber" },
+	{ GSM411_RP_CAUSE_MO_FACILITY_REJ, "(MO) Facility reject" },
+	{ GSM411_RP_CAUSE_MO_UNKNOWN_SUBSCR, "(MO) Unknown subscriber" },
+	{ GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER, "(MO) Network out of order" },
+	{ GSM411_RP_CAUSE_MO_TEMP_FAIL, "(MO) Temporary failure" },
+	{ GSM411_RP_CAUSE_MO_CONGESTION, "(MO) Congestion" },
+	{ GSM411_RP_CAUSE_MO_RES_UNAVAIL, "(MO) Resource unavailable" },
+	{ GSM411_RP_CAUSE_MO_REQ_FAC_NOTSUBSCR, "(MO) Requested facility not subscribed" },
+	{ GSM411_RP_CAUSE_MO_REQ_FAC_NOTIMPL, "(MO) Requested facility not implemented" },
+	{ GSM411_RP_CAUSE_MO_INTERWORKING, "(MO) Interworking" },
+	/* valid only for MT */
+	{ GSM411_RP_CAUSE_MT_MEM_EXCEEDED, "(MT) Memory Exceeded" },
+	/* valid for both directions */
+	{ GSM411_RP_CAUSE_INV_TRANS_REF, "Invalid Transaction Reference" },
+	{ GSM411_RP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
+	{ GSM411_RP_CAUSE_INV_MAND_INF, "Invalid Mandatory Information" },
+	{ GSM411_RP_CAUSE_MSGTYPE_NOTEXIST, "Message Type non-existant" },
+	{ GSM411_RP_CAUSE_MSG_INCOMP_STATE, "Message incompatible with protocol state" },
+	{ GSM411_RP_CAUSE_IE_NOTEXIST, "Information Element not existing" },
+	{ GSM411_RP_CAUSE_PROTOCOL_ERR, "Protocol Error" },
+	{ 0, NULL }
+};
+
+struct gsm_sms *sms_alloc(void)
+{
+	return talloc_zero(tall_gsms_ctx, struct gsm_sms);
+}
+
+void sms_free(struct gsm_sms *sms)
+{
+	/* drop references to subscriber structure */
+	if (sms->sender)
+		subscr_put(sms->sender);
+	if (sms->receiver)
+		subscr_put(sms->receiver);
+
+	talloc_free(sms);
+}
+
+struct msgb *gsm411_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(GSM411_ALLOC_SIZE, GSM411_ALLOC_HEADROOM,
+				   "GSM 04.11");
+}
+
+static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg, u_int8_t link_id)
+{
+	DEBUGP(DSMS, "GSM4.11 TX %s\n", hexdump(msg->data, msg->len));
+	msg->l3h = msg->data;
+	return gsm0808_submit_dtap(conn, msg, link_id);
+}
+
+/* SMC TC1* is expired */
+static void cp_timer_expired(void *data)
+{
+	struct gsm_trans *trans = data;
+
+	DEBUGP(DSMS, "SMC Timer TC1* is expired, calling trans_free()\n");
+	/* FIXME: we need to re-transmit the last CP-DATA 1..3 times */
+	trans_free(trans);
+}
+
+/* Prefix msg with a 04.08/04.11 CP header */
+static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
+			     u_int8_t msg_type)
+{
+	struct gsm48_hdr *gh;
+
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	/* Outgoing needs the highest bit set */
+	gh->proto_discr = trans->protocol | (trans->transaction_id<<4);
+	gh->msg_type = msg_type;
+
+	/* mobile originating */
+	switch (gh->msg_type) {
+	case GSM411_MT_CP_DATA:
+		/* 5.2.3.1.2: enter MO-wait for CP-ack */
+		/* 5.2.3.2.3: enter MT-wait for CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_WAIT_CP_ACK;
+		trans->sms.cp_timer.data = trans;
+		trans->sms.cp_timer.cb = cp_timer_expired;
+		/* 5.3.2.1: Set Timer TC1A */
+		bsc_schedule_timer(&trans->sms.cp_timer, GSM411_TMR_TC1A);
+		DEBUGP(DSMS, "TX: CP-DATA ");
+		break;
+	case GSM411_MT_CP_ACK:
+		DEBUGP(DSMS, "TX: CP-ACK ");
+		break;
+	case GSM411_MT_CP_ERROR:
+		DEBUGP(DSMS, "TX: CP-ERROR ");
+		break;
+	}
+
+	DEBUGPC(DSMS, "trans=%x\n", trans->transaction_id);
+
+	return gsm411_sendmsg(trans->conn, msg, trans->sms.link_id);
+}
+
+/* Prefix msg with a RP-DATA header and send as CP-DATA */
+static int gsm411_rp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
+			     u_int8_t rp_msg_type, u_int8_t rp_msg_ref)
+{
+	struct gsm411_rp_hdr *rp;
+	u_int8_t len = msg->len;
+
+	/* GSM 04.11 RP-DATA header */
+	rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp));
+	rp->len = len + 2;
+	rp->msg_type = rp_msg_type;
+	rp->msg_ref = rp_msg_ref; /* FIXME: Choose randomly */
+
+	return gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_DATA);
+}
+
+/* Turn int into semi-octet representation: 98 => 0x89 */
+static u_int8_t bcdify(u_int8_t value)
+{
+	u_int8_t ret;
+
+	ret = value / 10;
+	ret |= (value % 10) << 4;
+
+	return ret;
+}
+
+/* Turn semi-octet representation into int: 0x89 => 98 */
+static u_int8_t unbcdify(u_int8_t value)
+{
+	u_int8_t ret;
+
+	if ((value & 0x0F) > 9 || (value >> 4) > 9)
+		LOGP(DSMS, LOGL_ERROR,
+		     "unbcdify got too big nibble: 0x%02X\n", value);
+
+	ret = (value&0x0F)*10;
+	ret += value>>4;
+
+	return ret;
+}
+
+/* Generate 03.40 TP-SCTS */
+static void gsm340_gen_scts(u_int8_t *scts, time_t time)
+{
+	struct tm *tm = localtime(&time);
+
+	*scts++ = bcdify(tm->tm_year % 100);
+	*scts++ = bcdify(tm->tm_mon + 1);
+	*scts++ = bcdify(tm->tm_mday);
+	*scts++ = bcdify(tm->tm_hour);
+	*scts++ = bcdify(tm->tm_min);
+	*scts++ = bcdify(tm->tm_sec);
+	*scts++ = bcdify(tm->tm_gmtoff/(60*15));
+}
+
+/* Decode 03.40 TP-SCTS (into utc/gmt timestamp) */
+static time_t gsm340_scts(u_int8_t *scts)
+{
+	struct tm tm;
+
+	u_int8_t yr = unbcdify(*scts++);
+
+	if (yr <= 80)
+		tm.tm_year = 100 + yr;
+	else
+		tm.tm_year = yr;
+	tm.tm_mon  = unbcdify(*scts++) - 1;
+	tm.tm_mday = unbcdify(*scts++);
+	tm.tm_hour = unbcdify(*scts++);
+	tm.tm_min  = unbcdify(*scts++);
+	tm.tm_sec  = unbcdify(*scts++);
+	/* according to gsm 03.40 time zone is
+	   "expressed in quarters of an hour" */
+	tm.tm_gmtoff = unbcdify(*scts++) * 15*60;
+
+	return mktime(&tm);
+}
+
+/* Return the default validity period in minutes */
+static unsigned long gsm340_vp_default(void)
+{
+	unsigned long minutes;
+	/* Default validity: two days */
+	minutes = 24 * 60 * 2;
+	return minutes;
+}
+
+/* Decode validity period format 'relative' */
+static unsigned long gsm340_vp_relative(u_int8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.1 */
+	u_int8_t vp;
+	unsigned long minutes;
+
+	vp = *(sms_vp);
+	if (vp <= 143)
+		minutes = vp + 1 * 5;
+	else if (vp <= 167)
+		minutes = 12*60 + (vp-143) * 30;
+	else if (vp <= 196)
+		minutes = vp-166 * 60 * 24;
+	else
+		minutes = vp-192 * 60 * 24 * 7;
+	return minutes;
+}
+
+/* Decode validity period format 'absolute' */
+static unsigned long gsm340_vp_absolute(u_int8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.2 */
+	time_t expires, now;
+	unsigned long minutes;
+
+	expires = gsm340_scts(sms_vp);
+	now = mktime(gmtime(NULL));
+	if (expires <= now)
+		minutes = 0;
+	else
+		minutes = (expires-now)/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in integer representation' */
+static unsigned long gsm340_vp_relative_integer(u_int8_t *sms_vp)
+{
+	u_int8_t vp;
+	unsigned long minutes;
+	vp = *(sms_vp);
+	if (vp == 0) {
+		LOGP(DSMS, LOGL_ERROR,
+		     "reserved relative_integer validity period\n");
+		return gsm340_vp_default();
+	}
+	minutes = vp/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in semi-octet representation' */
+static unsigned long gsm340_vp_relative_semioctet(u_int8_t *sms_vp)
+{
+	unsigned long minutes;
+	minutes = unbcdify(*sms_vp++)*60;  /* hours */
+	minutes += unbcdify(*sms_vp++);    /* minutes */
+	minutes += unbcdify(*sms_vp++)/60; /* seconds */
+	return minutes;
+}
+
+/* decode validity period. return minutes */
+static unsigned long gsm340_validity_period(u_int8_t sms_vpf, u_int8_t *sms_vp)
+{
+	u_int8_t fi; /* functionality indicator */
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		return gsm340_vp_relative(sms_vp);
+	case GSM340_TP_VPF_ABSOLUTE:
+		return gsm340_vp_absolute(sms_vp);
+	case GSM340_TP_VPF_ENHANCED:
+		/* Chapter 9.2.3.12.3 */
+		fi = *sms_vp++;
+		/* ignore additional fi */
+		if (fi & (1<<7)) sms_vp++;
+		/* read validity period format */
+		switch (fi & 0b111) {
+		case 0b000:
+			return gsm340_vp_default(); /* no vpf specified */
+		case 0b001:
+			return gsm340_vp_relative(sms_vp);
+		case 0b010:
+			return gsm340_vp_relative_integer(sms_vp);
+		case 0b011:
+			return gsm340_vp_relative_semioctet(sms_vp);
+		default:
+			/* The GSM spec says that the SC should reject any
+			   unsupported and/or undefined values. FIXME */
+			LOGP(DSMS, LOGL_ERROR,
+			     "Reserved enhanced validity period format\n");
+			return gsm340_vp_default();
+		}
+	case GSM340_TP_VPF_NONE:
+	default:
+		return gsm340_vp_default();
+	}
+}
+
+/* determine coding alphabet dependent on GSM 03.38 Section 4 DCS */
+enum sms_alphabet gsm338_get_sms_alphabet(u_int8_t dcs)
+{
+	u_int8_t cgbits = dcs >> 4;
+	enum sms_alphabet alpha = DCS_NONE;
+
+	if ((cgbits & 0xc) == 0) {
+		if (cgbits & 2)
+			LOGP(DSMS, LOGL_NOTICE,
+			     "Compressed SMS not supported yet\n");
+
+		switch ((dcs >> 2)&0x03) {
+		case 0:
+			alpha = DCS_7BIT_DEFAULT;
+			break;
+		case 1:
+			alpha = DCS_8BIT_DATA;
+			break;
+		case 2:
+			alpha = DCS_UCS2;
+			break;
+		}
+	} else if (cgbits == 0xc || cgbits == 0xd)
+		alpha = DCS_7BIT_DEFAULT;
+	else if (cgbits == 0xe)
+		alpha = DCS_UCS2;
+	else if (cgbits == 0xf) {
+		if (dcs & 4)
+			alpha = DCS_8BIT_DATA;
+		else
+			alpha = DCS_7BIT_DEFAULT;
+	}
+
+	return alpha;
+}
+
+static int gsm340_rx_sms_submit(struct msgb *msg, struct gsm_sms *gsms)
+{
+	if (db_sms_store(gsms) != 0) {
+		LOGP(DSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+	/* dispatch a signal to tell higher level about it */
+	dispatch_signal(SS_SMS, S_SMS_SUBMITTED, gsms);
+	/* try delivering the SMS right now */
+	//gsm411_send_sms_subscr(gsms->receiver, gsms);
+
+	return 0;
+}
+
+/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
+static int gsm340_gen_oa(u_int8_t *oa, unsigned int oa_len,
+			 struct gsm_subscriber *subscr)
+{
+	int len_in_bytes;
+
+	oa[1] = 0xb9; /* networks-specific number, private numbering plan */
+
+	len_in_bytes = gsm48_encode_bcd_number(oa, oa_len, 1, subscr->extension);
+
+	/* GSM 03.40 tells us the length is in 'useful semi-octets' */
+	oa[0] = strlen(subscr->extension) & 0xff;
+
+	return len_in_bytes;
+}
+
+/* generate a msgb containing a TPDU derived from struct gsm_sms,
+ * returns total size of TPDU */
+static int gsm340_gen_tpdu(struct msgb *msg, struct gsm_sms *sms)
+{
+	u_int8_t *smsp;
+	u_int8_t oa[12];	/* max len per 03.40 */
+	u_int8_t oa_len = 0;
+	u_int8_t octet_len;
+	unsigned int old_msg_len = msg->len;
+
+	/* generate first octet with masked bits */
+	smsp = msgb_put(msg, 1);
+	/* TP-MTI (message type indicator) */
+	*smsp = GSM340_SMS_DELIVER_SC2MS;
+	/* TP-MMS (more messages to send) */
+	if (0 /* FIXME */)
+		*smsp |= 0x04;
+	/* TP-SRI(deliver)/SRR(submit) */
+	if (sms->status_rep_req)
+		*smsp |= 0x20;
+	/* TP-UDHI (indicating TP-UD contains a header) */
+	if (sms->ud_hdr_ind)
+		*smsp |= 0x40;
+#if 0
+	/* TP-RP (indicating that a reply path exists) */
+	if (sms->
+		*smsp |= 0x80;
+#endif
+	
+	/* generate originator address */
+	oa_len = gsm340_gen_oa(oa, sizeof(oa), sms->sender);
+	smsp = msgb_put(msg, oa_len);
+	memcpy(smsp, oa, oa_len);
+
+	/* generate TP-PID */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->protocol_id;
+
+	/* generate TP-DCS */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->data_coding_scheme;
+
+	/* generate TP-SCTS */
+	smsp = msgb_put(msg, 7);
+	gsm340_gen_scts(smsp, time(NULL));
+
+	/* generate TP-UDL */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->user_data_len;
+
+	/* generate TP-UD */
+	switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) {
+	case DCS_7BIT_DEFAULT:
+		octet_len = sms->user_data_len*7/8;
+		if (sms->user_data_len*7%8 != 0)
+			octet_len++;
+		/* Warning, user_data_len indicates the amount of septets
+		 * (characters), we need amount of octets occupied */
+		smsp = msgb_put(msg, octet_len);
+		memcpy(smsp, sms->user_data, octet_len);
+		break;
+	case DCS_UCS2:
+	case DCS_8BIT_DATA:
+		smsp = msgb_put(msg, sms->user_data_len);
+		memcpy(smsp, sms->user_data, sms->user_data_len);
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: 0x%02X\n",
+		     sms->data_coding_scheme);
+		break;
+	}
+
+	return msg->len - old_msg_len;
+}
+
+/* process an incoming TPDU (called from RP-DATA)
+ * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */
+static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	u_int8_t *smsp = msgb_sms(msg);
+	struct gsm_sms *gsms;
+	u_int8_t sms_mti, sms_mms, sms_vpf, sms_alphabet, sms_rp;
+	u_int8_t *sms_vp;
+	u_int8_t da_len_bytes;
+	u_int8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */
+	int rc = 0;
+
+	counter_inc(conn->bts->network->stats.sms.submitted);
+
+	gsms = sms_alloc();
+	if (!gsms)
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+
+	/* invert those fields where 0 means active/present */
+	sms_mti = *smsp & 0x03;
+	sms_mms = !!(*smsp & 0x04);
+	sms_vpf = (*smsp & 0x18) >> 3;
+	gsms->status_rep_req = (*smsp & 0x20);
+	gsms->ud_hdr_ind = (*smsp & 0x40);
+	sms_rp  = (*smsp & 0x80);
+
+	smsp++;
+	gsms->msg_ref = *smsp++;
+
+	/* length in bytes of the destination address */
+	da_len_bytes = 2 + *smsp/2 + *smsp%2;
+	if (da_len_bytes > 12) {
+		LOGP(DSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n");
+		rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
+		goto out;
+	}
+	memset(address_lv, 0, sizeof(address_lv));
+	memcpy(address_lv, smsp, da_len_bytes);
+	/* mangle first byte to reflect length in bytes, not digits */
+	address_lv[0] = da_len_bytes - 1;
+	/* convert to real number */
+	gsm48_decode_bcd_number(gsms->dest_addr, sizeof(gsms->dest_addr), address_lv, 1);
+	smsp += da_len_bytes;
+
+	gsms->protocol_id = *smsp++;
+	gsms->data_coding_scheme = *smsp++;
+
+	sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme);
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		sms_vp = smsp++;
+		break;
+	case GSM340_TP_VPF_ABSOLUTE:
+	case GSM340_TP_VPF_ENHANCED:
+		sms_vp = smsp;
+		/* the additional functionality indicator... */
+		if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7))
+			smsp++;
+		smsp += 7;
+		break;
+	case GSM340_TP_VPF_NONE:
+		sms_vp = 0;
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE,
+		     "SMS Validity period not implemented: 0x%02x\n", sms_vpf);
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+	gsms->user_data_len = *smsp++;
+	if (gsms->user_data_len) {
+		memcpy(gsms->user_data, smsp, gsms->user_data_len);
+
+		switch (sms_alphabet) {
+		case DCS_7BIT_DEFAULT:
+			gsm_7bit_decode(gsms->text, smsp, gsms->user_data_len);
+			break;
+		case DCS_8BIT_DATA:
+		case DCS_UCS2:
+		case DCS_NONE:
+			break;
+		}
+	}
+
+	gsms->sender = subscr_get(msg->lchan->conn.subscr);
+
+	LOGP(DSMS, LOGL_INFO, "RX SMS: Sender: %s, MTI: 0x%02x, VPF: 0x%02x, "
+	     "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, "
+	     "UserDataLength: 0x%02x, UserData: \"%s\"\n",
+	     subscr_name(gsms->sender), sms_mti, sms_vpf, gsms->msg_ref,
+	     gsms->protocol_id, gsms->data_coding_scheme, gsms->dest_addr,
+	     gsms->user_data_len,
+			sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text :
+				hexdump(gsms->user_data, gsms->user_data_len));
+
+	gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp);
+
+	dispatch_signal(SS_SMS, 0, gsms);
+
+	/* determine gsms->receiver based on dialled number */
+	gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dest_addr);
+	if (!gsms->receiver) {
+		rc = 1; /* cause 1: unknown subscriber */
+		counter_inc(conn->bts->network->stats.sms.no_receiver);
+		goto out;
+	}
+
+	switch (sms_mti) {
+	case GSM340_SMS_SUBMIT_MS2SC:
+		/* MS is submitting a SMS */
+		rc = gsm340_rx_sms_submit(msg, gsms);
+		break;
+	case GSM340_SMS_COMMAND_MS2SC:
+	case GSM340_SMS_DELIVER_REP_MS2SC:
+		LOGP(DSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti);
+		rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti);
+		rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+		break;
+	}
+
+	if (!rc && !gsms->receiver)
+		rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+
+out:
+	sms_free(gsms);
+
+	return rc;
+}
+
+static int gsm411_send_rp_ack(struct gsm_trans *trans, u_int8_t msg_ref)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	DEBUGP(DSMS, "TX: SMS RP ACK\n");
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_ACK_MT, msg_ref);
+}
+
+static int gsm411_send_rp_error(struct gsm_trans *trans,
+				u_int8_t msg_ref, u_int8_t cause)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	msgb_tv_put(msg, 1, cause);
+
+	LOGP(DSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause,
+		get_value_string(rp_cause_strs, cause));
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_ERROR_MT, msg_ref);
+}
+
+/* Receive a 04.11 TPDU inside RP-DATA / user data */
+static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans,
+			  struct gsm411_rp_hdr *rph,
+			  u_int8_t src_len, u_int8_t *src,
+			  u_int8_t dst_len, u_int8_t *dst,
+			  u_int8_t tpdu_len, u_int8_t *tpdu)
+{
+	int rc = 0;
+
+	if (src_len && src)
+		LOGP(DSMS, LOGL_ERROR, "RP-DATA (MO) with SRC ?!?\n");
+
+	if (!dst_len || !dst || !tpdu_len || !tpdu) {
+		LOGP(DSMS, LOGL_ERROR,
+			"RP-DATA (MO) without DST or TPDU ?!?\n");
+		gsm411_send_rp_error(trans, rph->msg_ref,
+				     GSM411_RP_CAUSE_INV_MAND_INF);
+		return -EIO;
+	}
+	msg->l4h = tpdu;
+
+	DEBUGP(DSMS, "DST(%u,%s)\n", dst_len, hexdump(dst, dst_len));
+
+	rc = gsm340_rx_tpdu(trans->conn, msg);
+	if (rc == 0)
+		return gsm411_send_rp_ack(trans, rph->msg_ref);
+	else if (rc > 0)
+		return gsm411_send_rp_error(trans, rph->msg_ref, rc);
+	else
+		return rc;
+}
+
+/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */
+static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans,
+			     struct gsm411_rp_hdr *rph)
+{
+	u_int8_t src_len, dst_len, rpud_len;
+	u_int8_t *src = NULL, *dst = NULL , *rp_ud = NULL;
+
+	/* in the MO case, this should always be zero length */
+	src_len = rph->data[0];
+	if (src_len)
+		src = &rph->data[1];
+
+	dst_len = rph->data[1+src_len];
+	if (dst_len)
+		dst = &rph->data[1+src_len+1];
+
+	rpud_len = rph->data[1+src_len+1+dst_len];
+	if (rpud_len)
+		rp_ud = &rph->data[1+src_len+1+dst_len+1];
+
+	DEBUGP(DSMS, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n",
+		src_len, dst_len, rpud_len);
+	return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst,
+				rpud_len, rp_ud);
+}
+
+/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */
+static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans,
+			    struct gsm411_rp_hdr *rph)
+{
+	struct gsm_sms *sms = trans->sms.sms;
+
+	/* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it
+	 * successfully received a SMS.  We can now safely mark it as
+	 * transmitted */
+
+	if (!trans->sms.is_mt) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ACK on a MO transfer ?\n");
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_MSG_INCOMP_STATE);
+	}
+
+	if (!sms) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ACK but no sms in transaction?!?\n");
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_PROTOCOL_ERR);
+	}
+
+	/* mark this SMS as sent in database */
+	db_sms_mark_sent(sms);
+
+	dispatch_signal(SS_SMS, S_SMS_DELIVERED, sms);
+
+	sms_free(sms);
+	trans->sms.sms = NULL;
+
+	/* check for more messages for this subscriber */
+	assert(msg->lchan->conn.subscr == trans->subscr);
+
+	sms = db_sms_get_unsent_for_subscr(trans->subscr);
+	if (sms)
+		gsm411_send_sms_lchan(trans->conn, sms);
+
+	/* free the transaction here */
+	trans_free(trans);
+
+	/* release channel if done */
+#warning "BROKEN. The SAPI will be released automatically by the BSC"
+	if (!sms)
+		rsl_release_request(msg->lchan, trans->sms.link_id);
+
+	return 0;
+}
+
+static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans,
+			      struct gsm411_rp_hdr *rph)
+{
+	struct gsm_network *net = trans->conn->bts->network;
+	struct gsm_sms *sms = trans->sms.sms;
+	u_int8_t cause_len = rph->data[0];
+	u_int8_t cause = rph->data[1];
+
+	/* Error in response to MT RP_DATA, i.e. the MS did not
+	 * successfully receive the SMS.  We need to investigate
+	 * the cause and take action depending on it */
+
+	LOGP(DSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n",
+	     subscr_name(trans->conn->subscr), cause_len, cause,
+	     get_value_string(rp_cause_strs, cause));
+
+	if (!trans->sms.is_mt) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ERR on a MO transfer ?\n");
+#if 0
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_MSG_INCOMP_STATE);
+#endif
+	}
+
+	if (!sms) {
+		LOGP(DSMS, LOGL_ERROR,
+			"RX RP-ERR, but no sms in transaction?!?\n");
+		return -EINVAL;
+#if 0
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_PROTOCOL_ERR);
+#endif
+	}
+
+	if (cause == GSM411_RP_CAUSE_MT_MEM_EXCEEDED) {
+		/* MS has not enough memory to store the message.  We need
+		 * to store this in our database and wati for a SMMA message */
+		/* FIXME */
+		dispatch_signal(SS_SMS, S_SMS_MEM_EXCEEDED, trans->subscr);
+		counter_inc(net->stats.sms.rp_err_mem);
+	} else
+		counter_inc(net->stats.sms.rp_err_other);
+
+	sms_free(sms);
+	trans->sms.sms = NULL;
+
+	//trans_free(trans);
+
+	return 0;
+}
+
+static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans,
+			     struct gsm411_rp_hdr *rph)
+{
+	struct gsm_sms *sms;
+	int rc;
+
+	rc = gsm411_send_rp_ack(trans, rph->msg_ref);
+	trans->sms.rp_state = GSM411_RPS_IDLE;
+
+	/* MS tells us that it has memory for more SMS, we need
+	 * to check if we have any pending messages for it and then
+	 * transfer those */
+	dispatch_signal(SS_SMS, S_SMS_SMMA, trans->subscr);
+
+	/* check for more messages for this subscriber */
+	assert(msg->lchan->conn.subscr == trans->subscr);
+	sms = db_sms_get_unsent_for_subscr(trans->subscr);
+	if (sms)
+		gsm411_send_sms_lchan(trans->conn, sms);
+	else
+		rsl_release_request(msg->lchan, trans->sms.link_id);
+#warning "BROKEN: The SAPI=3 will be released automatically by the BSC"
+
+	return rc;
+}
+
+static int gsm411_rx_cp_data(struct msgb *msg, struct gsm48_hdr *gh,
+			     struct gsm_trans *trans)
+{
+	struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+	u_int8_t msg_type =  rp_data->msg_type & 0x07;
+	int rc = 0;
+
+	switch (msg_type) {
+	case GSM411_MT_RP_DATA_MO:
+		DEBUGP(DSMS, "RX SMS RP-DATA (MO)\n");
+		/* start TR2N and enter 'wait to send RP-ACK state' */
+		trans->sms.rp_state = GSM411_RPS_WAIT_TO_TX_RP_ACK;
+		rc = gsm411_rx_rp_data(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_ACK_MO:
+		DEBUGP(DSMS,"RX SMS RP-ACK (MO)\n");
+		rc = gsm411_rx_rp_ack(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_SMMA_MO:
+		DEBUGP(DSMS, "RX SMS RP-SMMA\n");
+		/* start TR2N and enter 'wait to send RP-ACK state' */
+		trans->sms.rp_state = GSM411_RPS_WAIT_TO_TX_RP_ACK;
+		rc = gsm411_rx_rp_smma(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_ERROR_MO:
+		rc = gsm411_rx_rp_error(msg, trans, rp_data);
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		rc = gsm411_send_rp_error(trans, rp_data->msg_ref,
+					  GSM411_RP_CAUSE_MSGTYPE_NOTEXIST);
+		break;
+	}
+
+	return rc;
+}
+
+/* send CP-ACK to given transaction */
+static int gsm411_tx_cp_ack(struct gsm_trans *trans)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	int rc;
+
+	rc = gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_ACK);
+
+	if (trans->sms.is_mt) {
+		/* If this is a MT SMS DELIVER, we can clear transaction here */
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		//trans_free(trans);
+	}
+
+	return rc;
+}
+
+static int gsm411_tx_cp_error(struct gsm_trans *trans, u_int8_t cause)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	u_int8_t *causep;
+
+	LOGP(DSMS, LOGL_NOTICE, "TX CP-ERROR, cause %d (%s)\n", cause,
+		get_value_string(cp_cause_strs, cause));
+
+	causep = msgb_put(msg, 1);
+	*causep = cause;
+
+	return gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_ERROR);
+}
+
+/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */
+int gsm0411_rcv_sms(struct msgb *msg, u_int8_t link_id)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type;
+	u_int8_t transaction_id = ((gh->proto_discr >> 4) ^ 0x8); /* flip */
+	struct gsm_lchan *lchan = msg->lchan;
+	struct gsm_trans *trans;
+	int rc = 0;
+
+	if (!lchan->conn.subscr)
+		return -EIO;
+		/* FIXME: send some error message */
+
+	DEBUGP(DSMS, "trans_id=%x ", transaction_id);
+	trans = trans_find_by_id(lchan->conn.subscr, GSM48_PDISC_SMS,
+				 transaction_id);
+	if (!trans) {
+		DEBUGPC(DSMS, "(new) ");
+		trans = trans_alloc(lchan->conn.subscr, GSM48_PDISC_SMS,
+				    transaction_id, new_callref++);
+		if (!trans) {
+			DEBUGPC(DSMS, "No memory for trans\n");
+			/* FIXME: send some error message */
+			return -ENOMEM;
+		}
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans->sms.rp_state = GSM411_RPS_IDLE;
+		trans->sms.is_mt = 0;
+		trans->sms.link_id = link_id;
+
+		trans->conn = &lchan->conn;
+		use_subscr_con(trans->conn);
+	}
+
+	switch(msg_type) {
+	case GSM411_MT_CP_DATA:
+		DEBUGPC(DSMS, "RX SMS CP-DATA\n");
+
+		/* 5.4: For MO, if a CP-DATA is received for a new
+		 * transaction, equals reception of an implicit
+		 * last CP-ACK for previous transaction */
+		if (trans->sms.cp_state == GSM411_CPS_IDLE) {
+			int i;
+			struct gsm_trans *ptrans;
+
+			/* Scan through all remote initiated transactions */
+			for (i=8; i<15; i++) {
+				if (i == transaction_id)
+					continue;
+
+				ptrans = trans_find_by_id(lchan->conn.subscr,
+				                          GSM48_PDISC_SMS, i);
+				if (!ptrans)
+					continue;
+
+				DEBUGP(DSMS, "Implicit CP-ACK for trans_id=%x\n", i);
+
+				/* Finish it for good */
+				bsc_del_timer(&ptrans->sms.cp_timer);
+				ptrans->sms.cp_state = GSM411_CPS_IDLE;
+				trans_free(ptrans);
+			}
+		}
+
+		/* 5.2.3.1.3: MO state exists when SMC has received
+		 * CP-DATA, including sending of the assoc. CP-ACK */
+		/* 5.2.3.2.4: MT state exists when SMC has received
+		 * CP-DATA, including sending of the assoc. CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_MM_ESTABLISHED;
+
+		/* SMC instance acknowledges the CP-DATA frame */
+		gsm411_tx_cp_ack(trans);
+		
+		rc = gsm411_rx_cp_data(msg, gh, trans);
+#if 0
+		/* Send CP-ACK or CP-ERORR in response */
+		if (rc < 0) {
+			rc = gsm411_tx_cp_error(trans, GSM411_CP_CAUSE_NET_FAIL);
+		} else
+			rc = gsm411_tx_cp_ack(trans);
+#endif
+		break;
+	case GSM411_MT_CP_ACK:
+		/* previous CP-DATA in this transaction was confirmed */
+		DEBUGPC(DSMS, "RX SMS CP-ACK\n");
+		/* 5.2.3.1.3: MO state exists when SMC has received CP-ACK */
+		/* 5.2.3.2.4: MT state exists when SMC has received CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_MM_ESTABLISHED;
+		/* Stop TC1* after CP-ACK has been received */
+		bsc_del_timer(&trans->sms.cp_timer);
+
+		if (!trans->sms.is_mt) {
+			/* FIXME: we have sont one CP-DATA, which was now
+			 * acknowledged.  Check if we want to transfer more,
+			 * i.e. multi-part message */
+			trans->sms.cp_state = GSM411_CPS_IDLE;
+			trans_free(trans);
+		}
+		break;
+	case GSM411_MT_CP_ERROR:
+		DEBUGPC(DSMS, "RX SMS CP-ERROR, cause %d (%s)\n", gh->data[0],
+			get_value_string(cp_cause_strs, gh->data[0]));
+		bsc_del_timer(&trans->sms.cp_timer);
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans_free(trans);
+		break;
+	default:
+		DEBUGPC(DSMS, "RX Unimplemented CP msg_type: 0x%02x\n", msg_type);
+		rc = gsm411_tx_cp_error(trans, GSM411_CP_CAUSE_MSGTYPE_NOTEXIST);
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans_free(trans);
+		break;
+	}
+
+	return rc;
+}
+
+#if 0
+/* Test TPDU - ALL YOUR */
+static u_int8_t tpdu_test[] = {
+	0x04, 0x04, 0x81, 0x32, 0x24, 0x00, 0x00, 0x80, 0x21, 0x03, 0x41, 0x24,
+	0x32, 0x40, 0x1F, 0x41, 0x26, 0x13, 0x94, 0x7D, 0x56, 0xA5, 0x20, 0x28,
+	0xF2, 0xE9, 0x2C, 0x82, 0x82, 0xD2, 0x22, 0x48, 0x58, 0x64, 0x3E, 0x9D,
+	0x47, 0x10, 0xF5, 0x09, 0xAA, 0x4E, 0x01
+};
+#endif
+
+/* Take a SMS in gsm_sms structure and send it through an already
+ * existing lchan. We also assume that the caller ensured this lchan already
+ * has a SAPI3 RLL connection! */
+int gsm411_send_sms_lchan(struct gsm_subscriber_connection *conn, struct gsm_sms *sms)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	struct gsm_trans *trans;
+	u_int8_t *data, *rp_ud_len;
+	u_int8_t msg_ref = 42;
+	int transaction_id;
+	int rc;
+
+	transaction_id = trans_assign_trans_id(conn->subscr, GSM48_PDISC_SMS, 0);
+	if (transaction_id == -1) {
+		LOGP(DSMS, LOGL_ERROR, "No available transaction ids\n");
+		return -EBUSY;
+	}
+
+	DEBUGP(DSMS, "send_sms_lchan()\n");
+
+	/* FIXME: allocate transaction with message reference */
+	trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS,
+			    transaction_id, new_callref++);
+	if (!trans) {
+		LOGP(DSMS, LOGL_ERROR, "No memory for trans\n");
+		/* FIXME: send some error message */
+		return -ENOMEM;
+	}
+	trans->sms.cp_state = GSM411_CPS_IDLE;
+	trans->sms.rp_state = GSM411_RPS_IDLE;
+	trans->sms.is_mt = 1;
+	trans->sms.sms = sms;
+	trans->sms.link_id = UM_SAPI_SMS;	/* FIXME: main or SACCH ? */
+
+	trans->conn = conn;
+	use_subscr_con(trans->conn);
+
+	/* Hardcode SMSC Originating Address for now */
+	data = (u_int8_t *)msgb_put(msg, 8);
+	data[0] = 0x07;	/* originator length == 7 */
+	data[1] = 0x91; /* type of number: international, ISDN */
+	data[2] = 0x44; /* 447785016005 */
+	data[3] = 0x77;
+	data[4] = 0x58;
+	data[5] = 0x10;
+	data[6] = 0x06;
+	data[7] = 0x50;
+
+	/* Hardcoded Destination Address */
+	data = (u_int8_t *)msgb_put(msg, 1);
+	data[0] = 0;	/* destination length == 0 */
+
+	/* obtain a pointer for the rp_ud_len, so we can fill it later */
+	rp_ud_len = (u_int8_t *)msgb_put(msg, 1);
+
+#if 1
+	/* generate the 03.40 TPDU */
+	rc = gsm340_gen_tpdu(msg, sms);
+	if (rc < 0) {
+		msgb_free(msg);
+		return rc;
+	}
+
+	*rp_ud_len = rc;
+#else
+	data = msgb_put(msg, sizeof(tpdu_test));
+	memcpy(data, tpdu_test, sizeof(tpdu_test));
+	*rp_ud_len = sizeof(tpdu_test);
+#endif
+
+	DEBUGP(DSMS, "TX: SMS DELIVER\n");
+
+	counter_inc(conn->bts->network->stats.sms.delivered);
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_DATA_MT, msg_ref);
+	/* FIXME: enter 'wait for RP-ACK' state, start TR1N */
+}
+
+/* RLL SAPI3 establish callback. Now we have a RLL connection and
+ * can deliver the actual message */
+static void rll_ind_cb(struct gsm_lchan *lchan, u_int8_t link_id,
+			void *_sms, enum bsc_rllr_ind type)
+{
+	struct gsm_sms *sms = _sms;
+
+	DEBUGP(DSMS, "rll_ind_cb(lchan=%p, link_id=%u, sms=%p, type=%u\n",
+		lchan, link_id, sms, type);
+
+	switch (type) {
+	case BSC_RLLR_IND_EST_CONF:
+#warning "BROKEN: The BSC will establish this transparently"
+		gsm411_send_sms_lchan(&lchan->conn, sms);
+		break;
+	case BSC_RLLR_IND_REL_IND:
+	case BSC_RLLR_IND_ERR_IND:
+	case BSC_RLLR_IND_TIMEOUT:
+#warning "BROKEN: We will need to handle SAPI n Reject"
+		sms_free(sms);
+		break;
+	}
+}
+
+/* paging callback. Here we get called if paging a subscriber has
+ * succeeded or failed. */
+static int paging_cb_send_sms(unsigned int hooknum, unsigned int event,
+			      struct msgb *msg, void *_lchan, void *_sms)
+{
+	struct gsm_lchan *lchan = _lchan;
+	struct gsm_sms *sms = _sms;
+	int rc;
+
+	DEBUGP(DSMS, "paging_cb_send_sms(hooknum=%u, event=%u, msg=%p,"
+		"lchan=%p, sms=%p)\n", hooknum, event, msg, lchan, sms);
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		/* Paging aborted without lchan ?!? */
+		if (!lchan) {
+			sms_free(sms);
+			rc = -EIO;
+			break;
+		}
+		/* Establish a SAPI3 RLL connection for SMS */
+		rc = rll_establish(lchan, UM_SAPI_SMS, rll_ind_cb, sms);
+		break;
+	case GSM_PAGING_EXPIRED:
+		sms_free(sms);
+		rc = -ETIMEDOUT;
+		break;
+	default:
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+/* high-level function to send a SMS to a given subscriber. The function
+ * will take care of paging the subscriber, establishing the RLL SAPI3
+ * connection, etc. */
+int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
+			   struct gsm_sms *sms)
+{
+	struct gsm_lchan *lchan;
+	int rc;
+
+	/* check if we already have an open lchan to the subscriber.
+	 * if yes, send the SMS this way */
+	lchan = lchan_for_subscr(subscr);
+	if (lchan)
+		return rll_establish(lchan, UM_SAPI_SMS,
+				     rll_ind_cb, sms);
+
+	/* if not, we have to start paging */
+	rc = paging_request(subscr->net, subscr, RSL_CHANNEED_SDCCH,
+			    paging_cb_send_sms, sms);
+	if (rc <= 0)
+		sms_free(sms);
+
+	return 0;
+}
+
+static int subscr_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr;
+	struct gsm_lchan *lchan;
+	struct gsm_sms *sms;
+
+	switch (signal) {
+	case S_SUBSCR_ATTACHED:
+		/* A subscriber has attached. Check if there are
+		 * any pending SMS for him to be delivered */
+		subscr = signal_data;
+		lchan = lchan_for_subscr(subscr);
+		if (!lchan)
+			break;
+		sms = db_sms_get_unsent_for_subscr(subscr);
+		if (!sms)
+			break;
+		/* Establish a SAPI3 RLL connection for SMS */
+		rll_establish(lchan, UM_SAPI_SMS, rll_ind_cb, sms);
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+void _gsm411_sms_trans_free(struct gsm_trans *trans)
+{
+	bsc_del_timer(&trans->sms.cp_timer);
+}
+
+static __attribute__((constructor)) void on_dso_load_sms(void)
+{
+	register_signal_handler(SS_SUBSCR, subscr_sig_cb, NULL);
+}
diff --git a/openbsc/src/gsm_04_80.c b/openbsc/src/gsm_04_80.c
new file mode 100644
index 0000000..ef10b17
--- /dev/null
+++ b/openbsc/src/gsm_04_80.c
@@ -0,0 +1,328 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+
+/* Forward declarations */
+static int parse_ussd(u_int8_t *ussd, struct ussd_request *req);
+static int parse_ussd_info_elements(u_int8_t *ussd_ie,
+					struct ussd_request *req);
+static int parse_facility_ie(u_int8_t *facility_ie, u_int8_t length,
+					struct ussd_request *req);
+static int parse_ss_invoke(u_int8_t *invoke_data, u_int8_t length,
+					struct ussd_request *req);
+static int parse_process_uss_req(u_int8_t *uss_req_data, u_int8_t length,
+					struct ussd_request *req);
+
+static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, u_int8_t tag)
+{
+	msgb->data -= 2;
+	msgb->data[0] = tag;
+	msgb->data[1] = msgb->len;
+	msgb->len += 2;
+	return msgb->data;
+}
+
+static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, u_int8_t tag,
+					    u_int8_t value)
+{
+	msgb->data -= 3;
+	msgb->len += 3;
+	msgb->data[0] = tag;
+	msgb->data[1] = 1;
+	msgb->data[2] = value;
+	return msgb->data;
+}
+
+
+/* Decode a mobile-originated USSD-request message */
+int gsm0480_decode_ussd_request(const struct msgb *msg, struct ussd_request *req)
+{
+	int rc = 0;
+	u_int8_t *parse_ptr = msgb_l3(msg);
+
+	if ((*parse_ptr & 0x0F) == GSM48_PDISC_NC_SS) {
+		req->transaction_id = *parse_ptr & 0x70;
+		rc = parse_ussd(parse_ptr+1, req);
+	}
+
+	if (!rc)
+		DEBUGP(DMM, "Error occurred while parsing received USSD!\n");
+
+	return rc;
+}
+
+static int parse_ussd(u_int8_t *ussd, struct ussd_request *req)
+{
+	int rc = 1;
+	u_int8_t msg_type = ussd[0] & 0xBF;  /* message-type - section 3.4 */
+
+	switch (msg_type) {
+	case GSM0480_MTYPE_RELEASE_COMPLETE:
+		DEBUGP(DMM, "USS Release Complete\n");
+		/* could also parse out the optional Cause/Facility data */
+		req->text[0] = 0xFF;
+		break;
+	case GSM0480_MTYPE_REGISTER:
+	case GSM0480_MTYPE_FACILITY:
+		rc &= parse_ussd_info_elements(ussd+1, req);
+		break;
+	default:
+		fprintf(stderr, "Unknown GSM 04.80 message-type field 0x%02x\n",
+			ussd[0]);
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+static int parse_ussd_info_elements(u_int8_t *ussd_ie, struct ussd_request *req)
+{
+	int rc;
+	/* Information Element Identifier - table 3.2 & GSM 04.08 section 10.5 */
+	u_int8_t iei = ussd_ie[0];
+	u_int8_t iei_length = ussd_ie[1];
+
+	switch (iei) {
+	case GSM48_IE_CAUSE:
+		break;
+	case GSM0480_IE_FACILITY:
+		rc = parse_facility_ie(ussd_ie+2, iei_length, req);
+		break;
+	case GSM0480_IE_SS_VERSION:
+		break;
+	default:
+		fprintf(stderr, "Unhandled GSM 04.08 or 04.80 IEI 0x%02x\n",
+			iei);
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+static int parse_facility_ie(u_int8_t *facility_ie, u_int8_t length,
+						struct ussd_request *req)
+{
+	int rc = 1;
+	u_int8_t offset = 0;
+
+	do {
+		/* Component Type tag - table 3.7 */
+		u_int8_t component_type = facility_ie[offset];
+		u_int8_t component_length = facility_ie[offset+1];
+
+		switch (component_type) {
+		case GSM0480_CTYPE_INVOKE:
+			rc &= parse_ss_invoke(facility_ie+2,
+						component_length,
+						req);
+			break;
+		case GSM0480_CTYPE_RETURN_RESULT:
+			break;
+		case GSM0480_CTYPE_RETURN_ERROR:
+			break;
+		case GSM0480_CTYPE_REJECT:
+			break;
+		default:
+			fprintf(stderr, "Unknown GSM 04.80 Facility "
+				"Component Type 0x%02x\n", component_type);
+			rc = 0;
+			break;
+		}
+		offset += (component_length+2);
+	} while (offset < length);
+
+	return rc;
+}
+
+/* Parse an Invoke component - see table 3.3 */
+static int parse_ss_invoke(u_int8_t *invoke_data, u_int8_t length,
+						struct ussd_request *req)
+{
+	int rc = 1;
+	u_int8_t offset;
+
+	/* mandatory part */
+	if (invoke_data[0] != GSM0480_COMPIDTAG_INVOKE_ID) {
+		fprintf(stderr, "Unexpected GSM 04.80 Component-ID tag "
+			"0x%02x (expecting Invoke ID tag)\n", invoke_data[0]);
+	}
+
+	offset = invoke_data[1] + 2;
+	req->invoke_id = invoke_data[2];
+
+	/* optional part */
+	if (invoke_data[offset] == GSM0480_COMPIDTAG_LINKED_ID)
+		offset += invoke_data[offset+1] + 2;  /* skip over it */
+
+	/* mandatory part */
+	if (invoke_data[offset] == GSM0480_OPERATION_CODE) {
+		u_int8_t operation_code = invoke_data[offset+2];
+		switch (operation_code) {
+		case GSM0480_OP_CODE_PROCESS_USS_REQ:
+			rc = parse_process_uss_req(invoke_data + offset + 3,
+						   length - offset - 3,
+						   req);
+			break;
+		default:
+			fprintf(stderr, "GSM 04.80 operation code 0x%02x "
+				"is not yet handled\n", operation_code);
+			rc = 0;
+			break;
+		}
+	} else {
+		fprintf(stderr, "Unexpected GSM 04.80 Component-ID tag 0x%02x "
+			"(expecting Operation Code tag)\n",
+			invoke_data[0]);
+		rc = 0;
+	}
+
+	return rc;
+}
+
+/* Parse the parameters of a Process UnstructuredSS Request */
+static int parse_process_uss_req(u_int8_t *uss_req_data, u_int8_t length,
+					struct ussd_request *req)
+{
+	int rc = 0;
+	int num_chars;
+	u_int8_t dcs;
+
+	if (uss_req_data[0] == GSM_0480_SEQUENCE_TAG) {
+		if (uss_req_data[2] == ASN1_OCTET_STRING_TAG) {
+			dcs = uss_req_data[4];
+			if ((dcs == 0x0F) &&
+			    (uss_req_data[5] == ASN1_OCTET_STRING_TAG)) {
+				num_chars = (uss_req_data[6] * 8) / 7;
+				/* Prevent a mobile-originated buffer-overrun! */
+				if (num_chars > MAX_LEN_USSD_STRING)
+					num_chars = MAX_LEN_USSD_STRING;
+				gsm_7bit_decode(req->text,
+						&(uss_req_data[7]), num_chars);
+				/* append null-terminator */
+				req->text[num_chars+1] = 0;
+				rc = 1;
+			}
+		}
+	}
+	return rc;
+}
+
+/* Send response to a mobile-originated ProcessUnstructuredSS-Request */
+int gsm0480_send_ussd_response(const struct msgb *in_msg, const char *response_text,
+						const struct ussd_request *req)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	u_int8_t *ptr8;
+	int response_len;
+
+	response_len = (strlen(response_text) * 7) / 8;
+	if (((strlen(response_text) * 7) % 8) != 0)
+		response_len += 1;
+
+	msg->lchan = in_msg->lchan;
+
+	/* First put the payload text into the message */
+	ptr8 = msgb_put(msg, response_len);
+	gsm_7bit_encode(ptr8, response_text);
+
+	/* Then wrap it as an Octet String */
+	msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG);
+
+	/* Pre-pend the DCS octet string */
+	msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F);
+
+	/* Then wrap these as a Sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the operation code */
+	msgb_push_TLV1(msg, GSM0480_OPERATION_CODE,
+			GSM0480_OP_CODE_PROCESS_USS_REQ);
+
+	/* Wrap the operation code and IA5 string as a sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+	/* Wrap this up as a Return Result component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS | req->transaction_id
+					| (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm48_sendmsg(msg, NULL);
+}
+
+int gsm0480_send_ussd_reject(const struct msgb *in_msg,
+				const struct ussd_request *req)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	msg->lchan = in_msg->lchan;
+
+	/* First insert the problem code */
+	msgb_push_TLV1(msg, GSM_0480_PROBLEM_CODE_TAG_GENERAL,
+			GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
+
+	/* Before it insert the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+	/* Wrap this up as a Reject component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS;
+	gh->proto_discr |= req->transaction_id | (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm48_sendmsg(msg, NULL);
+}
diff --git a/openbsc/src/gsm_data.c b/openbsc/src/gsm_data.c
new file mode 100644
index 0000000..f9cca49
--- /dev/null
+++ b/openbsc/src/gsm_data.c
@@ -0,0 +1,532 @@
+/* (C) 2008-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <netinet/in.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/talloc.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/abis_nm.h>
+#include <osmocore/statistics.h>
+
+void *tall_bsc_ctx;
+
+static LLIST_HEAD(bts_models);
+
+void set_ts_e1link(struct gsm_bts_trx_ts *ts, u_int8_t e1_nr,
+		   u_int8_t e1_ts, u_int8_t e1_ts_ss)
+{
+	ts->e1_link.e1_nr = e1_nr;
+	ts->e1_link.e1_ts = e1_ts;
+	ts->e1_link.e1_ts_ss = e1_ts_ss;
+}
+
+static const struct value_string pchan_names[] = {
+	{ GSM_PCHAN_NONE,	"NONE" },
+	{ GSM_PCHAN_CCCH,	"CCCH" },
+	{ GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" },
+	{ GSM_PCHAN_TCH_F,	"TCH/F" },
+	{ GSM_PCHAN_TCH_H,	"TCH/H" },
+	{ GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
+	{ GSM_PCHAN_PDCH,	"PDCH" },
+	{ GSM_PCHAN_TCH_F_PDCH,	"TCH/F_PDCH" },
+	{ GSM_PCHAN_UNKNOWN,	"UNKNOWN" },
+	{ 0,			NULL }
+};
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c)
+{
+	return get_value_string(pchan_names, c);
+}
+
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name)
+{
+	return get_string_value(pchan_names, name);
+}
+
+static const struct value_string lchant_names[] = {
+	{ GSM_LCHAN_NONE,	"NONE" },
+	{ GSM_LCHAN_SDCCH,	"SDCCH" },
+	{ GSM_LCHAN_TCH_F,	"TCH/F" },
+	{ GSM_LCHAN_TCH_H,	"TCH/H" },
+	{ GSM_LCHAN_UNKNOWN,	"UNKNOWN" },
+	{ 0,			NULL }
+};
+
+const char *gsm_lchant_name(enum gsm_chan_t c)
+{
+	return get_value_string(lchant_names, c);
+}
+
+static const struct value_string lchan_s_names[] = {
+	{ LCHAN_S_NONE,		"NONE" },
+	{ LCHAN_S_ACT_REQ,	"ACTIVATION REQUESTED" },
+	{ LCHAN_S_ACTIVE,	"ACTIVE" },
+	{ LCHAN_S_INACTIVE,	"INACTIVE" },
+	{ LCHAN_S_REL_REQ,	"RELEASE REQUESTED" },
+	{ 0,			NULL }
+};
+
+const char *gsm_lchans_name(enum gsm_lchan_state s)
+{
+	return get_value_string(lchan_s_names, s);
+}
+
+static const struct value_string chreq_names[] = {
+	{ GSM_CHREQ_REASON_EMERG,	"EMERGENCY" },
+	{ GSM_CHREQ_REASON_PAG,		"PAGING" },
+	{ GSM_CHREQ_REASON_CALL,	"CALL" },
+	{ GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" },
+	{ GSM_CHREQ_REASON_OTHER,	"OTHER" },
+	{ 0,				NULL }
+};
+
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c)
+{
+	return get_value_string(chreq_names, c);
+}
+
+static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type)
+{
+	struct gsm_bts_model *model;
+
+	llist_for_each_entry(model, &bts_models, list) {
+		if (model->type == type)
+			return model;
+	}
+
+	return NULL;
+}
+
+int gsm_bts_model_register(struct gsm_bts_model *model)
+{
+	if (bts_model_find(model->type))
+		return -EEXIST;
+
+	tlv_def_patch(&model->nm_att_tlvdef, &nm_att_tlvdef);
+	llist_add_tail(&model->list, &bts_models);
+	return 0;
+}
+
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx);
+	int k;
+
+	if (!trx)
+		return NULL;
+
+	trx->bts = bts;
+	trx->nr = bts->num_trx++;
+	trx->nm_state.administrative = NM_STATE_UNLOCKED;
+
+	for (k = 0; k < TRX_NR_TS; k++) {
+		struct gsm_bts_trx_ts *ts = &trx->ts[k];
+		int l;
+		
+		ts->trx = trx;
+		ts->nr = k;
+		ts->pchan = GSM_PCHAN_NONE;
+
+		for (l = 0; l < TS_MAX_LCHAN; l++) {
+			struct gsm_lchan *lchan;
+			lchan = &ts->lchan[l];
+
+			lchan->ts = ts;
+			lchan->nr = l;
+			lchan->type = GSM_LCHAN_NONE;
+		}
+	}
+
+	if (trx->nr != 0)
+		trx->nominal_power = bts->c0->nominal_power;
+
+	llist_add_tail(&trx->list, &bts->trx_list);
+
+	return trx;
+}
+
+struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, enum gsm_bts_type type,
+			      u_int8_t tsc, u_int8_t bsic)
+{
+	struct gsm_bts *bts = talloc_zero(net, struct gsm_bts);
+	struct gsm_bts_model *model = bts_model_find(type);
+	int i;
+
+	if (!bts)
+		return NULL;
+
+	if (!model && type != GSM_BTS_TYPE_UNKNOWN) {
+		talloc_free(bts);
+		return NULL;
+	}
+
+	bts->network = net;
+	bts->nr = net->num_bts++;
+	bts->type = type;
+	bts->model = model;
+	bts->tsc = tsc;
+	bts->bsic = bsic;
+	bts->num_trx = 0;
+	INIT_LLIST_HEAD(&bts->trx_list);
+	bts->ms_max_power = 15;	/* dBm */
+	bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */
+	bts->si_common.cell_sel_par.rxlev_acc_min = 0;
+	bts->si_common.neigh_list.data = bts->si_common.data.neigh_list;
+	bts->si_common.neigh_list.data_len =
+				sizeof(bts->si_common.data.neigh_list);
+	bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc;
+	bts->si_common.cell_alloc.data_len =
+				sizeof(bts->si_common.data.cell_alloc);
+	bts->si_common.rach_control.re = 1; /* no re-establishment */
+	bts->si_common.rach_control.tx_integer = 9;  /* 12 slots spread - 217/115 slots delay */
+	bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */
+	bts->si_common.rach_control.t2 = 4; /* no emergency calls */
+
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+		bts->gprs.nsvc[i].bts = bts;
+		bts->gprs.nsvc[i].id = i;
+	}
+
+	/* create our primary TRX */
+	bts->c0 = gsm_bts_trx_alloc(bts);
+	if (!bts->c0) {
+		talloc_free(bts);
+		return NULL;
+	}
+	bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4;
+
+	bts->rach_b_thresh = -1;
+	bts->rach_ldavg_slots = -1;
+	llist_add_tail(&bts->list, &net->bts_list);
+
+	return bts;
+}
+
+struct gsm_network *gsm_network_init(u_int16_t country_code, u_int16_t network_code,
+				     int (*mncc_recv)(struct gsm_network *, int, void *))
+{
+	struct gsm_network *net;
+
+	net = talloc_zero(tall_bsc_ctx, struct gsm_network);
+	if (!net)
+		return NULL;
+
+	net->country_code = country_code;
+	net->network_code = network_code;
+	net->num_bts = 0;
+	net->reject_cause = GSM48_REJECT_ROAMING_NOT_ALLOWED;
+	net->T3101 = GSM_T3101_DEFAULT;
+	net->T3113 = GSM_T3113_DEFAULT;
+	/* FIXME: initialize all other timers! */
+
+	/* default set of handover parameters */
+	net->handover.win_rxlev_avg = 10;
+	net->handover.win_rxqual_avg = 1;
+	net->handover.win_rxlev_avg_neigh = 10;
+	net->handover.pwr_interval = 6;
+	net->handover.pwr_hysteresis = 3;
+	net->handover.max_distance = 9999;
+
+	INIT_LLIST_HEAD(&net->trans_list);
+	INIT_LLIST_HEAD(&net->upqueue);
+	INIT_LLIST_HEAD(&net->bts_list);
+
+	net->stats.chreq.total = counter_alloc("net.chreq.total");
+	net->stats.chreq.no_channel = counter_alloc("net.chreq.no_channel");
+	net->stats.handover.attempted = counter_alloc("net.handover.attempted");
+	net->stats.handover.no_channel = counter_alloc("net.handover.no_channel");
+	net->stats.handover.timeout = counter_alloc("net.handover.timeout");
+	net->stats.handover.completed = counter_alloc("net.handover.completed");
+	net->stats.handover.failed = counter_alloc("net.handover.failed");
+	net->stats.loc_upd_type.attach = counter_alloc("net.loc_upd_type.attach");
+	net->stats.loc_upd_type.normal = counter_alloc("net.loc_upd_type.normal");
+	net->stats.loc_upd_type.periodic = counter_alloc("net.loc_upd_type.periodic");
+	net->stats.loc_upd_type.detach = counter_alloc("net.imsi_detach.count");
+	net->stats.loc_upd_resp.reject = counter_alloc("net.loc_upd_resp.reject");
+	net->stats.loc_upd_resp.accept = counter_alloc("net.loc_upd_resp.accept");
+	net->stats.paging.attempted = counter_alloc("net.paging.attempted");
+	net->stats.paging.detached = counter_alloc("net.paging.detached");
+	net->stats.paging.completed = counter_alloc("net.paging.completed");
+	net->stats.paging.expired = counter_alloc("net.paging.expired");
+	net->stats.sms.submitted = counter_alloc("net.sms.submitted");
+	net->stats.sms.no_receiver = counter_alloc("net.sms.no_receiver");
+	net->stats.sms.delivered = counter_alloc("net.sms.delivered");
+	net->stats.sms.rp_err_mem = counter_alloc("net.sms.rp_err_mem");
+	net->stats.sms.rp_err_other = counter_alloc("net.sms.rp_err_other");
+	net->stats.call.dialled = counter_alloc("net.call.dialled");
+	net->stats.call.alerted = counter_alloc("net.call.alerted");
+	net->stats.call.connected = counter_alloc("net.call.connected");
+	net->stats.chan.rf_fail = counter_alloc("net.chan.rf_fail");
+	net->stats.chan.rll_err = counter_alloc("net.chan.rll_err");
+	net->stats.bts.oml_fail = counter_alloc("net.bts.oml_fail");
+	net->stats.bts.rsl_fail = counter_alloc("net.bts.rsl_fail");
+
+	net->mncc_recv = mncc_recv;
+
+	return net;
+}
+
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num)
+{
+	struct gsm_bts *bts;
+
+	if (num >= net->num_bts)
+		return NULL;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		if (bts->nr == num)
+			return bts;
+	}
+
+	return NULL;
+}
+
+/* Get reference to a neighbor cell on a given BCCH ARFCN */
+struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts,
+				 u_int16_t arfcn, u_int8_t bsic)
+{
+	struct gsm_bts *neigh;
+	/* FIXME: use some better heuristics here to determine which cell
+	 * using this ARFCN really is closest to the target cell.  For
+	 * now we simply assume that each ARFCN will only be used by one
+	 * cell */
+
+	llist_for_each_entry(neigh, &bts->network->bts_list, list) {
+		if (neigh->c0->arfcn == arfcn &&
+		    neigh->bsic == bsic)
+			return neigh;
+	}
+
+	return NULL;
+}
+
+struct gsm_bts_trx *gsm_bts_trx_num(struct gsm_bts *bts, int num)
+{
+	struct gsm_bts_trx *trx;
+
+	if (num >= bts->num_trx)
+		return NULL;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (trx->nr == num)
+			return trx;
+	}
+
+	return NULL;
+}
+
+static char ts2str[255];
+
+char *gsm_trx_name(struct gsm_bts_trx *trx)
+{
+	snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)",
+		 trx->bts->nr, trx->nr);
+
+	return ts2str;
+}
+
+
+char *gsm_ts_name(struct gsm_bts_trx_ts *ts)
+{
+	snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)",
+		 ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+	return ts2str;
+}
+
+char *gsm_lchan_name(struct gsm_lchan *lchan)
+{
+	struct gsm_bts_trx_ts *ts = lchan->ts;
+
+	snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)",
+		 ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr);
+
+	return ts2str;
+}
+
+static const struct value_string bts_types[] = {
+	{ GSM_BTS_TYPE_UNKNOWN,	"unknown" },
+	{ GSM_BTS_TYPE_BS11,	"bs11" },
+	{ GSM_BTS_TYPE_NANOBTS,	"nanobts" },
+	{ 0,			NULL }
+};
+
+enum gsm_bts_type parse_btstype(const char *arg)
+{
+	return get_string_value(bts_types, arg);
+}
+
+const char *btstype2str(enum gsm_bts_type type)
+{
+	return get_value_string(bts_types, type);
+}
+
+struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr)
+{
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (trx->nr == nr)
+			return trx;
+	}
+	return NULL;
+}
+
+/* Search for a BTS in the given Location Area; optionally start searching
+ * with start_bts (for continuing to search after the first result) */
+struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
+				struct gsm_bts *start_bts)
+{
+	int i;
+	struct gsm_bts *bts;
+	int skip = 0;
+
+	if (start_bts)
+		skip = 1;
+
+	for (i = 0; i < net->num_bts; i++) {
+		bts = gsm_bts_num(net, i);
+
+		if (skip) {
+			if (start_bts == bts)
+				skip = 0;
+			continue;
+		}
+
+		if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac)
+			return bts;
+	}
+	return NULL;
+}
+
+static const struct value_string auth_policy_names[] = {
+	{ GSM_AUTH_POLICY_CLOSED,	"closed" },
+	{ GSM_AUTH_POLICY_ACCEPT_ALL,	"accept-all" },
+	{ GSM_AUTH_POLICY_TOKEN,	"token" },
+	{ 0,				NULL }
+};
+
+enum gsm_auth_policy gsm_auth_policy_parse(const char *arg)
+{
+	return get_string_value(auth_policy_names, arg);
+}
+
+const char *gsm_auth_policy_name(enum gsm_auth_policy policy)
+{
+	return get_value_string(auth_policy_names, policy);
+}
+
+void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts)
+{
+	raid->mcc = bts->network->country_code;
+	raid->mnc = bts->network->network_code;
+	raid->lac = bts->location_area_code;
+	raid->rac = bts->gprs.rac;
+}
+
+int gsm48_ra_id_by_bts(u_int8_t *buf, struct gsm_bts *bts)
+{
+	struct gprs_ra_id raid;
+
+	gprs_ra_id_by_bts(&raid, bts);
+
+	return gsm48_construct_ra(buf, &raid);
+}
+
+static const struct value_string rrlp_mode_names[] = {
+	{ RRLP_MODE_NONE,	"none" },
+	{ RRLP_MODE_MS_BASED,	"ms-based" },
+	{ RRLP_MODE_MS_PREF,	"ms-preferred" },
+	{ RRLP_MODE_ASS_PREF,	"ass-preferred" },
+	{ 0,			NULL }
+};
+
+enum rrlp_mode rrlp_mode_parse(const char *arg)
+{
+	return get_string_value(rrlp_mode_names, arg);
+}
+
+const char *rrlp_mode_name(enum rrlp_mode mode)
+{
+	return get_value_string(rrlp_mode_names, mode);
+}
+
+static const struct value_string bts_gprs_mode_names[] = {
+	{ BTS_GPRS_NONE,	"none" },
+	{ BTS_GPRS_GPRS,	"gprs" },
+	{ BTS_GPRS_EGPRS,	"egprs" },
+	{ 0,			NULL }
+};
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg)
+{
+	return get_string_value(bts_gprs_mode_names, arg);
+}
+
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode)
+{
+	return get_value_string(bts_gprs_mode_names, mode);
+}
+
+struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan)
+{
+	struct gsm_meas_rep *meas_rep;
+
+	meas_rep = &lchan->meas_rep[lchan->meas_rep_idx];
+	memset(meas_rep, 0, sizeof(*meas_rep));
+	meas_rep->lchan = lchan;
+	lchan->meas_rep_idx = (lchan->meas_rep_idx + 1)
+					% ARRAY_SIZE(lchan->meas_rep);
+
+	return meas_rep;
+}
+
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type)
+{
+	struct gsm_bts_model *model;
+
+	model = bts_model_find(type);
+	if (!model)
+		return -EINVAL;
+
+	bts->type = type;
+	bts->model = model;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		/* Set the default OML Stream ID to 0xff */
+		bts->oml_tei = 0xff;
+		bts->c0->nominal_power = 23;
+		break;
+	case GSM_BTS_TYPE_BS11:
+		break;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/gsm_subscriber.c b/openbsc/src/gsm_subscriber.c
new file mode 100644
index 0000000..6925087
--- /dev/null
+++ b/openbsc/src/gsm_subscriber.c
@@ -0,0 +1,130 @@
+/* The concept of a subscriber for the MSC, roughly HLR/VLR functionality */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+
+extern struct llist_head *subscr_bsc_active_subscriber(void);
+
+char *subscr_name(struct gsm_subscriber *subscr)
+{
+	if (strlen(subscr->name))
+		return subscr->name;
+
+	return subscr->imsi;
+}
+
+struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_network *net,
+					  u_int32_t tmsi)
+{
+	char tmsi_string[14];
+	struct gsm_subscriber *subscr;
+
+	/* we might have a record in memory already */
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (tmsi == subscr->tmsi)
+			return subscr_get(subscr);
+	}
+
+	sprintf(tmsi_string, "%u", tmsi);
+	return db_get_subscriber(net, GSM_SUBSCRIBER_TMSI, tmsi_string);
+}
+
+struct gsm_subscriber *subscr_get_by_imsi(struct gsm_network *net,
+					  const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_IMSI, imsi);
+}
+
+struct gsm_subscriber *subscr_get_by_extension(struct gsm_network *net,
+					       const char *ext)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->extension, ext) == 0)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_EXTENSION, ext);
+}
+
+struct gsm_subscriber *subscr_get_by_id(struct gsm_network *net,
+					unsigned long long id)
+{
+	struct gsm_subscriber *subscr;
+	char buf[32];
+	sprintf(buf, "%llu", id);
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (subscr->id == id)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_ID, buf);
+}
+
+
+int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason)
+{
+	/* FIXME: Migrate pending requests from one BSC to another */
+	switch (reason) {
+	case GSM_SUBSCRIBER_UPDATE_ATTACHED:
+		s->net = bts->network;
+		/* Indicate "attached to LAC" */
+		s->lac = bts->location_area_code;
+		LOGP(DMM, LOGL_INFO, "Subscriber %s ATTACHED LAC=%u\n",
+			subscr_name(s), s->lac);
+		dispatch_signal(SS_SUBSCR, S_SUBSCR_ATTACHED, s);
+		break;
+	case GSM_SUBSCRIBER_UPDATE_DETACHED:
+		/* Only detach if we are currently in this area */
+		if (bts->location_area_code == s->lac)
+			s->lac = GSM_LAC_RESERVED_DETACHED;
+		LOGP(DMM, LOGL_INFO, "Subscriber %s DETACHED\n", subscr_name(s));
+		dispatch_signal(SS_SUBSCR, S_SUBSCR_DETACHED, s);
+		break;
+	default:
+		fprintf(stderr, "subscr_update with unknown reason: %d\n",
+			reason);
+		break;
+	};
+	return db_sync_subscriber(s);
+}
+
+
diff --git a/openbsc/src/gsm_subscriber_base.c b/openbsc/src/gsm_subscriber_base.c
new file mode 100644
index 0000000..40c3bbd
--- /dev/null
+++ b/openbsc/src/gsm_subscriber_base.c
@@ -0,0 +1,214 @@
+/* The concept of a subscriber as seen by the BSC */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/paging.h>
+#include <openbsc/debug.h>
+
+LLIST_HEAD(active_subscribers);
+void *tall_subscr_ctx;
+void *tall_sub_req_ctx;
+
+/* for the gsm_subscriber.c */
+struct llist_head *subscr_bsc_active_subscriber(void)
+{
+	return &active_subscribers;
+}
+
+/*
+ * Struct for pending channel requests. This is managed in the
+ * llist_head requests of each subscriber. The reference counting
+ * should work in such a way that a subscriber with a pending request
+ * remains in memory.
+ */
+struct subscr_request {
+	struct llist_head entry;
+
+	/* back reference */
+	struct gsm_subscriber *subscr;
+
+	/* the requested channel type */
+	int channel_type;
+
+	/* the callback data */
+	gsm_cbfn *cbfn;
+	void *param;
+};
+
+/*
+ * We got the channel assigned and can now hand this channel
+ * over to one of our callbacks.
+ */
+static int subscr_paging_cb(unsigned int hooknum, unsigned int event,
+			     struct msgb *msg, void *data, void *param)
+{
+	struct subscr_request *request;
+	struct gsm_subscriber *subscr = (struct gsm_subscriber *)param;
+
+	/* There is no request anymore... */
+	if (llist_empty(&subscr->requests))
+		return -1;
+
+	/*
+	 * FIXME: What to do with paging requests coming during
+	 * this callback? We must be sure to not start paging when
+	 * we have an active connection to a subscriber and to make
+	 * the subscr_put_channel work as required...
+	 */
+	request = (struct subscr_request *)subscr->requests.next;
+	llist_del(&request->entry);
+	subscr->in_callback = 1;
+	request->cbfn(hooknum, event, msg, data, request->param);
+	subscr->in_callback = 0;
+
+	talloc_free(request);
+	return 0;
+}
+
+static void subscr_send_paging_request(struct gsm_subscriber *subscr)
+{
+	struct subscr_request *request;
+	int rc;
+
+	assert(!llist_empty(&subscr->requests));
+
+	request = (struct subscr_request *)subscr->requests.next;
+	rc = paging_request(subscr->net, subscr, request->channel_type,
+			    subscr_paging_cb, subscr);
+
+	/* paging failed, quit now */
+	if (rc <= 0) {
+		subscr_paging_cb(GSM_HOOK_RR_PAGING, GSM_PAGING_EXPIRED,
+				 NULL, NULL, request->param);
+	}
+}
+
+struct gsm_subscriber *subscr_alloc(void)
+{
+	struct gsm_subscriber *s;
+
+	s = talloc_zero(tall_subscr_ctx, struct gsm_subscriber);
+	if (!s)
+		return NULL;
+
+	llist_add_tail(&s->entry, &active_subscribers);
+	s->use_count = 1;
+	s->tmsi = GSM_RESERVED_TMSI;
+
+	INIT_LLIST_HEAD(&s->requests);
+
+	return s;
+}
+
+static void subscr_free(struct gsm_subscriber *subscr)
+{
+	llist_del(&subscr->entry);
+	talloc_free(subscr);
+}
+
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr)
+{
+	subscr->use_count++;
+	DEBUGP(DREF, "subscr %s usage increases usage to: %d\n",
+			subscr->extension, subscr->use_count);
+	return subscr;
+}
+
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr)
+{
+	subscr->use_count--;
+	DEBUGP(DREF, "subscr %s usage decreased usage to: %d\n",
+			subscr->extension, subscr->use_count);
+	if (subscr->use_count <= 0)
+		subscr_free(subscr);
+	return NULL;
+}
+
+void subscr_get_channel(struct gsm_subscriber *subscr,
+			int type, gsm_cbfn *cbfn, void *param)
+{
+	struct subscr_request *request;
+
+	request = talloc(tall_sub_req_ctx, struct subscr_request);
+	if (!request) {
+		if (cbfn)
+			cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_OOM,
+				NULL, NULL, param);
+		return;
+	}
+
+	memset(request, 0, sizeof(*request));
+	request->subscr = subscr;
+	request->channel_type = type;
+	request->cbfn = cbfn;
+	request->param = param;
+
+	/*
+	 * FIXME: We might be able to assign more than one
+	 * channel, e.g. voice and SMS submit at the same
+	 * time.
+	 */
+	if (!subscr->in_callback && llist_empty(&subscr->requests)) {
+		/* add to the list, send a request */
+		llist_add_tail(&request->entry, &subscr->requests);
+		subscr_send_paging_request(subscr);
+	} else {
+		/* this will be picked up later, from subscr_put_channel */
+		llist_add_tail(&request->entry, &subscr->requests);
+	}
+}
+
+void subscr_put_channel(struct gsm_lchan *lchan)
+{
+	struct gsm_subscriber_connection *conn = &lchan->conn;
+	/*
+	 * FIXME: Continue with other requests now... by checking
+	 * the gsm_subscriber inside the gsm_lchan. Drop the ref count
+	 * of the lchan after having asked the next requestee to handle
+	 * the channel.
+	 */
+	/*
+	 * FIXME: is the lchan is of a different type we could still
+	 * issue an immediate assignment for another channel and then
+	 * close this one.
+	 */
+	/*
+	 * Currently we will drop the last ref of the lchan which
+	 * will result in a channel release on RSL and we will start
+	 * the paging. This should work most of the time as the MS
+	 * will listen to the paging requests before we timeout
+	 */
+
+	put_subscr_con(conn);
+
+	if (lchan->conn.subscr && !llist_empty(&lchan->conn.subscr->requests))
+		subscr_send_paging_request(lchan->conn.subscr);
+}
+
diff --git a/openbsc/src/handover_decision.c b/openbsc/src/handover_decision.c
new file mode 100644
index 0000000..efafca6
--- /dev/null
+++ b/openbsc/src/handover_decision.c
@@ -0,0 +1,298 @@
+/* Handover Decision making for Inter-BTS (Intra-BSC) Handover.  This
+ * only implements the handover algorithm/decision, but not execution
+ * of it */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+#include <openbsc/handover.h>
+#include <osmocore/gsm_utils.h>
+
+/* issue handover to a cell identified by ARFCN and BSIC */
+static int handover_to_arfcn_bsic(struct gsm_lchan *lchan,
+				  u_int16_t arfcn, u_int8_t bsic)
+{
+	struct gsm_bts *new_bts;
+
+	/* resolve the gsm_bts structure for the best neighbor */
+	new_bts = gsm_bts_neighbor(lchan->ts->trx->bts, arfcn, bsic);
+	if (!new_bts) {
+		LOGP(DHO, LOGL_NOTICE, "unable to determine neighbor BTS "
+		     "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic);
+		return -EINVAL;
+	}
+
+	/* and actually try to handover to that cell */
+	return bsc_handover_start(lchan, new_bts);
+}
+
+/* did we get a RXLEV for a given cell in the given report? */
+static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr,
+				 u_int16_t arfcn, u_int8_t bsic)
+{
+	int i;
+
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+
+		/* search for matching report */
+		if (!(mrc->arfcn == arfcn && mrc->bsic == bsic))
+			continue;
+
+		mrc->flags |= MRC_F_PROCESSED;
+		return mrc->rxlev;
+	}
+	return -ENODEV;
+}
+
+/* obtain averaged rxlev for given neighbor */
+static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window)
+{
+	unsigned int i, idx;
+	int avg = 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev),
+				nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev),
+				window);
+
+	for (i = 0; i < window; i++) {
+		int j = (idx+i) % ARRAY_SIZE(nmp->rxlev);
+
+		avg += nmp->rxlev[j];
+	}
+
+	return avg / window;
+}
+
+/* find empty or evict bad neighbor */
+static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan)
+{
+	int j, worst = 999999;
+	struct neigh_meas_proc *nmp_worst;
+
+	/* first try to find an empty/unused slot */
+	for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &lchan->neigh_meas[j];
+		if (!nmp->arfcn)
+			return nmp;
+	}
+
+	/* no empty slot found. evict worst neighbor from list */
+	for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &lchan->neigh_meas[j];
+		int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG);
+		if (avg < worst) {
+			worst = avg;
+			nmp_worst = nmp;
+		}
+	}
+
+	return nmp_worst;
+}
+
+/* process neighbor cell measurement reports */
+static void process_meas_neigh(struct gsm_meas_rep *mr)
+{
+	int i, j, idx;
+
+	/* for each reported cell, try to update global state */
+	for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j];
+		unsigned int idx;
+		int rxlev;
+
+		/* skip unused entries */
+		if (!nmp->arfcn)
+			continue;
+
+		rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic);
+		idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev);
+		if (rxlev >= 0) {
+			nmp->rxlev[idx] = rxlev;
+			nmp->last_seen_nr = mr->nr;
+		} else
+			nmp->rxlev[idx] = 0;
+		nmp->rxlev_cnt++;
+	}
+
+	/* iterate over list of reported cells, check if we did not
+	 * process all of them */
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+		struct neigh_meas_proc *nmp;
+
+		if (mrc->flags & MRC_F_PROCESSED)
+			continue;
+
+		nmp = find_evict_neigh(mr->lchan);
+
+		nmp->arfcn = mrc->arfcn;
+		nmp->bsic = mrc->bsic;
+
+		idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev);
+		nmp->rxlev[idx] = mrc->rxlev;
+		nmp->rxlev_cnt++;
+		nmp->last_seen_nr = mr->nr;
+
+		mrc->flags |= MRC_F_PROCESSED;
+	}
+}
+
+/* attempt to do a handover */
+static int attempt_handover(struct gsm_meas_rep *mr)
+{
+	struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+	struct neigh_meas_proc *best_cell = NULL;
+	unsigned int best_better_db = 0;
+	int i, rc;
+
+	/* find the best cell in this report that is at least RXLEV_HYST
+	 * better than the current serving cell */
+
+	for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) {
+		struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i];
+		int avg, better;
+
+		/* skip empty slots */
+		if (nmp->arfcn == 0)
+			continue;
+
+		/* caculate average rxlev for this cell over the window */
+		avg = neigh_meas_avg(nmp, net->handover.win_rxlev_avg_neigh);
+
+		/* check if hysteresis is fulfilled */
+		if (avg < mr->dl.full.rx_lev + net->handover.pwr_hysteresis)
+			continue;
+
+		better = avg - mr->dl.full.rx_lev;
+		if (better > best_better_db) {
+			best_cell = nmp;
+			best_better_db = better;
+		}
+	}
+
+	if (!best_cell)
+		return 0;
+
+	LOGP(DHO, LOGL_INFO, "%s: Cell on ARFCN %u is better: ",
+		gsm_ts_name(mr->lchan->ts), best_cell->arfcn);
+	if (!net->handover.active) {
+		LOGPC(DHO, LOGL_INFO, "Skipping, Handover disabled\n");
+		return 0;
+	}
+
+	rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic);
+	switch (rc) {
+	case 0:
+		LOGPC(DHO, LOGL_INFO, "Starting handover\n");
+		break;
+	case -ENOSPC:
+		LOGPC(DHO, LOGL_INFO, "No channel available\n");
+		break;
+	case -EBUSY:
+		LOGPC(DHO, LOGL_INFO, "Handover already active\n");
+		break;
+	default:
+		LOGPC(DHO, LOGL_ERROR, "Unknown error\n");
+	}
+	return rc;
+}
+
+/* process an already parsed measurement report and decide if we want to
+ * attempt a handover */
+static int process_meas_rep(struct gsm_meas_rep *mr)
+{
+	struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+	int av_rxlev;
+
+	/* we currently only do handover for TCH channels */
+	switch (mr->lchan->type) {
+	case GSM_LCHAN_TCH_F:
+	case GSM_LCHAN_TCH_H:
+		break;
+	default:
+		return 0;
+	}
+
+	/* parse actual neighbor cell info */
+	if (mr->num_cell > 0 && mr->num_cell < 7)
+		process_meas_neigh(mr);
+
+	av_rxlev = get_meas_rep_avg(mr->lchan, MEAS_REP_DL_RXLEV_FULL,
+				    net->handover.win_rxlev_avg);
+
+	/* Interference HO */
+	if (rxlev2dbm(av_rxlev) > -85 &&
+	    meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL,
+				   3, 4, 5))
+		return attempt_handover(mr);
+
+	/* Bad Quality */
+	if (meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL,
+				   3, 4, 5))
+		return attempt_handover(mr);
+
+	/* Low Level */
+	if (rxlev2dbm(av_rxlev) <= -110)
+		return attempt_handover(mr);
+
+	/* Distance */
+	if (mr->ms_l1.ta > net->handover.max_distance)
+		return attempt_handover(mr);
+
+	/* Power Budget AKA Better Cell */
+	if ((mr->nr % net->handover.pwr_interval) == 0)
+		return attempt_handover(mr);
+
+	return 0;
+
+}
+
+static int ho_dec_sig_cb(unsigned int subsys, unsigned int signal,
+			   void *handler_data, void *signal_data)
+{
+	struct gsm_meas_rep *mr;
+
+	if (subsys != SS_LCHAN)
+		return 0;
+
+	switch (signal) {
+	case S_LCHAN_MEAS_REP:
+		mr = signal_data;
+		process_meas_rep(mr);
+		break;
+	}
+
+	return 0;
+}
+
+void on_dso_load_ho_dec(void)
+{
+	register_signal_handler(SS_LCHAN, ho_dec_sig_cb, NULL);
+}
diff --git a/openbsc/src/handover_logic.c b/openbsc/src/handover_logic.c
new file mode 100644
index 0000000..b2ffe46
--- /dev/null
+++ b/openbsc/src/handover_logic.c
@@ -0,0 +1,379 @@
+/* Handover Logic for Inter-BTS (Intra-BSC) Handover.  This does not
+ * actually implement the handover algorithm/decision, but executes a
+ * handover decision */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+struct bsc_handover {
+	struct llist_head list;
+
+	struct gsm_lchan *old_lchan;
+	struct gsm_lchan *new_lchan;
+
+	struct timer_list T3103;
+
+	u_int8_t ho_ref;
+};
+
+static LLIST_HEAD(bsc_handovers);
+
+static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	llist_for_each_entry(ho, &bsc_handovers, list) {
+		if (ho->new_lchan == new_lchan)
+			return ho;
+	}
+
+	return NULL;
+}
+
+static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan)
+{
+	struct bsc_handover *ho;
+
+	llist_for_each_entry(ho, &bsc_handovers, list) {
+		if (ho->old_lchan == old_lchan)
+			return ho;
+	}
+
+	return NULL;
+}
+
+/* Hand over the specified logical channel to the specified new BTS.
+ * This is the main entry point for the actual handover algorithm,
+ * after it has decided it wants to initiate HO to a specific BTS */
+int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts)
+{
+	struct gsm_lchan *new_lchan;
+	struct bsc_handover *ho;
+	static u_int8_t ho_ref;
+	int rc;
+
+	/* don't attempt multiple handovers for the same lchan at
+	 * the same time */
+	if (bsc_ho_by_old_lchan(old_lchan))
+		return -EBUSY;
+
+	DEBUGP(DHO, "(old_lchan on BTS %u, new BTS %u)\n",
+		old_lchan->ts->trx->bts->nr, bts->nr);
+
+	counter_inc(bts->network->stats.handover.attempted);
+
+	new_lchan = lchan_alloc(bts, old_lchan->type);
+	if (!new_lchan) {
+		LOGP(DHO, LOGL_NOTICE, "No free channel\n");
+		counter_inc(bts->network->stats.handover.no_channel);
+		return -ENOSPC;
+	}
+
+	ho = talloc_zero(NULL, struct bsc_handover);
+	if (!ho) {
+		LOGP(DHO, LOGL_FATAL, "Out of Memory\n");
+		lchan_free(new_lchan);
+		return -ENOMEM;
+	}
+	ho->old_lchan = old_lchan;
+	ho->new_lchan = new_lchan;
+	ho->ho_ref = ho_ref++;
+
+	/* copy some parameters from old lchan */
+	memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr));
+	new_lchan->ms_power = old_lchan->ms_power;
+	new_lchan->bs_power = old_lchan->bs_power;
+	new_lchan->rsl_cmode = old_lchan->rsl_cmode;
+	new_lchan->tch_mode = old_lchan->tch_mode;
+	new_lchan->conn.subscr = subscr_get(old_lchan->conn.subscr);
+
+	/* FIXME: do we have a better idea of the timing advance? */
+	rc = rsl_chan_activate_lchan(new_lchan, RSL_ACT_INTER_ASYNC, 0,
+				     ho->ho_ref);
+	if (rc < 0) {
+		LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+		talloc_free(ho);
+		lchan_free(new_lchan);
+		return rc;
+	}
+
+	rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
+	llist_add(&ho->list, &bsc_handovers);
+	/* we continue in the SS_LCHAN handler / ho_chan_activ_ack */
+
+	return 0;
+}
+
+/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */
+static void ho_T3103_cb(void *_ho)
+{
+	struct bsc_handover *ho = _ho;
+	struct gsm_network *net = ho->new_lchan->ts->trx->bts->network;
+
+	DEBUGP(DHO, "HO T3103 expired\n");
+	counter_inc(net->stats.handover.timeout);
+
+	lchan_free(ho->new_lchan);
+	llist_del(&ho->list);
+	talloc_free(ho);
+}
+
+/* RSL has acknowledged activation of the new lchan */
+static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+	int rc;
+
+	/* we need to check if this channel activation is related to
+	 * a handover at all (and if, which particular handover) */
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho)
+		return -ENODEV;
+
+	DEBUGP(DHO, "handover activate ack, send HO Command\n");
+
+	/* we can now send the 04.08 HANDOVER COMMAND to the MS
+	 * using the old lchan */
+
+	rc = gsm48_send_ho_cmd(ho->old_lchan, new_lchan, 0, ho->ho_ref);
+
+	/* start T3103.  We can continue either with T3103 expiration,
+	 * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */
+	ho->T3103.cb = ho_T3103_cb;
+	ho->T3103.data = ho;
+	bsc_schedule_timer(&ho->T3103, 10, 0);
+
+	/* create a RTP connection */
+	if (is_ipaccess_bts(new_lchan->ts->trx->bts))
+		rsl_ipacc_crcx(new_lchan);
+
+	return 0;
+}
+
+/* RSL has not acknowledged activation of the new lchan */
+static int ho_chan_activ_nack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	llist_del(&ho->list);
+	talloc_free(ho);
+
+	/* FIXME: maybe we should try to allocate a new LCHAN here? */
+
+	return 0;
+}
+
+/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */
+static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan)
+{
+	struct gsm_network *net = new_lchan->ts->trx->bts->network;
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	LOGP(DHO, LOGL_INFO, "Subscriber %s HO from BTS %u->%u on ARFCN "
+	     "%u->%u\n", subscr_name(ho->old_lchan->conn.subscr),
+	     ho->old_lchan->ts->trx->bts->nr, new_lchan->ts->trx->bts->nr,
+	     ho->old_lchan->ts->trx->arfcn, new_lchan->ts->trx->arfcn);
+
+	counter_inc(net->stats.handover.completed);
+
+	bsc_del_timer(&ho->T3103);
+
+	/* update lchan pointer of transaction */
+	trans_lchan_change(&ho->old_lchan->conn, &new_lchan->conn);
+
+	rsl_lchan_set_state(ho->old_lchan, LCHAN_S_INACTIVE);
+	lchan_auto_release(ho->old_lchan);
+
+	/* do something to re-route the actual speech frames ! */
+
+	llist_del(&ho->list);
+	talloc_free(ho);
+
+	return 0;
+}
+
+/* GSM 04.08 HANDOVER FAIL has been received */
+static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_network *net = old_lchan->ts->trx->bts->network;
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_old_lchan(old_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	counter_inc(net->stats.handover.failed);
+
+	bsc_del_timer(&ho->T3103);
+	llist_del(&ho->list);
+	conn = &ho->new_lchan->conn;
+	put_subscr_con(conn);
+	talloc_free(ho);
+
+	return 0;
+}
+
+/* GSM 08.58 HANDOVER DETECT has been received */
+static int ho_rsl_detect(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	/* FIXME: do we actually want to do something here ? */
+
+	return 0;
+}
+
+static int ho_ipac_crcx_ack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+	struct rtp_socket *old_rs, *new_rs, *other_rs;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		/* it is perfectly normal, we have CRCX even in non-HO cases */
+		return 0;
+	}
+
+	if (ipacc_rtp_direct) {
+		LOGP(DHO, LOGL_ERROR, "unable to handover in direct RTP mode\n");
+		return 0;
+	}
+
+	/* RTP Proxy mode */
+	new_rs = new_lchan->abis_ip.rtp_socket;
+	old_rs = ho->old_lchan->abis_ip.rtp_socket;
+
+	if (!new_rs) {
+		LOGP(DHO, LOGL_ERROR, "no RTP socket for new_lchan\n");
+		return -EIO;
+	}
+
+	rsl_ipacc_mdcx_to_rtpsock(new_lchan);
+
+	if (!old_rs) {
+		LOGP(DHO, LOGL_ERROR, "no RTP socekt for old_lchan\n");
+		return -EIO;
+	}
+
+	/* copy rx_action and reference to other sock */
+	new_rs->rx_action = old_rs->rx_action;
+	new_rs->tx_action = old_rs->tx_action;
+	new_rs->transmit = old_rs->transmit;
+
+	switch (ho->old_lchan->abis_ip.rtp_socket->rx_action) {
+	case RTP_PROXY:
+		other_rs = old_rs->proxy.other_sock;
+		rtp_socket_proxy(new_rs, other_rs);
+		/* delete reference to other end socket to prevent
+		 * rtp_socket_free() from removing the inverse reference */
+		old_rs->proxy.other_sock = NULL;
+		break;
+	case RTP_RECV_UPSTREAM:
+		new_rs->receive = old_rs->receive;
+		break;
+	case RTP_NONE:
+		break;
+	}
+
+	return 0;
+}
+
+static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
+			   void *handler_data, void *signal_data)
+{
+	struct gsm_lchan *lchan;
+
+	switch (subsys) {
+	case SS_LCHAN:
+		lchan = signal_data;
+		switch (signal) {
+		case S_LCHAN_ACTIVATE_ACK:
+			return ho_chan_activ_ack(lchan);
+		case S_LCHAN_ACTIVATE_NACK:
+			return ho_chan_activ_nack(lchan);
+		case S_LCHAN_HANDOVER_DETECT:
+			return ho_rsl_detect(lchan);
+		case S_LCHAN_HANDOVER_COMPL:
+			return ho_gsm48_ho_compl(lchan);
+		case S_LCHAN_HANDOVER_FAIL:
+			return ho_gsm48_ho_fail(lchan);
+		}
+		break;
+	case SS_ABISIP:
+		lchan = signal_data;
+		switch (signal) {
+		case S_ABISIP_CRCX_ACK:
+			return ho_ipac_crcx_ack(lchan);
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_ho_logic(void)
+{
+	register_signal_handler(SS_LCHAN, ho_logic_sig_cb, NULL);
+	register_signal_handler(SS_ABISIP, ho_logic_sig_cb, NULL);
+}
diff --git a/openbsc/src/input/ipaccess.c b/openbsc/src/input/ipaccess.c
new file mode 100644
index 0000000..721cadd
--- /dev/null
+++ b/openbsc/src/input/ipaccess.c
@@ -0,0 +1,778 @@
+/* OpenBSC Abis input driver for ip.access */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+
+#define PRIV_OML 1
+#define PRIV_RSL 2
+
+/* data structure for one E1 interface with A-bis */
+struct ia_e1_handle {
+	struct bsc_fd listen_fd;
+	struct bsc_fd rsl_listen_fd;
+	struct gsm_network *gsmnet;
+};
+
+static struct ia_e1_handle *e1h;
+
+
+#define TS1_ALLOC_SIZE	900
+
+static const u_int8_t pong[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG };
+static const u_int8_t id_ack[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK };
+static const u_int8_t id_req[] = { 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET,
+					0x01, IPAC_IDTAG_UNIT,
+					0x01, IPAC_IDTAG_MACADDR,
+					0x01, IPAC_IDTAG_LOCATION1,
+					0x01, IPAC_IDTAG_LOCATION2,
+					0x01, IPAC_IDTAG_EQUIPVERS,
+					0x01, IPAC_IDTAG_SWVERSION,
+					0x01, IPAC_IDTAG_UNITNAME,
+					0x01, IPAC_IDTAG_SERNR,
+				};
+
+static const char *idtag_names[] = {
+	[IPAC_IDTAG_SERNR]	= "Serial_Number",
+	[IPAC_IDTAG_UNITNAME]	= "Unit_Name",
+	[IPAC_IDTAG_LOCATION1]	= "Location_1",
+	[IPAC_IDTAG_LOCATION2]	= "Location_2",
+	[IPAC_IDTAG_EQUIPVERS]	= "Equipment_Version",
+	[IPAC_IDTAG_SWVERSION]	= "Software_Version",
+	[IPAC_IDTAG_IPADDR]	= "IP_Address",
+	[IPAC_IDTAG_MACADDR]	= "MAC_Address",
+	[IPAC_IDTAG_UNIT]	= "Unit_ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+	if (tag >= ARRAY_SIZE(idtag_names))
+		return "unknown";
+
+	return idtag_names[tag];
+}
+
+int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
+{
+	u_int8_t t_len;
+	u_int8_t t_tag;
+	u_int8_t *cur = buf;
+
+	while (cur < buf + len) {
+		t_len = *cur++;
+		t_tag = *cur++;
+
+		DEBUGPC(DMI, "%s='%s' ", ipac_idtag_name(t_tag), cur);
+
+		dec->lv[t_tag].len = t_len;
+		dec->lv[t_tag].val = cur;
+
+		cur += t_len;
+	}
+	return 0;
+}
+
+struct gsm_bts *find_bts_by_unitid(struct gsm_network *net,
+				   u_int16_t site_id, u_int16_t bts_id)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+
+		if (!is_ipaccess_bts(bts))
+			continue;
+
+		if (bts->ip_access.site_id == site_id &&
+		    bts->ip_access.bts_id == bts_id)
+			return bts;
+	}
+
+	return NULL;
+}
+
+static int parse_unitid(const char *str, u_int16_t *site_id, u_int16_t *bts_id,
+			u_int16_t *trx_id)
+{
+	unsigned long ul;
+	char *endptr;
+	const char *nptr;
+
+	nptr = str;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (site_id)
+		*site_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (bts_id)
+		*bts_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+	
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (trx_id)
+		*trx_id = ul & 0xffff;
+
+	return 0;
+}
+
+/* send the id ack */
+int ipaccess_send_id_ack(int fd)
+{
+	return write(fd, id_ack, sizeof(id_ack));
+}
+
+int ipaccess_send_id_req(int fd)
+{
+	return write(fd, id_req, sizeof(id_req));
+}
+
+/* base handling of the ip.access protocol */
+int ipaccess_rcvmsg_base(struct msgb *msg,
+			 struct bsc_fd *bfd)
+{
+	u_int8_t msg_type = *(msg->l2h);
+	int ret = 0;
+
+	switch (msg_type) {
+	case IPAC_MSGT_PING:
+		ret = write(bfd->fd, pong, sizeof(pong));
+		break;
+	case IPAC_MSGT_PONG:
+		DEBUGP(DMI, "PONG!\n");
+		break;
+	case IPAC_MSGT_ID_ACK:
+		DEBUGP(DMI, "ID_ACK? -> ACK!\n");
+		ret = ipaccess_send_id_ack(bfd->fd);
+		break;
+	}
+	return 0;
+}
+
+static int ipaccess_rcvmsg(struct e1inp_line *line, struct msgb *msg,
+			   struct bsc_fd *bfd)
+{
+	struct tlv_parsed tlvp;
+	u_int8_t msg_type = *(msg->l2h);
+	u_int16_t site_id = 0, bts_id = 0, trx_id = 0;
+	struct gsm_bts *bts;
+
+	/* handle base messages */
+	ipaccess_rcvmsg_base(msg, bfd);
+
+	switch (msg_type) {
+	case IPAC_MSGT_ID_RESP:
+		DEBUGP(DMI, "ID_RESP ");
+		/* parse tags, search for Unit ID */
+		ipaccess_idtag_parse(&tlvp, (u_int8_t *)msg->l2h + 2,
+				 msgb_l2len(msg)-2);
+		DEBUGP(DMI, "\n");
+
+		if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT))
+			break;
+
+		/* lookup BTS, create sign_link, ... */
+		parse_unitid((char *)TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT),
+			     &site_id, &bts_id, &trx_id);
+		bts = find_bts_by_unitid(e1h->gsmnet, site_id, bts_id);
+		if (!bts) {
+			LOGP(DINP, LOGL_ERROR, "Unable to find BTS configuration for "
+			       " %u/%u/%u, disconnecting\n", site_id, bts_id,
+				trx_id);
+			return -EIO;
+		}
+		DEBUGP(DINP, "Identified BTS %u/%u/%u\n", site_id, bts_id, trx_id);
+		if (bfd->priv_nr == PRIV_OML) {
+			/* drop any old oml connection */
+			ipaccess_drop_oml(bts);
+			bts->oml_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1],
+						  E1INP_SIGN_OML, bts->c0,
+						  bts->oml_tei, 0);
+		} else if (bfd->priv_nr == PRIV_RSL) {
+			struct e1inp_ts *e1i_ts;
+			struct bsc_fd *newbfd;
+			struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_id);
+
+			/* drop any old rsl connection */
+			ipaccess_drop_rsl(trx);
+
+			if (!bts->oml_link) {
+				bsc_unregister_fd(bfd);
+				close(bfd->fd);
+				bfd->fd = -1;
+				talloc_free(bfd);
+				return 0;
+			}
+
+			bfd->data = line = bts->oml_link->ts->line;
+			e1i_ts = &line->ts[PRIV_RSL + trx_id - 1];
+			newbfd = &e1i_ts->driver.ipaccess.fd;
+			e1inp_ts_config(e1i_ts, line, E1INP_TS_TYPE_SIGN);
+
+			trx->rsl_link = e1inp_sign_link_create(e1i_ts,
+							E1INP_SIGN_RSL, trx,
+							trx->rsl_tei, 0);
+
+			/* get rid of our old temporary bfd */
+			memcpy(newbfd, bfd, sizeof(*newbfd));
+			newbfd->priv_nr = PRIV_RSL + trx_id;
+			bsc_unregister_fd(bfd);
+			bfd->fd = -1;
+			talloc_free(bfd);
+			bsc_register_fd(newbfd);
+		}
+		break;
+	}
+	return 0;
+}
+
+#define OML_UP		0x0001
+#define RSL_UP		0x0002
+
+/*
+ * read one ipa message from the socket
+ * return NULL in case of error
+ */
+struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error)
+{
+	struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "Abis/IP");
+	struct ipaccess_head *hh;
+	int len, ret = 0;
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	/* first read our 3-byte header */
+	hh = (struct ipaccess_head *) msg->data;
+	ret = recv(bfd->fd, msg->data, sizeof(*hh), 0);
+	if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	} else if (ret != sizeof(*hh)) {
+		if (errno != EAGAIN)
+			LOGP(DINP, LOGL_ERROR, "recv error %d %s\n", ret, strerror(errno));
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msgb_put(msg, ret);
+
+	/* then read te length as specified in header */
+	msg->l2h = msg->data + sizeof(*hh);
+	len = ntohs(hh->len);
+
+	if (len < 0 || TS1_ALLOC_SIZE < len + sizeof(*hh)) {
+		LOGP(DINP, LOGL_ERROR, "Can not read this packet. %d avail\n", len);
+		msgb_free(msg);
+		*error = -EIO;
+		return NULL;
+	}
+
+	ret = recv(bfd->fd, msg->l2h, len, 0);
+	if (ret < len) {
+		LOGP(DINP, LOGL_ERROR, "short read! Got %d from %d\n", ret, len);
+		msgb_free(msg);
+		*error = -EIO;
+		return NULL;
+	}
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+int ipaccess_drop_oml(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	struct e1inp_ts *ts;
+	struct e1inp_line *line;
+	struct bsc_fd *bfd;
+
+	if (!bts || !bts->oml_link)
+		return -1;
+
+	/* send OML down */
+	ts = bts->oml_link->ts;
+	line = ts->line;
+	e1inp_event(ts, EVT_E1_TEI_DN, bts->oml_link->tei, bts->oml_link->sapi);
+
+	bfd = &ts->driver.ipaccess.fd;
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	/* clean up OML and RSL */
+	e1inp_sign_link_destroy(bts->oml_link);
+	bts->oml_link = NULL;
+	bts->ip_access.flags = 0;
+
+	/* drop all RSL connections too */
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		ipaccess_drop_rsl(trx);
+
+	/* kill the E1 line now... as we have no one left to use it */
+	talloc_free(line);
+
+	return -1;
+}
+
+static int ipaccess_drop(struct e1inp_ts *ts, struct bsc_fd *bfd)
+{
+	struct e1inp_sign_link *link;
+	int bts_nr;
+
+	if (!ts) {
+		/*
+		 * If we don't have a TS this means that this is a RSL
+		 * connection but we are not past the authentication
+		 * handling yet. So we can safely delete this bfd and
+		 * wait for a reconnect.
+		 */
+		bsc_unregister_fd(bfd);
+		close(bfd->fd);
+		bfd->fd = -1;
+		talloc_free(bfd);
+		return -1;
+	}
+
+	/* attempt to find a signalling link */
+	if (ts->type == E1INP_TS_TYPE_SIGN) {
+		llist_for_each_entry(link, &ts->sign.sign_links, list) {
+			bts_nr = link->trx->bts->bts_nr;
+			/* we have issues just reconnecting RLS so we drop OML */
+			ipaccess_drop_oml(link->trx->bts);
+			return bts_nr;
+		}
+	}
+
+	/* error case */
+	LOGP(DINP, LOGL_ERROR, "Failed to find a signalling link for ts: %p\n", ts);
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+	return -1;
+}
+
+int ipaccess_drop_rsl(struct gsm_bts_trx *trx)
+{
+	struct bsc_fd *bfd;
+	struct e1inp_ts *ts;
+
+	if (!trx || !trx->rsl_link)
+		return -1;
+
+	/* send RSL down */
+	ts = trx->rsl_link->ts;
+	e1inp_event(ts, EVT_E1_TEI_DN, trx->rsl_link->tei, trx->rsl_link->sapi);
+
+	/* close the socket */
+	bfd = &ts->driver.ipaccess.fd;
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	/* destroy */
+	e1inp_sign_link_destroy(trx->rsl_link);
+	trx->rsl_link = NULL;
+
+	return -1;
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *link;
+	struct msgb *msg;
+	struct ipaccess_head *hh;
+	int ret = 0, error;
+
+	msg = ipaccess_read_msg(bfd, &error);
+	if (!msg) {
+		if (error == 0) {
+			int ret = ipaccess_drop(e1i_ts, bfd);
+			if (ret >= 0)
+				LOGP(DINP, LOGL_NOTICE, "BTS %u disappeared, dead socket\n",
+					ret);
+			else
+				LOGP(DINP, LOGL_NOTICE, "unknown BTS disappeared, dead socket\n");
+		}
+		return error;
+	}
+
+	DEBUGP(DMI, "RX %u: %s\n", ts_nr, hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+	hh = (struct ipaccess_head *) msg->data;
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		ret = ipaccess_rcvmsg(line, msg, bfd);
+		if (ret < 0)
+			ipaccess_drop(e1i_ts, bfd);
+		msgb_free(msg);
+		return ret;
+	}
+	/* BIG FAT WARNING: bfd might no longer exist here, since ipaccess_rcvmsg()
+	 * might have free'd it !!! */
+
+	link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0);
+	if (!link) {
+		LOGP(DINP, LOGL_ERROR, "no matching signalling link for "
+			"hh->proto=0x%02x\n", hh->proto);
+		msgb_free(msg);
+		return -EIO;
+	}
+	msg->trx = link->trx;
+
+	switch (link->type) {
+	case E1INP_SIGN_RSL:
+		if (!(msg->trx->bts->ip_access.flags & (RSL_UP << msg->trx->nr))) {
+			e1inp_event(e1i_ts, EVT_E1_TEI_UP, link->tei, link->sapi);
+			msg->trx->bts->ip_access.flags |= (RSL_UP << msg->trx->nr);
+		}
+		ret = abis_rsl_rcvmsg(msg);
+		break;
+	case E1INP_SIGN_OML:
+		if (!(msg->trx->bts->ip_access.flags & OML_UP)) {
+			e1inp_event(e1i_ts, EVT_E1_TEI_UP, link->tei, link->sapi);
+			msg->trx->bts->ip_access.flags |= OML_UP;
+		}
+		ret = abis_nm_rcvmsg(msg);
+		break;
+	default:
+		LOGP(DINP, LOGL_NOTICE, "Unknown IP.access protocol proto=0x%02x\n", hh->proto);
+		msgb_free(msg);
+		break;
+	}
+	return ret;
+}
+
+void ipaccess_prepend_header(struct msgb *msg, int proto)
+{
+	struct ipaccess_head *hh;
+
+	/* prepend the ip.access header */
+	hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh));
+	hh->len = htons(msg->len - sizeof(*hh));
+	hh->proto = proto;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+	e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+	struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+	/* trigger write of ts1, due to tx delay timer */
+	ts_want_write(e1i_ts);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *sign_link;
+	struct msgb *msg;
+	u_int8_t proto;
+	int ret;
+
+	bfd->when &= ~BSC_FD_WRITE;
+
+	/* get the next msg for this timeslot */
+	msg = e1inp_tx_ts(e1i_ts, &sign_link);
+	if (!msg) {
+		/* no message after tx delay timer */
+		return 0;
+	}
+
+	switch (sign_link->type) {
+	case E1INP_SIGN_OML:
+		proto = IPAC_PROTO_OML;
+		break;
+	case E1INP_SIGN_RSL:
+		proto = IPAC_PROTO_RSL;
+		break;
+	default:
+		msgb_free(msg);
+		bfd->when |= BSC_FD_WRITE; /* come back for more msg */
+		return -EINVAL;
+	}
+
+	msg->l2h = msg->data;
+	ipaccess_prepend_header(msg, sign_link->tei);
+
+	DEBUGP(DMI, "TX %u: %s\n", ts_nr, hexdump(msg->l2h, msgb_l2len(msg)));
+
+	ret = send(bfd->fd, msg->data, msg->len, 0);
+	msgb_free(msg);
+
+	/* set tx delay timer for next event */
+	e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+	e1i_ts->sign.tx_timer.data = e1i_ts;
+
+	/* Reducing this might break the nanoBTS 900 init. */
+	bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, 100000);
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int ipaccess_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	unsigned int idx = ts_nr-1;
+	struct e1inp_ts *e1i_ts;
+	int rc = 0;
+
+	/* In case of early RSL we might not yet have a line */
+
+	if (line)
+ 		e1i_ts = &line->ts[idx];
+
+	if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) {
+		if (what & BSC_FD_READ)
+			rc = handle_ts1_read(bfd);
+		if (what & BSC_FD_WRITE)
+			rc = handle_ts1_write(bfd);
+	} else
+		LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type);
+
+	return rc;
+}
+
+struct e1inp_driver ipaccess_driver = {
+	.name = "ip.access",
+	.want_write = ts_want_write,
+};
+
+/* callback of the OML listening filedescriptor */
+static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+	int ret;
+	int idx = 0;
+	int i;
+	struct e1inp_line *line;
+	struct e1inp_ts *e1i_ts;
+	struct bsc_fd *bfd;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (ret < 0) {
+		perror("accept");
+		return ret;
+	}
+	LOGP(DINP, LOGL_NOTICE, "accept()ed new OML link from %s\n",
+		inet_ntoa(sa.sin_addr));
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line) {
+		close(ret);
+		return -ENOMEM;
+	}
+	line->driver = &ipaccess_driver;
+	//line->driver_data = e1h;
+	/* create virrtual E1 timeslots for signalling */
+	e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN);
+
+	/* initialize the fds */
+	for (i = 0; i < ARRAY_SIZE(line->ts); ++i)
+		line->ts[i].driver.ipaccess.fd.fd = -1;
+
+	e1i_ts = &line->ts[idx];
+
+	bfd = &e1i_ts->driver.ipaccess.fd;
+	bfd->fd = ret;
+	bfd->data = line;
+	bfd->priv_nr = PRIV_OML;
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ;
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+		close(bfd->fd);
+		talloc_free(line);
+		return ret;
+	}
+
+	/* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+	ret = ipaccess_send_id_req(bfd->fd);
+
+        return ret;
+	//return e1inp_line_register(line);
+}
+
+static int rsl_listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+	struct bsc_fd *bfd;
+	int ret;
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	bfd = talloc_zero(tall_bsc_ctx, struct bsc_fd);
+	if (!bfd)
+		return -ENOMEM;
+
+	/* Some BTS has connected to us, but we don't know yet which line
+	 * (as created by the OML link) to associate it with.  Thus, we
+	 * allocate a temporary bfd until we have received ID from BTS */
+
+	bfd->fd = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (bfd->fd < 0) {
+		perror("accept");
+		return bfd->fd;
+	}
+	LOGP(DINP, LOGL_NOTICE, "accept()ed new RSL link from %s\n", inet_ntoa(sa.sin_addr));
+	bfd->priv_nr = PRIV_RSL;
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ;
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+		close(bfd->fd);
+		talloc_free(bfd);
+		return ret;
+	}
+	/* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+	ret = write(bfd->fd, id_req, sizeof(id_req));
+
+	return 0;
+}
+
+/* Actively connect to a BTS.  Currently used by ipaccess-config.c */
+int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa)
+{
+	struct e1inp_ts *e1i_ts = &line->ts[0];
+	struct bsc_fd *bfd = &e1i_ts->driver.ipaccess.fd;
+	int ret, on = 1;
+
+	bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd->data = line;
+	bfd->priv_nr = PRIV_OML;
+
+	if (bfd->fd < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not create TCP socket.\n");
+		return -EIO;
+	}
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not connect socket\n");
+		close(bfd->fd);
+		return ret;
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		close(bfd->fd);
+		return ret;
+	}
+	
+	line->driver = &ipaccess_driver;
+
+        return ret;
+	//return e1inp_line_register(line);
+}
+
+int ipaccess_setup(struct gsm_network *gsmnet)
+{
+	int ret;
+
+	/* register the driver with the core */
+	/* FIXME: do this in the plugin initializer function */
+	ret = e1inp_driver_register(&ipaccess_driver);
+	if (ret)
+		return ret;
+
+	e1h = talloc_zero(tall_bsc_ctx, struct ia_e1_handle);
+	if (!e1h)
+		return -ENOMEM;
+
+	e1h->gsmnet = gsmnet;
+
+	/* Listen for OML connections */
+	ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, IPA_TCP_PORT_OML,
+			listen_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	/* Listen for RSL connections */
+	ret = make_sock(&e1h->rsl_listen_fd, IPPROTO_TCP,
+			IPA_TCP_PORT_RSL, rsl_listen_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
diff --git a/openbsc/src/input/misdn.c b/openbsc/src/input/misdn.c
new file mode 100644
index 0000000..83b01f2
--- /dev/null
+++ b/openbsc/src/input/misdn.c
@@ -0,0 +1,536 @@
+/* OpenBSC Abis input driver for mISDNuser */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <mISDNif.h>
+
+//#define AF_COMPATIBILITY_FUNC
+//#include <compat_af_isdn.h>
+#ifndef AF_ISDN
+#define AF_ISDN 34
+#define PF_ISDN AF_ISDN
+#endif
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <osmocore/talloc.h>
+
+#define TS1_ALLOC_SIZE	300
+
+struct prim_name {
+	unsigned int prim;
+	const char *name;
+};
+
+const struct prim_name prim_names[] = {
+	{ PH_CONTROL_IND, "PH_CONTROL_IND" },
+	{ PH_DATA_IND, "PH_DATA_IND" },
+	{ PH_DATA_CNF, "PH_DATA_CNF" },
+	{ PH_ACTIVATE_IND, "PH_ACTIVATE_IND" },
+	{ DL_ESTABLISH_IND, "DL_ESTABLISH_IND" },
+	{ DL_ESTABLISH_CNF, "DL_ESTABLISH_CNF" },
+	{ DL_RELEASE_IND, "DL_RELEASE_IND" },
+	{ DL_RELEASE_CNF, "DL_RELEASE_CNF" },
+	{ DL_DATA_IND, "DL_DATA_IND" },
+	{ DL_UNITDATA_IND, "DL_UNITDATA_IND" },
+	{ DL_INFORMATION_IND, "DL_INFORMATION_IND" },
+	{ MPH_ACTIVATE_IND, "MPH_ACTIVATE_IND" },
+	{ MPH_DEACTIVATE_IND, "MPH_DEACTIVATE_IND" },
+};
+
+const char *get_prim_name(unsigned int prim)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(prim_names); i++) {
+		if (prim_names[i].prim == prim)
+			return prim_names[i].name;
+	}
+
+	return "UNKNOWN";
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *link;
+	struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "mISDN TS1");
+	struct sockaddr_mISDN l2addr;
+	struct mISDNhead *hh;
+	socklen_t alen;
+	int ret;
+
+	if (!msg)
+		return -ENOMEM;
+
+	hh = (struct mISDNhead *) msg->data;
+
+	alen = sizeof(l2addr);
+	ret = recvfrom(bfd->fd, msg->data, 300, 0,
+		       (struct sockaddr *) &l2addr, &alen);
+	if (ret < 0) {
+		fprintf(stderr, "recvfrom error  %s\n", strerror(errno));
+		return ret;
+	}
+
+	if (alen != sizeof(l2addr)) {
+		fprintf(stderr, "%s error len\n", __func__);
+		return -EINVAL;
+	}
+
+	msgb_put(msg, ret);
+
+	DEBUGP(DMI, "alen =%d, dev(%d) channel(%d) sapi(%d) tei(%d)\n",
+		alen, l2addr.dev, l2addr.channel, l2addr.sapi, l2addr.tei);
+
+	DEBUGP(DMI, "<= len = %d, prim(0x%x) id(0x%x): %s\n",
+		ret, hh->prim, hh->id, get_prim_name(hh->prim));
+
+	switch (hh->prim) {
+	case DL_INFORMATION_IND:
+		/* mISDN tells us which channel number is allocated for this
+		 * tuple of (SAPI, TEI). */
+		DEBUGP(DMI, "DL_INFORMATION_IND: use channel(%d) sapi(%d) tei(%d) for now\n",
+			l2addr.channel, l2addr.sapi, l2addr.tei);
+		link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi);
+		if (!link) {
+			DEBUGPC(DMI, "mISDN message for unknown sign_link\n");
+			msgb_free(msg);
+			return -EINVAL;
+		}
+		/* save the channel number in the driver private struct */
+		link->driver.misdn.channel = l2addr.channel;
+		break;
+	case DL_ESTABLISH_IND:
+		DEBUGP(DMI, "DL_ESTABLISH_IND: channel(%d) sapi(%d) tei(%d)\n",
+		l2addr.channel, l2addr.sapi, l2addr.tei);
+		ret = e1inp_event(e1i_ts, EVT_E1_TEI_UP, l2addr.tei, l2addr.sapi);
+		break;
+	case DL_RELEASE_IND:
+		DEBUGP(DMI, "DL_RELEASE_IND: channel(%d) sapi(%d) tei(%d)\n",
+		l2addr.channel, l2addr.sapi, l2addr.tei);
+		ret = e1inp_event(e1i_ts, EVT_E1_TEI_DN, l2addr.tei, l2addr.sapi);
+		break;
+	case DL_DATA_IND:
+	case DL_UNITDATA_IND:
+		msg->l2h = msg->data + MISDN_HEADER_LEN;
+		DEBUGP(DMI, "RX: %s\n", hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN));
+		ret = e1inp_rx_ts(e1i_ts, msg, l2addr.tei, l2addr.sapi);
+		break;
+	case PH_ACTIVATE_IND:
+		DEBUGP(DMI, "PH_ACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n",
+		l2addr.channel, l2addr.sapi, l2addr.tei);
+		break;
+	case PH_DEACTIVATE_IND:
+		DEBUGP(DMI, "PH_DEACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n",
+		l2addr.channel, l2addr.sapi, l2addr.tei);
+		break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+	/* We never include the mISDN B-Channel FD into the
+	 * writeset, since it doesn't support poll() based
+	 * write flow control */		
+	if (e1i_ts->type == E1INP_TS_TYPE_TRAU)
+		return 0;
+
+	e1i_ts->driver.misdn.fd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+	struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+	/* trigger write of ts1, due to tx delay timer */
+	ts_want_write(e1i_ts);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *sign_link;
+	struct sockaddr_mISDN sa;
+	struct msgb *msg;
+	struct mISDNhead *hh;
+	u_int8_t *l2_data;
+	int ret;
+
+	bfd->when &= ~BSC_FD_WRITE;
+
+	/* get the next msg for this timeslot */
+	msg = e1inp_tx_ts(e1i_ts, &sign_link);
+	if (!msg) {
+		/* no message after tx delay timer */
+		return 0;
+	}
+
+	l2_data = msg->data;
+
+	/* prepend the mISDNhead */
+	hh = (struct mISDNhead *) msgb_push(msg, sizeof(*hh));
+	hh->prim = DL_DATA_REQ;
+
+	DEBUGP(DMI, "TX TEI(%d) SAPI(%d): %s\n", sign_link->tei,
+		sign_link->sapi, hexdump(l2_data, msg->len - MISDN_HEADER_LEN));
+
+	/* construct the sockaddr */
+	sa.family = AF_ISDN;
+	sa.sapi = sign_link->sapi;
+	sa.dev = sign_link->tei;
+	sa.channel = sign_link->driver.misdn.channel;
+
+	ret = sendto(bfd->fd, msg->data, msg->len, 0,
+		     (struct sockaddr *)&sa, sizeof(sa));
+	if (ret < 0)
+		fprintf(stderr, "%s sendto failed %d\n", __func__, ret);
+	msgb_free(msg);
+
+	/* set tx delay timer for next event */
+	e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+	e1i_ts->sign.tx_timer.data = e1i_ts;
+	bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, 50000);
+
+	return ret;
+}
+
+#define BCHAN_TX_GRAN	160
+/* write to a B channel TS */
+static int handle_tsX_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct mISDNhead *hh;
+	u_int8_t tx_buf[BCHAN_TX_GRAN + sizeof(*hh)];
+	struct subch_mux *mx = &e1i_ts->trau.mux;
+	int ret;
+
+	hh = (struct mISDNhead *) tx_buf;
+	hh->prim = PH_DATA_REQ;
+
+	subchan_mux_out(mx, tx_buf+sizeof(*hh), BCHAN_TX_GRAN);
+
+	DEBUGP(DMIB, "BCHAN TX: %s\n",
+		hexdump(tx_buf+sizeof(*hh), BCHAN_TX_GRAN));
+
+	ret = send(bfd->fd, tx_buf, sizeof(*hh) + BCHAN_TX_GRAN, 0);
+	if (ret < sizeof(*hh) + BCHAN_TX_GRAN)
+		DEBUGP(DMIB, "send returns %d instead of %zu\n", ret,
+			sizeof(*hh) + BCHAN_TX_GRAN);
+
+	return ret;
+}
+
+#define TSX_ALLOC_SIZE 4096
+/* FIXME: read from a B channel TS */
+static int handle_tsX_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct msgb *msg = msgb_alloc(TSX_ALLOC_SIZE, "mISDN TSx");
+	struct mISDNhead *hh;
+	int ret;
+
+	if (!msg)
+		return -ENOMEM;
+
+	hh = (struct mISDNhead *) msg->data;
+
+	ret = recv(bfd->fd, msg->data, TSX_ALLOC_SIZE, 0);
+	if (ret < 0) {
+		fprintf(stderr, "recvfrom error  %s\n", strerror(errno));
+		return ret;
+	}
+
+	msgb_put(msg, ret);
+
+	if (hh->prim != PH_CONTROL_IND)
+		DEBUGP(DMIB, "<= BCHAN len = %d, prim(0x%x) id(0x%x): %s\n",
+			ret, hh->prim, hh->id, get_prim_name(hh->prim));
+
+	switch (hh->prim) {
+	case PH_DATA_IND:
+		msg->l2h = msg->data + MISDN_HEADER_LEN;
+		DEBUGP(DMIB, "BCHAN RX: %s\n",
+			hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN));
+		ret = e1inp_rx_ts(e1i_ts, msg, 0, 0);
+		break;
+	case PH_ACTIVATE_IND:
+	case PH_DATA_CNF:
+		/* physical layer indicates that data has been sent,
+		 * we thus can send some more data */
+		ret = handle_tsX_write(bfd);
+	default:
+		break;
+	}
+	/* FIXME: why do we free signalling msgs in the caller, and trau not? */
+	msgb_free(msg);
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int misdn_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	unsigned int idx = ts_nr-1;
+	struct e1inp_ts *e1i_ts = &line->ts[idx];
+	int rc = 0;
+
+	switch (e1i_ts->type) {
+	case E1INP_TS_TYPE_SIGN:
+		if (what & BSC_FD_READ)
+			rc = handle_ts1_read(bfd);
+		if (what & BSC_FD_WRITE)
+			rc = handle_ts1_write(bfd);
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		if (what & BSC_FD_READ)
+			rc = handle_tsX_read(bfd);
+		/* We never include the mISDN B-Channel FD into the
+		 * writeset, since it doesn't support poll() based
+		 * write flow control */		
+		break;
+	default:
+		fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type);
+		break;
+	}
+
+	return rc;
+}
+
+static int activate_bchan(struct e1inp_line *line, int ts, int act)
+{
+	struct mISDNhead hh;
+	int ret;
+	unsigned int idx = ts-1;
+	struct e1inp_ts *e1i_ts = &line->ts[idx];
+	struct bsc_fd *bfd = &e1i_ts->driver.misdn.fd;
+
+	fprintf(stdout, "activate bchan\n");
+	if (act)
+		hh.prim = PH_ACTIVATE_REQ;
+	else
+		hh.prim = PH_DEACTIVATE_REQ;
+
+	hh.id = MISDN_ID_ANY;
+	ret = sendto(bfd->fd, &hh, sizeof(hh), 0, NULL, 0);
+	if (ret < 0) {
+		fprintf(stdout, "could not send ACTIVATE_RQ %s\n",
+			strerror(errno));
+	}
+
+	return ret;
+}
+
+struct e1inp_driver misdn_driver = {
+	.name = "mISDNuser",
+	.want_write = ts_want_write,
+};
+
+static int mi_e1_setup(struct e1inp_line *line, int release_l2)
+{
+	int ts, ret;
+
+	/* TS0 is CRC4, don't need any fd for it */
+	for (ts = 1; ts < NUM_E1_TS; ts++) {
+		unsigned int idx = ts-1;
+		struct e1inp_ts *e1i_ts = &line->ts[idx];
+		struct bsc_fd *bfd = &e1i_ts->driver.misdn.fd;
+		struct sockaddr_mISDN addr;
+
+		bfd->data = line;
+		bfd->priv_nr = ts;
+		bfd->cb = misdn_fd_cb;
+
+		switch (e1i_ts->type) {
+		case E1INP_TS_TYPE_NONE:
+			continue;
+			break;
+		case E1INP_TS_TYPE_SIGN:
+			bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_NT);
+			bfd->when = BSC_FD_READ;
+			break;
+		case E1INP_TS_TYPE_TRAU:
+			bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_B_RAW);
+			/* We never include the mISDN B-Channel FD into the
+	 		* writeset, since it doesn't support poll() based
+	 		* write flow control */		
+			bfd->when = BSC_FD_READ;
+			break;
+		}
+
+		if (bfd->fd < 0) {
+			fprintf(stderr, "%s could not open socket %s\n",
+				__func__, strerror(errno));
+			return bfd->fd;
+		}
+
+		memset(&addr, 0, sizeof(addr));
+		addr.family = AF_ISDN;
+		addr.dev = line->num;
+		switch (e1i_ts->type) {
+		case E1INP_TS_TYPE_SIGN:
+			addr.channel = 0;
+			/* SAPI not supported yet in kernel */
+			//addr.sapi = e1inp_ts->sign.sapi;
+			addr.sapi = 0;
+			addr.tei = GROUP_TEI;
+			break;
+		case E1INP_TS_TYPE_TRAU:
+			addr.channel = ts;
+			break;
+		default:
+			DEBUGP(DMI, "unsupported E1 TS type: %u\n",
+				e1i_ts->type);
+			break;
+		}
+
+		ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+		if (ret < 0) {
+			fprintf(stderr, "could not bind l2 socket %s\n",
+				strerror(errno));
+			return -EIO;
+		}
+
+		if (e1i_ts->type == E1INP_TS_TYPE_SIGN) {
+			ret = ioctl(bfd->fd, IMCLEAR_L2, &release_l2);
+			if (ret < 0) {
+				fprintf(stderr, "could not send IOCTL IMCLEAN_L2 %s\n", strerror(errno));
+				return -EIO;
+			}
+		}
+
+		/* FIXME: only activate B-Channels once we start to
+		 * use them to conserve CPU power */
+		if (e1i_ts->type == E1INP_TS_TYPE_TRAU)
+			activate_bchan(line, ts, 1);
+
+		ret = bsc_register_fd(bfd);
+		if (ret < 0) {
+			fprintf(stderr, "could not register FD: %s\n",
+				strerror(ret));
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+int mi_e1_line_update(struct e1inp_line *line)
+{
+	struct mISDN_devinfo devinfo;
+	int sk, ret, cnt;
+
+	if (!line->driver) {
+		/* this must be the first update */
+		line->driver = &misdn_driver;
+	} else {
+		/* this is a subsequent update */
+		/* FIXME: first close all sockets */
+		fprintf(stderr, "incremental line updates not supported yet\n");
+		return 0;
+	}
+
+	if (line->driver != &misdn_driver)
+		return -EINVAL;
+
+	/* open the ISDN card device */
+	sk = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (sk < 0) {
+		fprintf(stderr, "%s could not open socket %s\n",
+			__func__, strerror(errno));
+		return sk;
+	}
+
+	ret = ioctl(sk, IMGETCOUNT, &cnt);
+	if (ret) {
+		fprintf(stderr, "%s error getting interf count: %s\n",
+			__func__, strerror(errno));
+		close(sk);
+		return -ENODEV;
+	}
+	//DEBUGP(DMI,"%d device%s found\n", cnt, (cnt==1)?"":"s");
+	printf("%d device%s found\n", cnt, (cnt==1)?"":"s");
+#if 1
+	devinfo.id = line->num;
+	ret = ioctl(sk, IMGETDEVINFO, &devinfo);
+	if (ret < 0) {
+		fprintf(stdout, "error getting info for device %d: %s\n",
+			line->num, strerror(errno));
+		return -ENODEV;
+	}
+	fprintf(stdout, "        id:             %d\n", devinfo.id);
+	fprintf(stdout, "        Dprotocols:     %08x\n", devinfo.Dprotocols);
+	fprintf(stdout, "        Bprotocols:     %08x\n", devinfo.Bprotocols);
+	fprintf(stdout, "        protocol:       %d\n", devinfo.protocol);
+	fprintf(stdout, "        nrbchan:        %d\n", devinfo.nrbchan);
+	fprintf(stdout, "        name:           %s\n", devinfo.name);
+#endif
+
+	if (!(devinfo.Dprotocols & (1 << ISDN_P_NT_E1))) {
+		fprintf(stderr, "error: card is not of type E1 (NT-mode)\n");
+		return -EINVAL;
+	}
+
+	ret = mi_e1_setup(line, 1);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_sms(void)
+{
+	/* register the driver with the core */
+	e1inp_driver_register(&misdn_driver);
+}
diff --git a/openbsc/src/ipaccess/Makefile.am b/openbsc/src/ipaccess/Makefile.am
new file mode 100644
index 0000000..5339321
--- /dev/null
+++ b/openbsc/src/ipaccess/Makefile.am
@@ -0,0 +1,13 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS)
+
+sbin_PROGRAMS = ipaccess-find ipaccess-config ipaccess-proxy
+
+ipaccess_find_SOURCES = ipaccess-find.c
+
+ipaccess_config_SOURCES = ipaccess-config.c ipaccess-firmware.c
+ipaccess_config_LDADD = $(top_builddir)/src/libbsc.a $(top_builddir)/src/libmsc.a \
+			$(top_builddir)/src/libbsc.a $(top_builddir)/src/libvty.a -ldl -ldbi $(LIBCRYPT)
+
+ipaccess_proxy_SOURCES = ipaccess-proxy.c ../debug.c
diff --git a/openbsc/src/ipaccess/ipaccess-config.c b/openbsc/src/ipaccess/ipaccess-config.c
new file mode 100644
index 0000000..670e3f1
--- /dev/null
+++ b/openbsc/src/ipaccess/ipaccess-config.c
@@ -0,0 +1,735 @@
+/* ip.access nanoBTS configuration tool */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther
+ * (C) 2009 by On Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocore/select.h>
+#include <osmocore/timer.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+
+static struct gsm_network *gsmnet;
+
+static int net_listen_testnr;
+static int restart;
+static char *prim_oml_ip;
+static char *unit_id;
+static u_int16_t nv_flags;
+static u_int16_t nv_mask;
+static char *software = NULL;
+static int sw_load_state = 0;
+static int oml_state = 0;
+static int dump_files = 0;
+static char *firmware_analysis = NULL;
+
+struct sw_load {
+	u_int8_t file_id[255];
+	u_int8_t file_id_len;
+
+	u_int8_t file_version[255];
+	u_int8_t file_version_len;
+};
+
+static void *tall_ctx_config = NULL;
+static struct sw_load *sw_load1 = NULL;
+static struct sw_load *sw_load2 = NULL;
+
+/*
+static u_int8_t prim_oml_attr[] = { 0x95, 0x00, 7, 0x88, 192, 168, 100, 11, 0x00, 0x00 };
+static u_int8_t unit_id_attr[] = { 0x91, 0x00, 9, '2', '3', '4', '2', '/' , '0', '/', '0', 0x00 };
+*/
+
+/*
+ * Callback function for NACK on the OML NM
+ *
+ * Currently we send the config requests but don't check the
+ * result. The nanoBTS will send us a NACK when we did something the
+ * BTS didn't like.
+ */
+static int ipacc_msg_nack(u_int8_t mt)
+{
+	fprintf(stderr, "Failure to set attribute. This seems fatal\n");
+	exit(-1);
+	return 0;
+}
+
+static int ipacc_msg_ack(u_int8_t mt, struct gsm_bts *bts)
+{
+	if (sw_load_state == 1) {
+		fprintf(stderr, "The new software is activaed.\n");
+
+		if (restart) {
+			abis_nm_ipaccess_restart(bts);
+		} else {
+			exit(0);
+		}
+	} else if (oml_state == 1) {
+		fprintf(stderr, "Set the primary OML IP.\n");
+		if (restart) {
+			abis_nm_ipaccess_restart(bts);
+		} else {
+			exit(0);
+		}
+	}
+
+	return 0;
+}
+
+struct ipacc_ferr_elem {
+	int16_t freq_err;
+	u_int8_t freq_qual;
+	u_int8_t arfcn;
+} __attribute__((packed));
+
+struct ipacc_cusage_elem {
+	u_int16_t arfcn:10,
+		  rxlev:6;
+} __attribute__ ((packed));
+
+static int test_rep(void *_msg)
+{
+	struct msgb *msg = _msg;
+	struct abis_om_fom_hdr *foh = msgb_l3(msg);
+	u_int16_t test_rep_len, ferr_list_len;
+	struct ipacc_ferr_elem *ife;
+	struct ipac_bcch_info binfo;
+	int i, rc;
+
+	DEBUGP(DNM, "TEST REPORT: ");
+
+	if (foh->data[0] != NM_ATT_TEST_NO ||
+	    foh->data[2] != NM_ATT_TEST_REPORT)
+		return -EINVAL;
+
+	DEBUGPC(DNM, "test_no=0x%02x ", foh->data[1]);
+	/* data[2] == NM_ATT_TEST_REPORT */
+	/* data[3..4]: test_rep_len */
+	test_rep_len = ntohs(*(u_int16_t *) &foh->data[3]);
+	/* data[5]: ip.access test result */
+	DEBUGPC(DNM, "test_res=%s\n", ipacc_testres_name(foh->data[5]));
+
+	/* data[6]: ip.access nested IE. 3 == freq_err_list */
+	switch (foh->data[6]) {
+	case NM_IPAC_EIE_FREQ_ERR_LIST:
+		/* data[7..8]: length of ferr_list */
+		ferr_list_len = ntohs(*(u_int16_t *) &foh->data[7]);
+
+		/* data[9...]: frequency error list elements */
+		for (i = 0; i < ferr_list_len; i+= sizeof(*ife)) {
+			ife = (struct ipacc_ferr_elem *) (foh->data + 9 + i);
+			DEBUGP(DNM, "==> ARFCN %4u, Frequency Error %6hd\n",
+			ife->arfcn, ntohs(ife->freq_err));
+		}
+		break;
+	case NM_IPAC_EIE_CHAN_USE_LIST:
+		/* data[7..8]: length of ferr_list */
+		ferr_list_len = ntohs(*(u_int16_t *) &foh->data[7]);
+
+		/* data[9...]: channel usage list elements */
+		for (i = 0; i < ferr_list_len; i+= 2) {
+			u_int16_t *cu_ptr = (u_int16_t *)(foh->data + 9 + i);
+			u_int16_t cu = ntohs(*cu_ptr);
+			DEBUGP(DNM, "==> ARFCN %4u, RxLev %2u\n",
+				cu & 0x3ff, cu >> 10);
+		}
+		break;
+	case NM_IPAC_EIE_BCCH_INFO_TYPE:
+		break;
+	case NM_IPAC_EIE_BCCH_INFO:
+		rc = ipac_parse_bcch_info(&binfo, foh->data+6);
+		if (rc < 0) {
+			DEBUGP(DNM, "BCCH Info parsing failed\n");
+			break;
+		}
+		DEBUGP(DNM, "==> ARFCN %u, RxLev %2u, RxQual %2u: %3d-%d, LAC %d CI %d\n",
+			binfo.arfcn, binfo.rx_lev, binfo.rx_qual,
+			binfo.cgi.mcc, binfo.cgi.mnc,
+			binfo.cgi.lac, binfo.cgi.ci);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	struct ipacc_ack_signal_data *ipacc_data;
+
+	switch (signal) {
+	case S_NM_IPACC_NACK:
+		ipacc_data = signal_data;
+		return ipacc_msg_nack(ipacc_data->msg_type);
+	case S_NM_IPACC_ACK:
+		ipacc_data = signal_data;
+		return ipacc_msg_ack(ipacc_data->msg_type, ipacc_data->bts);
+	case S_NM_TEST_REP:
+		return test_rep(signal_data);
+	case S_NM_IPACC_RESTART_ACK:
+		printf("The BTS has acked the restart. Exiting.\n");
+		exit(0);
+		break;
+	case S_NM_IPACC_RESTART_NACK:
+		printf("The BTS has nacked the restart. Exiting.\n");
+		exit(0);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* callback function passed to the ABIS OML code */
+static int percent;
+static int percent_old;
+static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *_msg,
+		       void *data, void *param)
+{
+	struct msgb *msg;
+	struct gsm_bts *bts;
+
+	if (hook != GSM_HOOK_NM_SWLOAD)
+		return 0;
+
+	bts = (struct gsm_bts *) data;
+
+	switch (event) {
+	case NM_MT_LOAD_INIT_ACK:
+		fprintf(stdout, "Software Load Initiate ACK\n");
+		break;
+	case NM_MT_LOAD_INIT_NACK:
+		fprintf(stderr, "ERROR: Software Load Initiate NACK\n");
+		exit(5);
+		break;
+	case NM_MT_LOAD_END_ACK:
+		fprintf(stderr, "LOAD END ACK...");
+		/* now make it the default */
+		sw_load_state = 1;
+
+		msg = msgb_alloc(1024, "sw: nvattr");
+		msg->l2h = msgb_put(msg, 3);
+		msg->l3h = &msg->l2h[3];
+
+		/* activate software */
+		if (sw_load1) {
+			msgb_v_put(msg, NM_ATT_SW_DESCR);
+			msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw_load1->file_id_len, sw_load1->file_id);
+			msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw_load1->file_version_len,
+					sw_load1->file_version);
+		}
+
+		if (sw_load2) {
+			msgb_v_put(msg, NM_ATT_SW_DESCR);
+			msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw_load2->file_id_len, sw_load2->file_id);
+			msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw_load2->file_version_len,
+					sw_load2->file_version);
+		}
+
+		/* fill in the data */
+		msg->l2h[0] = NM_ATT_IPACC_CUR_SW_CFG;
+		msg->l2h[1] = msgb_l3len(msg) >> 8;
+		msg->l2h[2] = msgb_l3len(msg) & 0xff;
+		printf("Foo l2h: %p l3h: %p... length l2: %u  l3: %u\n", msg->l2h, msg->l3h, msgb_l2len(msg), msgb_l3len(msg));
+		abis_nm_ipaccess_set_nvattr(bts->c0, msg->l2h, msgb_l2len(msg));
+		msgb_free(msg);
+		break;
+	case NM_MT_LOAD_END_NACK:
+		fprintf(stderr, "ERROR: Software Load End NACK\n");
+		exit(3);
+		break;
+	case NM_MT_ACTIVATE_SW_NACK:
+		fprintf(stderr, "ERROR: Activate Software NACK\n");
+		exit(4);
+		break;
+	case NM_MT_ACTIVATE_SW_ACK:
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+		percent = abis_nm_software_load_status(bts);
+		if (percent > percent_old)
+			printf("Software Download Progress: %d%%\n", percent);
+		percent_old = percent;
+		break;
+	case NM_MT_LOAD_ABORT:
+		fprintf(stderr, "ERROR: Load aborted by the BTS.\n");
+		exit(6);
+		break;
+	}
+	return 0;
+}
+
+static void bootstrap_om(struct gsm_bts *bts)
+{
+	int len;
+	static u_int8_t buf[1024];
+	u_int8_t *cur = buf;
+
+	printf("OML link established\n");
+
+	if (unit_id) {
+		len = strlen(unit_id);
+		if (len > sizeof(buf)-10)
+			return;
+		buf[0] = NM_ATT_IPACC_UNIT_ID;
+		buf[1] = (len+1) >> 8;
+		buf[2] = (len+1) & 0xff;
+		memcpy(buf+3, unit_id, len);
+		buf[3+len] = 0;
+		printf("setting Unit ID to '%s'\n", unit_id);
+		abis_nm_ipaccess_set_nvattr(bts->c0, buf, 3+len+1);
+	}
+	if (prim_oml_ip) {
+		struct in_addr ia;
+
+		if (!inet_aton(prim_oml_ip, &ia)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				prim_oml_ip);
+			return;
+		}
+
+		/* 0x88 + IP + port */
+		len = 1 + sizeof(ia) + 2;
+
+		*cur++ = NM_ATT_IPACC_PRIM_OML_CFG_LIST;
+		*cur++ = (len) >> 8;
+		*cur++ = (len) & 0xff;
+		*cur++ = 0x88;
+		memcpy(cur, &ia, sizeof(ia));
+		cur += sizeof(ia);
+		*cur++ = 0;
+		*cur++ = 0;
+		printf("setting primary OML link IP to '%s'\n", inet_ntoa(ia));
+		oml_state = 1;
+		abis_nm_ipaccess_set_nvattr(bts->c0, buf, 3+len);
+	}
+	if (nv_mask) {
+		len = 4;
+
+		*cur++ = NM_ATT_IPACC_NV_FLAGS;
+		*cur++ = (len) >> 8;
+		*cur++ = (len) & 0xff;
+		*cur++ = nv_flags & 0xff;
+		*cur++ = nv_mask & 0xff;
+		*cur++ = nv_flags >> 8;
+		*cur++ = nv_mask >> 8;
+		printf("setting NV Flags/Mask to 0x%04x/0x%04x\n",
+			nv_flags, nv_mask);
+		abis_nm_ipaccess_set_nvattr(bts->c0, buf, 3+len);
+	}
+
+	if (restart && !prim_oml_ip && !software) {
+		printf("restarting BTS\n");
+		abis_nm_ipaccess_restart(bts);
+	}
+
+}
+
+void input_event(int event, enum e1inp_sign_type type, struct gsm_bts_trx *trx)
+{
+	switch (event) {
+	case EVT_E1_TEI_UP:
+		switch (type) {
+		case E1INP_SIGN_OML:
+			bootstrap_om(trx->bts);
+			break;
+		case E1INP_SIGN_RSL:
+			/* FIXME */
+			break;
+		default:
+			break;
+		}
+		break;
+	case EVT_E1_TEI_DN:
+		fprintf(stderr, "Lost some E1 TEI link\n");
+		/* FIXME: deal with TEI or L1 link loss */
+		break;
+	default:
+		break;
+	}
+}
+
+int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj,
+		   struct gsm_nm_state *old_state, struct gsm_nm_state *new_state)
+{
+	if (evt == EVT_STATECHG_OPER &&
+	    obj_class == NM_OC_RADIO_CARRIER &&
+	    new_state->availability == 3) {
+		struct gsm_bts_trx *trx = obj;
+
+		if (net_listen_testnr) {
+			u_int8_t phys_config[] = { 0x02, 0x0a, 0x00, 0x01, 0x02 };
+			abis_nm_perform_test(trx->bts, 2, 0, 0, 0xff,
+					     net_listen_testnr, 1,
+					     phys_config, sizeof(phys_config));
+		} else if (software) {
+			int rc;
+			printf("Attempting software upload with '%s'\n", software);
+			rc = abis_nm_software_load(trx->bts, software, 19, 0, swload_cbfn, trx->bts);
+			if (rc < 0) {
+				fprintf(stderr, "Failed to start software load\n");
+				exit(-3);
+			}
+		}
+	}
+	return 0;
+}
+
+static struct sw_load *create_swload(struct sdp_header *header)
+{
+	struct sw_load *load;
+
+	load = talloc_zero(tall_ctx_config, struct sw_load);
+
+	strncpy((char *)load->file_id, header->firmware_info.sw_part, 20);
+	load->file_id_len = strlen(header->firmware_info.sw_part) + 1;
+
+	strncpy((char *)load->file_version, header->firmware_info.version, 20);
+	load->file_version_len = strlen(header->firmware_info.version) + 1;
+
+	return load;
+}
+
+static int find_sw_load_params(const char *filename)
+{
+	struct stat stat;
+	struct sdp_header *header;
+	struct llist_head *entry;
+	int fd;
+	void *tall_firm_ctx = 0;
+
+	entry = talloc_zero(tall_firm_ctx, struct llist_head);
+	INIT_LLIST_HEAD(entry);
+
+	fd = open(filename, O_RDONLY);
+	if (!fd) {
+		perror("nada");
+		return -1;
+	}
+
+	/* verify the file */
+	if (fstat(fd, &stat) == -1) {
+		perror("Can not stat the file");
+		return -1;
+	}
+
+	ipaccess_analyze_file(fd, stat.st_size, 0, entry);
+	if (close(fd) != 0) {
+		perror("Close failed.\n");
+		return -1;
+	}
+
+	/* try to find what we are looking for */
+	llist_for_each_entry(header, entry, entry) {
+		if (ntohs(header->firmware_info.more_more_magic) == 0x1000) {
+			sw_load1 = create_swload(header);
+		} else if (ntohs(header->firmware_info.more_more_magic) == 0x2001) {
+			sw_load2 = create_swload(header);
+		}
+	}
+
+	if (!sw_load1 || !sw_load2) {
+		fprintf(stderr, "Did not find data.\n");
+		talloc_free(tall_firm_ctx);
+		return -1;
+        }
+
+	talloc_free(tall_firm_ctx);
+	return 0;
+}
+
+static void dump_entry(struct sdp_header_item *sub_entry, int part, int fd)
+{
+	int out_fd;
+	int copied;
+	char filename[4096];
+	off_t target;
+
+	if (!dump_files)
+		return;
+
+	if (sub_entry->header_entry.something1 == 0)
+		return;
+
+	snprintf(filename, sizeof(filename), "part.%d", part++);
+	out_fd = open(filename, O_WRONLY | O_CREAT, 0660);
+	if (out_fd < 0) {
+		perror("Can not dump firmware");
+		return;
+	}
+
+	target = sub_entry->absolute_offset + ntohl(sub_entry->header_entry.start) + 4;
+	if (lseek(fd, target, SEEK_SET) != target) {
+		perror("seek failed");
+		close(out_fd);
+		return;
+	}
+
+	for (copied = 0; copied < ntohl(sub_entry->header_entry.length); ++copied) {
+		char c;
+		if (read(fd, &c, sizeof(c)) != sizeof(c)) {
+			perror("copy failed");
+			break;
+		}
+
+		if (write(out_fd, &c, sizeof(c)) != sizeof(c)) {
+			perror("write failed");
+			break;
+		}
+	}
+
+	close(out_fd);
+}
+
+static void analyze_firmware(const char *filename)
+{
+	struct stat stat;
+	struct sdp_header *header;
+	struct sdp_header_item *sub_entry;
+	struct llist_head *entry;
+	int fd;
+	void *tall_firm_ctx = 0;
+	int part = 0;
+
+	entry = talloc_zero(tall_firm_ctx, struct llist_head);
+	INIT_LLIST_HEAD(entry);
+
+	printf("Opening possible firmware '%s'\n", filename);
+	fd = open(filename, O_RDONLY);
+	if (!fd) {
+		perror("nada");
+		return;
+	}
+
+	/* verify the file */
+	if (fstat(fd, &stat) == -1) {
+		perror("Can not stat the file");
+		return;
+	}
+
+	ipaccess_analyze_file(fd, stat.st_size, 0, entry);
+
+	llist_for_each_entry(header, entry, entry) {
+		printf("Printing header information:\n");
+		printf("more_more_magic: 0x%x\n", ntohs(header->firmware_info.more_more_magic));
+		printf("header_length: %u\n", ntohl(header->firmware_info.header_length));
+		printf("file_length: %u\n", ntohl(header->firmware_info.file_length));
+		printf("sw_part: %.20s\n", header->firmware_info.sw_part);
+		printf("text1: %.64s\n", header->firmware_info.text1);
+		printf("time: %.12s\n", header->firmware_info.time);
+		printf("date: %.14s\n", header->firmware_info.date);
+		printf("text2: %.10s\n", header->firmware_info.text2);
+		printf("version: %.20s\n", header->firmware_info.version);
+		printf("subitems...\n");
+
+		llist_for_each_entry(sub_entry, &header->header_list, entry) {
+			printf("\tsomething1: %u\n", sub_entry->header_entry.something1);
+			printf("\ttext1: %.64s\n", sub_entry->header_entry.text1);
+			printf("\ttime: %.12s\n", sub_entry->header_entry.time);
+			printf("\tdate: %.14s\n", sub_entry->header_entry.date);
+			printf("\ttext2: %.10s\n", sub_entry->header_entry.text2);
+			printf("\tversion: %.20s\n", sub_entry->header_entry.version);
+			printf("\tlength: %u\n", ntohl(sub_entry->header_entry.length));
+			printf("\taddr1: 0x%x\n", ntohl(sub_entry->header_entry.addr1));
+			printf("\taddr2: 0x%x\n", ntohl(sub_entry->header_entry.addr2));
+			printf("\tstart: 0x%x\n", ntohl(sub_entry->header_entry.start));
+			printf("\tabs. offset: 0x%lx\n", sub_entry->absolute_offset);
+			printf("\n\n");
+
+			dump_entry(sub_entry, part++, fd);
+		}
+		printf("\n\n");
+	}
+
+	if (close(fd) != 0) {
+		perror("Close failed.\n");
+		return;
+	}
+
+	talloc_free(tall_firm_ctx);
+}
+
+static void print_usage(void)
+{
+	printf("Usage: ipaccess-config\n");
+}
+
+static void print_help(void)
+{
+	printf("  -u --unit-id UNIT_ID\n");
+	printf("  -o --oml-ip ip\n");
+	printf("  -r --restart\n");
+	printf("  -n flags/mask\tSet NVRAM attributes.\n");
+	printf("  -l --listen testnr \tPerform specified test number\n");
+	printf("  -h --help this text\n");
+	printf("  -s --stream-id ID\n");
+	printf("  -d --software firmware\n");
+	printf("  -f --firmware firmware Provide firmware information\n");
+	printf("  -w --write-firmware. This will dump the firmware parts to the filesystem. Use with -f.\n");
+}
+
+int main(int argc, char **argv)
+{
+	struct gsm_bts *bts;
+	struct sockaddr_in sin;
+	int rc, option_index = 0, stream_id = 0xff;
+	struct log_target *stderr_target;
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+	log_set_log_level(stderr_target, 0);
+	log_parse_category_mask(stderr_target, "DNM,0");
+	bts_model_nanobts_init();
+
+	printf("ipaccess-config (C) 2009 by Harald Welte\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+	while (1) {
+		int c;
+		unsigned long ul;
+		char *slash;
+		static struct option long_options[] = {
+			{ "unit-id", 1, 0, 'u' },
+			{ "oml-ip", 1, 0, 'o' },
+			{ "restart", 0, 0, 'r' },
+			{ "help", 0, 0, 'h' },
+			{ "listen", 1, 0, 'l' },
+			{ "stream-id", 1, 0, 's' },
+			{ "software", 1, 0, 'd' },
+			{ "firmware", 1, 0, 'f' },
+			{ "write-firmware", 0, 0, 'w' },
+		};
+
+		c = getopt_long(argc, argv, "u:o:rn:l:hs:d:f:w", long_options,
+				&option_index);
+
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'u':
+			unit_id = optarg;
+			break;
+		case 'o':
+			prim_oml_ip = optarg;
+			break;
+		case 'r':
+			restart = 1;
+			break;
+		case 'n':
+			slash = strchr(optarg, '/');
+			if (!slash)
+				exit(2);
+			ul = strtoul(optarg, NULL, 16);
+			nv_flags = ul & 0xffff;
+			ul = strtoul(slash+1, NULL, 16);
+			nv_mask = ul & 0xffff;
+			break;
+		case 'l':
+			net_listen_testnr = atoi(optarg);
+			break;
+		case 's':
+			stream_id = atoi(optarg);
+			break;
+		case 'd':
+			software = strdup(optarg);
+			if (find_sw_load_params(optarg) != 0)
+				exit(0);
+			break;
+		case 'f':
+			firmware_analysis = optarg;
+			break;
+		case 'w':
+			dump_files = 1;
+			break;
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		}
+	};
+
+	if (firmware_analysis)
+		analyze_firmware(firmware_analysis);
+
+	if (optind >= argc) {
+		/* only warn if we have not done anything else */
+		if (!firmware_analysis)
+			fprintf(stderr, "you have to specify the IP address of the BTS. Use --help for more information\n");
+		exit(2);
+	}
+
+	gsmnet = gsm_network_init(1, 1, NULL);
+	if (!gsmnet)
+		exit(1);
+
+	bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_NANOBTS, HARDCODED_TSC,
+				HARDCODED_BSIC);
+	/* ip.access supports up to 4 chained TRX */
+	gsm_bts_trx_alloc(bts);
+	gsm_bts_trx_alloc(bts);
+	gsm_bts_trx_alloc(bts);
+	bts->oml_tei = stream_id;
+	
+	register_signal_handler(SS_NM, nm_sig_cb, NULL);
+	printf("Trying to connect to ip.access BTS ...\n");
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(argv[optind], &sin.sin_addr);
+	rc = ia_config_connect(bts, &sin);
+	if (rc < 0) {
+		perror("Error connecting to the BTS");
+		exit(1);
+	}
+	
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
diff --git a/openbsc/src/ipaccess/ipaccess-find.c b/openbsc/src/ipaccess/ipaccess-find.c
new file mode 100644
index 0000000..ec4a0b7
--- /dev/null
+++ b/openbsc/src/ipaccess/ipaccess-find.c
@@ -0,0 +1,208 @@
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocore/select.h>
+#include <osmocore/timer.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+
+static const char *idtag_names[] = {
+	[IPAC_IDTAG_SERNR]	= "Serial Number",
+	[IPAC_IDTAG_UNITNAME]	= "Unit Name",
+	[IPAC_IDTAG_LOCATION1]	= "Location 1",
+	[IPAC_IDTAG_LOCATION2]	= "Location 2",
+	[IPAC_IDTAG_EQUIPVERS]	= "Equipment Version",
+	[IPAC_IDTAG_SWVERSION]	= "Software Version",
+	[IPAC_IDTAG_IPADDR]	= "IP Address",
+	[IPAC_IDTAG_MACADDR]	= "MAC Address",
+	[IPAC_IDTAG_UNIT]	= "Unit ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+	if (tag >= ARRAY_SIZE(idtag_names))
+		return "unknown";
+
+	return idtag_names[tag];
+}
+
+static int udp_sock(const char *ifname)
+{
+	int fd, rc, bc = 1;
+	struct sockaddr_in sa;
+
+	fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (fd < 0)
+		return fd;
+
+	if (ifname) {
+		rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
+				strlen(ifname));
+		if (rc < 0)
+			goto err;
+	}
+
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	sa.sin_addr.s_addr = INADDR_ANY;
+
+	rc = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
+	if (rc < 0)
+		goto err;
+
+	rc = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc));
+	if (rc < 0)
+		goto err;
+
+#if 0
+	/* we cannot bind, since the response packets don't come from
+	 * the broadcast address */
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	inet_aton("255.255.255.255", &sa.sin_addr);
+
+	rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
+	if (rc < 0)
+		goto err;
+#endif
+	return fd;
+
+err:
+	close(fd);
+	return rc;
+}
+
+const unsigned char find_pkt[] = { 0x00, 0x0b+8, IPAC_PROTO_IPACCESS, 0x00,
+				IPAC_MSGT_ID_GET,
+					0x01, IPAC_IDTAG_MACADDR,
+					0x01, IPAC_IDTAG_IPADDR,
+					0x01, IPAC_IDTAG_UNIT,
+					0x01, IPAC_IDTAG_LOCATION1,
+					0x01, IPAC_IDTAG_LOCATION2,
+					0x01, IPAC_IDTAG_EQUIPVERS,
+					0x01, IPAC_IDTAG_SWVERSION,
+					0x01, IPAC_IDTAG_UNITNAME,
+					0x01, IPAC_IDTAG_SERNR,
+				};
+
+
+static int bcast_find(int fd)
+{
+	struct sockaddr_in sa;
+
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	inet_aton("255.255.255.255", &sa.sin_addr);
+
+	return sendto(fd, find_pkt, sizeof(find_pkt), 0, (struct sockaddr *) &sa, sizeof(sa));
+}
+
+static int parse_response(unsigned char *buf, int len)
+{
+	u_int8_t t_len;
+	u_int8_t t_tag;
+	u_int8_t *cur = buf;
+
+	while (cur < buf + len) {
+		t_len = *cur++;
+		t_tag = *cur++;
+		
+		printf("%s='%s'  ", ipac_idtag_name(t_tag), cur);
+
+		cur += t_len;
+	}
+	printf("\n");
+	return 0;
+}
+
+static int read_response(int fd)
+{
+	unsigned char buf[255];
+	struct sockaddr_in sa;
+	int len;
+	socklen_t sa_len = sizeof(sa);
+
+	len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &sa_len);
+	if (len < 0)
+		return len;
+
+	/* 2 bytes length, 1 byte protocol (0xfe) */
+	if (buf[2] != 0xfe)
+		return 0;
+
+	if (buf[4] != IPAC_MSGT_ID_RESP)
+		return 0;
+
+	return parse_response(buf+6, len-6);
+}
+
+static int bfd_cb(struct bsc_fd *bfd, unsigned int flags)
+{
+	if (flags & BSC_FD_READ)
+		return read_response(bfd->fd);
+	if (flags & BSC_FD_WRITE) {
+		bfd->when &= ~BSC_FD_WRITE;
+		return bcast_find(bfd->fd);
+	}
+	return 0;
+}
+
+static struct timer_list timer;
+
+static void timer_cb(void *_data)
+{
+	struct bsc_fd *bfd = _data;
+
+	bfd->when |= BSC_FD_WRITE;
+
+	bsc_schedule_timer(&timer, 5, 0);
+}
+
+int main(int argc, char **argv)
+{
+	struct bsc_fd bfd;
+	char *ifname;
+	int rc;
+
+	printf("ipaccess-find (C) 2009 by Harald Welte\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+	if (argc < 2) {
+		fprintf(stdout, "you might need to specify the outgoing\n"
+			" network interface, e.g. ``%s eth0''\n", argv[0]);
+	}
+
+	ifname = argv[1];
+	bfd.cb = bfd_cb;
+	bfd.when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd.fd = udp_sock(ifname);
+	if (bfd.fd < 0) {
+		perror("Cannot create local socket for broadcast udp");
+		exit(1);
+	}
+
+	bsc_register_fd(&bfd);
+
+	timer.cb = timer_cb;
+	timer.data = &bfd;
+
+	bsc_schedule_timer(&timer, 5, 0);
+
+	printf("Trying to find ip.access BTS by broadcast UDP...\n");
+
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
diff --git a/openbsc/src/ipaccess/ipaccess-firmware.c b/openbsc/src/ipaccess/ipaccess-firmware.c
new file mode 100644
index 0000000..bc40c1e
--- /dev/null
+++ b/openbsc/src/ipaccess/ipaccess-firmware.c
@@ -0,0 +1,136 @@
+/* Routines for parsing an ipacces SDP firmware file */
+
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PART_LENGTH 138
+
+static_assert(sizeof(struct sdp_header_entry) == 138, right_entry);
+static_assert(sizeof(struct sdp_firmware) == 158, _right_header_length);
+
+/* more magic, the second "int" in the header */
+static char more_magic[] = { 0x10, 0x02 };
+
+int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned int base_offset, struct llist_head *list)
+{
+	struct sdp_firmware *firmware_header = 0;
+	struct sdp_header *header;
+	char buf[4096];
+	int rc, i;
+	u_int16_t table_size;
+	u_int16_t table_offset;
+	off_t table_start;
+
+
+	rc = read(fd, buf, sizeof(*firmware_header));
+	if (rc < 0) {
+		perror("Can not read header start.");
+		return -1;
+	}
+
+	firmware_header = (struct sdp_firmware *) &buf[0];
+	if (strncmp(firmware_header->magic, " SDP", 4) != 0) {
+		fprintf(stderr, "Wrong magic.\n");
+		return -1;
+	}
+
+	if (memcmp(firmware_header->more_magic, more_magic, 2) != 0) {
+		fprintf(stderr, "Wrong more magic. Got: 0x%x %x %x %x\n",
+			firmware_header->more_magic[0] & 0xff, firmware_header->more_magic[1] & 0xff,
+			firmware_header->more_magic[2] & 0xff, firmware_header->more_magic[3] & 0xff);
+		return -1;
+	}
+
+
+	if (ntohl(firmware_header->file_length) != st_size) {
+		fprintf(stderr, "The filesize and the header do not match.\n");
+		return -1;
+	}
+
+	/* add the firmware */
+	header = talloc_zero(list, struct sdp_header);
+	header->firmware_info = *firmware_header;
+	INIT_LLIST_HEAD(&header->header_list);
+	llist_add(&header->entry, list);
+
+	table_offset = ntohs(firmware_header->table_offset);
+	table_start = lseek(fd, table_offset, SEEK_CUR);
+	if (table_start == -1) {
+		fprintf(stderr, "Failed to seek to the rel position: 0x%x\n", table_offset);
+		return -1;
+	}
+
+	if (read(fd, &table_size, sizeof(table_size)) != sizeof(table_size)) {
+		fprintf(stderr, "The table size could not be read.\n");
+		return -1;
+	}
+
+	table_size = ntohs(table_size);
+
+	if (table_size % PART_LENGTH != 0) {
+		fprintf(stderr, "The part length seems to be wrong: 0x%x\n", table_size);
+		return -1;
+	}
+
+	/* look into each firmware now */
+	for (i = 0; i < table_size / PART_LENGTH; ++i) {
+		struct sdp_header_entry entry;
+		struct sdp_header_item *header_entry;
+		unsigned int offset = table_start + 2;
+		offset += i * 138;
+
+		if (lseek(fd, offset, SEEK_SET) != offset) {
+			fprintf(stderr, "Can not seek to the offset: %u.\n", offset);
+			return -1;
+		}
+
+		rc = read(fd, &entry, sizeof(entry));
+		if (rc != sizeof(entry)) {
+			fprintf(stderr, "Can not read the header entry.\n");
+			return -1;
+		}
+
+		header_entry = talloc_zero(header,  struct sdp_header_item);
+		header_entry->header_entry = entry;
+		header_entry->absolute_offset = base_offset;
+		llist_add(&header_entry->entry, &header->header_list);
+
+		/* now we need to find the SDP file... */
+		offset = ntohl(entry.start) + 4 + base_offset;
+		if (lseek(fd, offset, SEEK_SET) != offset) {
+			perror("can't seek to sdp");
+			return -1;
+		}
+
+
+		ipaccess_analyze_file(fd, ntohl(entry.length), offset, list);
+	}
+
+	return 0;
+}
+
diff --git a/openbsc/src/ipaccess/ipaccess-proxy.c b/openbsc/src/ipaccess/ipaccess-proxy.c
new file mode 100644
index 0000000..3860813
--- /dev/null
+++ b/openbsc/src/ipaccess/ipaccess-proxy.c
@@ -0,0 +1,1128 @@
+/* OpenBSC Abis/IP proxy ip.access nanoBTS */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+
+static struct log_target *stderr_target;
+
+/* one instance of an ip.access protocol proxy */
+struct ipa_proxy {
+	/* socket where we listen for incoming OML from BTS */
+	struct bsc_fd oml_listen_fd;
+	/* socket where we listen for incoming RSL from BTS */
+	struct bsc_fd rsl_listen_fd;
+	/* list of BTS's (struct ipa_bts_conn */
+	struct llist_head bts_list;
+	/* the BSC reconnect timer */
+	struct timer_list reconn_timer;
+};
+
+/* global pointer to the proxy structure */
+static struct ipa_proxy *ipp;
+
+struct ipa_proxy_conn {
+	struct bsc_fd fd;
+	struct llist_head tx_queue;
+	struct ipa_bts_conn *bts_conn;
+};
+
+#define MAX_TRX 4
+
+/* represents a particular BTS in our proxy */
+struct ipa_bts_conn {
+	/* list of BTS's (ipa_proxy->bts_list) */
+	struct llist_head list;
+	/* back pointer to the proxy which we belong to */
+	struct ipa_proxy *ipp;
+	/* the unit ID as determined by CCM */
+	struct {
+		u_int16_t site_id;
+		u_int16_t bts_id;
+	} unit_id;
+
+	/* incoming connections from BTS */
+	struct ipa_proxy_conn *oml_conn;
+	struct ipa_proxy_conn *rsl_conn[MAX_TRX];
+
+	/* outgoing connections to BSC */
+	struct ipa_proxy_conn *bsc_oml_conn;
+	struct ipa_proxy_conn *bsc_rsl_conn[MAX_TRX];
+
+	/* UDP sockets for BTS and BSC injection */
+	struct bsc_fd udp_bts_fd;
+	struct bsc_fd udp_bsc_fd;
+
+	char *id_tags[0xff];
+	u_int8_t *id_resp;
+	unsigned int id_resp_len;
+};
+
+enum ipp_fd_type {
+	OML_FROM_BTS = 1,
+	RSL_FROM_BTS = 2,
+	OML_TO_BSC = 3,
+	RSL_TO_BSC = 4,
+	UDP_TO_BTS = 5,
+	UDP_TO_BSC = 6,
+};
+
+/* some of the code against we link from OpenBSC needs this */
+void *tall_bsc_ctx;
+
+static char *listen_ipaddr;
+static char *bsc_ipaddr;
+
+#define PROXY_ALLOC_SIZE	300
+
+static const u_int8_t pong[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG };
+static const u_int8_t id_ack[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK };
+static const u_int8_t id_req[] = { 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET,
+					0x01, IPAC_IDTAG_UNIT,
+					0x01, IPAC_IDTAG_MACADDR,
+					0x01, IPAC_IDTAG_LOCATION1,
+					0x01, IPAC_IDTAG_LOCATION2,
+					0x01, IPAC_IDTAG_EQUIPVERS,
+					0x01, IPAC_IDTAG_SWVERSION,
+					0x01, IPAC_IDTAG_UNITNAME,
+					0x01, IPAC_IDTAG_SERNR,
+				};
+
+static const char *idtag_names[] = {
+	[IPAC_IDTAG_SERNR]	= "Serial_Number",
+	[IPAC_IDTAG_UNITNAME]	= "Unit_Name",
+	[IPAC_IDTAG_LOCATION1]	= "Location_1",
+	[IPAC_IDTAG_LOCATION2]	= "Location_2",
+	[IPAC_IDTAG_EQUIPVERS]	= "Equipment_Version",
+	[IPAC_IDTAG_SWVERSION]	= "Software_Version",
+	[IPAC_IDTAG_IPADDR]	= "IP_Address",
+	[IPAC_IDTAG_MACADDR]	= "MAC_Address",
+	[IPAC_IDTAG_UNIT]	= "Unit_ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+	if (tag >= ARRAY_SIZE(idtag_names))
+		return "unknown";
+
+	return idtag_names[tag];
+}
+
+static int ipac_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
+{
+	u_int8_t t_len;
+	u_int8_t t_tag;
+	u_int8_t *cur = buf;
+
+	while (cur < buf + len) {
+		t_len = *cur++;
+		t_tag = *cur++;
+
+		DEBUGPC(DMI, "%s='%s' ", ipac_idtag_name(t_tag), cur);
+
+		dec->lv[t_tag].len = t_len;
+		dec->lv[t_tag].val = cur;
+
+		cur += t_len;
+	}
+	return 0;
+}
+
+static int parse_unitid(const char *str, u_int16_t *site_id, u_int16_t *bts_id,
+			u_int16_t *trx_id)
+{
+	unsigned long ul;
+	char *endptr;
+	const char *nptr;
+
+	nptr = str;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (site_id)
+		*site_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (bts_id)
+		*bts_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (trx_id)
+		*trx_id = ul & 0xffff;
+
+	return 0;
+}
+
+static struct ipa_bts_conn *find_bts_by_unitid(struct ipa_proxy *ipp,
+						u_int16_t site_id,
+						u_int16_t bts_id)
+{
+	struct ipa_bts_conn *ipbc;
+
+	llist_for_each_entry(ipbc, &ipp->bts_list, list) {
+		if (ipbc->unit_id.site_id == site_id &&
+		    ipbc->unit_id.bts_id == bts_id)
+			return ipbc;
+	}
+
+	return NULL;
+}
+
+struct ipa_proxy_conn *alloc_conn(void)
+{
+	struct ipa_proxy_conn *ipc;
+
+	ipc = talloc_zero(tall_bsc_ctx, struct ipa_proxy_conn);
+	if (!ipc)
+		return NULL;
+
+	INIT_LLIST_HEAD(&ipc->tx_queue);
+
+	return ipc;
+}
+
+static int store_idtags(struct ipa_bts_conn *ipbc, struct tlv_parsed *tlvp)
+{
+	unsigned int i, len;
+
+	for (i = 0; i <= 0xff; i++) {
+		if (!TLVP_PRESENT(tlvp, i))
+			continue;
+
+		len = TLVP_LEN(tlvp, i);
+#if 0
+		if (!ipbc->id_tags[i])
+			ipbc->id_tags[i] = talloc_size(tall_bsc_ctx, len);
+		else
+#endif
+			ipbc->id_tags[i] = talloc_realloc_size(ipbc,
+							  ipbc->id_tags[i], len);
+		if (!ipbc->id_tags[i])
+			return -ENOMEM;
+
+		memset(ipbc->id_tags[i], 0, len);
+		//memcpy(ipbc->id_tags[i], TLVP_VAL(tlvp, i), len);
+	}
+	return 0;
+}
+
+
+static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data);
+
+#define logp_ipbc_uid(ss, lvl, ipbc, trx_id) _logp_ipbc_uid(ss, lvl, __FILE__, __LINE__, ipbc, trx_id)
+
+static void _logp_ipbc_uid(unsigned int ss, unsigned int lvl, char *file, int line,
+			   struct ipa_bts_conn *ipbc, u_int8_t trx_id)
+{
+	if (ipbc)
+		logp2(ss, lvl, file, line, 0, "(%u/%u/%u) ", ipbc->unit_id.site_id,
+		     ipbc->unit_id.bts_id, trx_id);
+	else
+		logp2(ss, lvl, file, line, 0, "unknown ");
+}
+
+/* UDP socket handling */
+
+static int make_sock(struct bsc_fd *bfd, u_int16_t port, int proto, int priv_nr,
+		     int (*cb)(struct bsc_fd *fd, unsigned int what),
+		     void *data)
+{
+	struct sockaddr_in addr;
+	int ret, on = 1;
+
+	bfd->fd = socket(AF_INET, SOCK_DGRAM, proto);
+	bfd->cb = cb;
+	bfd->when = BSC_FD_READ;
+	bfd->data = data;
+	bfd->priv_nr = priv_nr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	addr.sin_addr.s_addr = INADDR_ANY;
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not bind socket: %s\n",
+			strerror(errno));
+		return -EIO;
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		perror("register UDP fd");
+		return ret;
+	}
+	return 0;
+}
+
+static int handle_udp_read(struct bsc_fd *bfd)
+{
+	struct ipa_bts_conn *ipbc = bfd->data;
+	struct ipa_proxy_conn *other_conn = NULL;
+	struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP UDP");
+	struct ipaccess_head *hh;
+	int ret;
+
+	/* with UDP sockets, we cannot read partial packets but have to read
+	 * all of it in one go */
+	hh = (struct ipaccess_head *) msg->data;
+	ret = recv(bfd->fd, msg->data, msg->data_len, 0);
+	if (ret < 0) {
+		if (errno != EAGAIN)
+			LOGP(DINP, LOGL_ERROR, "recv error  %s\n", strerror(errno));
+		msgb_free(msg);
+		return ret;
+	}
+	if (ret == 0) {
+		DEBUGP(DINP, "UDP peer disappeared, dead socket\n");
+		bsc_unregister_fd(bfd);
+		close(bfd->fd);
+		bfd->fd = -1;
+		msgb_free(msg);
+		return -EIO;
+	}
+	if (ret < sizeof(*hh)) {
+		DEBUGP(DINP, "could not even read header!?!\n");
+		msgb_free(msg);
+		return -EIO;
+	}
+	msgb_put(msg, ret);
+	msg->l2h = msg->data + sizeof(*hh);
+	DEBUGP(DMI, "UDP RX: %s\n", hexdump(msg->data, msg->len));
+
+	if (hh->len != msg->len - sizeof(*hh)) {
+		DEBUGP(DINP, "length (%u/%u) disagrees with header(%u)\n",
+			msg->len, msg->len - 3, hh->len);
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	switch (bfd->priv_nr & 0xff) {
+	case UDP_TO_BTS:
+		/* injection towards BTS */
+		switch (hh->proto) {
+		case IPAC_PROTO_RSL:
+			/* FIXME: what to do about TRX > 0 */
+			other_conn = ipbc->rsl_conn[0];
+			break;
+		default:
+			DEBUGP(DINP, "Unknown protocol 0x%02x, sending to "
+				"OML FD\n", hh->proto);
+			/* fall through */
+		case IPAC_PROTO_IPACCESS:
+		case IPAC_PROTO_OML:
+			other_conn = ipbc->oml_conn;
+			break;
+		}
+		break;
+	case UDP_TO_BSC:
+		/* injection towards BSC */
+		switch (hh->proto) {
+		case IPAC_PROTO_RSL:
+			/* FIXME: what to do about TRX > 0 */
+			other_conn = ipbc->bsc_rsl_conn[0];
+			break;
+		default:
+			DEBUGP(DINP, "Unknown protocol 0x%02x, sending to "
+				"OML FD\n", hh->proto);
+		case IPAC_PROTO_IPACCESS:
+		case IPAC_PROTO_OML:
+			other_conn = ipbc->bsc_oml_conn;
+			break;
+		}
+		break;
+	default:
+		DEBUGP(DINP, "Unknown filedescriptor priv_nr=%04x\n", bfd->priv_nr);
+		break;
+	}
+
+	if (other_conn) {
+		/* enqueue the message for TX on the respective FD */
+		msgb_enqueue(&other_conn->tx_queue, msg);
+		other_conn->fd.when |= BSC_FD_WRITE;
+	} else
+		msgb_free(msg);
+
+	return 0;
+}
+
+static int handle_udp_write(struct bsc_fd *bfd)
+{
+	/* not implemented yet */
+	bfd->when &= ~BSC_FD_WRITE;
+
+	return -EIO;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int udp_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_udp_read(bfd);
+	if (what & BSC_FD_WRITE)
+		rc = handle_udp_write(bfd);
+
+	return rc;
+}
+
+
+static int ipbc_alloc_connect(struct ipa_proxy_conn *ipc, struct bsc_fd *bfd,
+			      u_int16_t site_id, u_int16_t bts_id,
+			      u_int16_t trx_id, struct tlv_parsed *tlvp,
+			      struct msgb *msg)
+{
+	struct ipa_bts_conn *ipbc;
+	u_int16_t udp_port;
+	int ret = 0;
+	struct sockaddr_in sin;
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+	DEBUGP(DINP, "(%u/%u/%u) New BTS connection: ",
+		site_id, bts_id, trx_id);
+
+	/* OML needs to be established before RSL */
+	if ((bfd->priv_nr & 0xff) != OML_FROM_BTS) {
+		DEBUGPC(DINP, "Not a OML connection ?!?\n");
+		return -EIO;
+	}
+
+	/* allocate new BTS connection data structure */
+	ipbc = talloc_zero(tall_bsc_ctx, struct ipa_bts_conn);
+	if (!ipbc) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	DEBUGPC(DINP, "Created BTS Conn data structure\n");
+	ipbc->ipp = ipp;
+	ipbc->unit_id.site_id = site_id;
+	ipbc->unit_id.bts_id = bts_id;
+	ipbc->oml_conn = ipc;
+	ipc->bts_conn = ipbc;
+
+	/* store the content of the ID TAGS for later reference */
+	store_idtags(ipbc, tlvp);
+	ipbc->id_resp_len = msg->len;
+	ipbc->id_resp = talloc_size(tall_bsc_ctx, ipbc->id_resp_len);
+	memcpy(ipbc->id_resp, msg->data, ipbc->id_resp_len);
+
+	/* Create OML TCP connection towards BSC */
+	sin.sin_port = htons(IPA_TCP_PORT_OML);
+	ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc);
+	if (!ipbc->bsc_oml_conn) {
+		ret = -EIO;
+		goto err_bsc_conn;
+	}
+
+	DEBUGP(DINP, "(%u/%u/%u) OML Connected to BSC\n",
+		site_id, bts_id, trx_id);
+
+	/* Create UDP socket for BTS packet injection */
+	udp_port = 10000 + (site_id % 1000)*100 + (bts_id % 100);
+	ret = make_sock(&ipbc->udp_bts_fd, udp_port, IPPROTO_UDP,
+			UDP_TO_BTS, udp_fd_cb, ipbc);
+	if (ret < 0)
+		goto err_udp_bts;
+	DEBUGP(DINP, "(%u/%u/%u) Created UDP socket for injection "
+		"towards BTS at port %u\n", site_id, bts_id, trx_id, udp_port);
+
+	/* Create UDP socket for BSC packet injection */
+	udp_port = 20000 + (site_id % 1000)*100 + (bts_id % 100);
+	ret = make_sock(&ipbc->udp_bsc_fd, udp_port, IPPROTO_UDP,
+			UDP_TO_BSC, udp_fd_cb, ipbc);
+	if (ret < 0)
+		goto err_udp_bsc;
+	DEBUGP(DINP, "(%u/%u/%u) Created UDP socket for injection "
+		"towards BSC at port %u\n", site_id, bts_id, trx_id, udp_port);
+	llist_add(&ipbc->list, &ipp->bts_list);
+
+	return 0;
+
+err_udp_bsc:
+	bsc_unregister_fd(&ipbc->udp_bts_fd);
+err_udp_bts:
+	bsc_unregister_fd(&ipbc->bsc_oml_conn->fd);
+	close(ipbc->bsc_oml_conn->fd.fd);
+	talloc_free(ipbc->bsc_oml_conn);
+	ipbc->bsc_oml_conn = NULL;
+err_bsc_conn:
+	talloc_free(ipbc->id_resp);
+	talloc_free(ipbc);
+#if 0
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	talloc_free(bfd);
+#endif
+err_out:
+	return ret;
+}
+
+static int ipaccess_rcvmsg(struct ipa_proxy_conn *ipc, struct msgb *msg,
+			   struct bsc_fd *bfd)
+{
+	struct tlv_parsed tlvp;
+	u_int8_t msg_type = *(msg->l2h);
+	u_int16_t site_id, bts_id, trx_id;
+	struct ipa_bts_conn *ipbc;
+	int ret = 0;
+
+	switch (msg_type) {
+	case IPAC_MSGT_PING:
+		ret = write(bfd->fd, pong, sizeof(pong));
+		if (ret < 0)
+			return ret;
+		if (ret < sizeof(pong)) {
+			DEBUGP(DINP, "short write\n");
+			return -EIO;
+		}
+		break;
+	case IPAC_MSGT_PONG:
+		DEBUGP(DMI, "PONG!\n");
+		break;
+	case IPAC_MSGT_ID_RESP:
+		DEBUGP(DMI, "ID_RESP ");
+		/* parse tags, search for Unit ID */
+		ipac_idtag_parse(&tlvp, (u_int8_t *)msg->l2h + 2,
+				 msgb_l2len(msg)-2);
+		DEBUGP(DMI, "\n");
+
+		if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT)) {
+			LOGP(DINP, LOGL_ERROR, "No Unit ID in ID RESPONSE !?!\n");
+			return -EIO;
+		}
+
+		/* lookup BTS, create sign_link, ... */
+		site_id = bts_id = trx_id = 0;
+		parse_unitid((char *)TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT),
+			     &site_id, &bts_id, &trx_id);
+		ipbc = find_bts_by_unitid(ipp, site_id, bts_id);
+		if (!ipbc) {
+			/* We have not found an ipbc (per-bts proxy instance)
+			 * for this BTS yet.  The first connection of a new BTS must
+			 * be a OML connection.  We allocate the associated data structures,
+			 * and try to connect to the remote end */
+
+			return ipbc_alloc_connect(ipc, bfd, site_id, bts_id,
+						  trx_id, &tlvp, msg);
+			/* if this fails, the caller will clean up bfd */
+		} else {
+			struct sockaddr_in sin;
+			memset(&sin, 0, sizeof(sin));
+			sin.sin_family = AF_INET;
+			inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+			DEBUGP(DINP, "Identified BTS %u/%u/%u\n",
+				site_id, bts_id, trx_id);
+
+			if ((bfd->priv_nr & 0xff) != RSL_FROM_BTS) {
+				LOGP(DINP, LOGL_ERROR, "Second OML connection from "
+				     "same BTS ?!?\n");
+				return 0;
+			}
+
+			if (trx_id > MAX_TRX) {
+				LOGP(DINP, LOGL_ERROR, "We don't support more "
+				     "than %u TRX\n", MAX_TRX);
+				return -EINVAL;
+			}
+
+			ipc->bts_conn = ipbc;
+			/* store TRX number in higher 8 bit of the bfd private number */
+			bfd->priv_nr |= trx_id << 8;
+			ipbc->rsl_conn[trx_id] = ipc;
+
+			/* Create RSL TCP connection towards BSC */
+			sin.sin_port = htons(IPA_TCP_PORT_RSL);
+			ipbc->bsc_rsl_conn[trx_id] =
+				connect_bsc(&sin, RSL_TO_BSC | (trx_id << 8), ipbc);
+			if (!ipbc->bsc_oml_conn)
+				return -EIO;
+			DEBUGP(DINP, "(%u/%u/%u) Connected RSL to BSC\n",
+				site_id, bts_id, trx_id);
+		}
+		break;
+	case IPAC_MSGT_ID_GET:
+		DEBUGP(DMI, "ID_GET\n");
+		if ((bfd->priv_nr & 0xff) != OML_TO_BSC &&
+		    (bfd->priv_nr & 0xff) != RSL_TO_BSC) {
+			DEBUGP(DINP, "IDentity REQuest from BTS ?!?\n");
+			return -EIO;
+		}
+		ipbc = ipc->bts_conn;
+		if (!ipbc) {
+			DEBUGP(DINP, "ID_GET from BSC before we have ID_RESP from BTS\n");
+			return -EIO;
+		}
+		ret = write(bfd->fd, ipbc->id_resp, ipbc->id_resp_len);
+		break;
+	case IPAC_MSGT_ID_ACK:
+		DEBUGP(DMI, "ID_ACK? -> ACK!\n");
+		ret = write(bfd->fd, id_ack, sizeof(id_ack));
+		break;
+	}
+	return 0;
+}
+
+struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error)
+{
+	struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP");
+	struct ipaccess_head *hh;
+	int len, ret = 0;
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	/* first read our 3-byte header */
+	hh = (struct ipaccess_head *) msg->data;
+	ret = recv(bfd->fd, msg->data, 3, 0);
+	if (ret < 0) {
+		if (errno != EAGAIN)
+			LOGP(DINP, LOGL_ERROR, "recv error: %s\n", strerror(errno));
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	} else if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msgb_put(msg, ret);
+
+	/* then read te length as specified in header */
+	msg->l2h = msg->data + sizeof(*hh);
+	len = ntohs(hh->len);
+	ret = recv(bfd->fd, msg->l2h, len, 0);
+	if (ret < len) {
+		LOGP(DINP, LOGL_ERROR, "short read!\n");
+		msgb_free(msg);
+		*error = -EIO;
+		return NULL;
+	}
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+static struct ipa_proxy_conn *ipc_by_priv_nr(struct ipa_bts_conn *ipbc,
+					     unsigned int priv_nr)
+{
+	struct ipa_proxy_conn *bsc_conn;
+	unsigned int trx_id = priv_nr >> 8;
+
+	switch (priv_nr & 0xff) {
+	case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */
+		bsc_conn = ipbc->bsc_oml_conn;
+		break;
+	case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */
+		bsc_conn = ipbc->bsc_rsl_conn[trx_id];
+		break;
+	case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */
+		bsc_conn = ipbc->oml_conn;
+		break;
+	case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */
+		bsc_conn = ipbc->rsl_conn[trx_id];
+		break;
+	default:
+		bsc_conn = NULL;
+		break;
+	}
+	return bsc_conn;
+}
+
+static void reconn_tmr_cb(void *data)
+{
+	struct ipa_proxy *ipp = data;
+	struct ipa_bts_conn *ipbc;
+	struct sockaddr_in sin;
+	int i;
+
+	DEBUGP(DINP, "Running reconnect timer\n");
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+	llist_for_each_entry(ipbc, &ipp->bts_list, list) {
+		/* if OML to BSC is dead, try to restore it */
+		if (ipbc->oml_conn && !ipbc->bsc_oml_conn) {
+			sin.sin_port = htons(IPA_TCP_PORT_OML);
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, 0);
+			LOGPC(DINP, LOGL_NOTICE, "OML Trying to reconnect\n");
+			ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc);
+			if (!ipbc->bsc_oml_conn)
+				goto reschedule;
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, 0);
+			LOGPC(DINP, LOGL_NOTICE, "OML Reconnected\n");
+		}
+		/* if we (still) don't have a OML connection, skip RSL */
+		if (!ipbc->oml_conn || !ipbc->bsc_oml_conn)
+			continue;
+
+		for (i = 0; i < ARRAY_SIZE(ipbc->rsl_conn); i++) {
+			unsigned int priv_nr;
+			/* don't establish RSL links which we don't have */
+			if (!ipbc->rsl_conn[i])
+				continue;
+			if (ipbc->bsc_rsl_conn[i])
+				continue;
+			priv_nr = ipbc->rsl_conn[i]->fd.priv_nr;
+			priv_nr &= ~0xff;
+			priv_nr |= RSL_TO_BSC;
+			sin.sin_port = htons(IPA_TCP_PORT_RSL);
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+			LOGPC(DINP, LOGL_NOTICE, "RSL Trying to reconnect\n");
+			ipbc->bsc_rsl_conn[i] = connect_bsc(&sin, priv_nr, ipbc);
+			if (!ipbc->bsc_rsl_conn)
+				goto reschedule;
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+			LOGPC(DINP, LOGL_NOTICE, "RSL Reconnected\n");
+		}
+	}
+	return;
+
+reschedule:
+	bsc_schedule_timer(&ipp->reconn_timer, 5, 0);
+}
+
+static void handle_dead_socket(struct bsc_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;		/* local conn */
+	struct ipa_proxy_conn *bsc_conn;		/* remote conn */
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	unsigned int trx_id = bfd->priv_nr >> 8;
+	struct msgb *msg, *msg2;
+
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	/* FIXME: clear tx_queue, remove all references, etc. */
+	llist_for_each_entry_safe(msg, msg2, &ipc->tx_queue, list)
+		msgb_free(msg);
+
+	switch (bfd->priv_nr & 0xff) {
+	case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */
+		ipbc->oml_conn = NULL;
+		bsc_conn = ipbc->bsc_oml_conn;
+		/* close the connection to the BSC */
+		bsc_unregister_fd(&bsc_conn->fd);
+		close(bsc_conn->fd.fd);
+		llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list)
+			msgb_free(msg);
+		talloc_free(bsc_conn);
+		ipbc->bsc_oml_conn = NULL;
+		/* FIXME: do we need to delete the entire ipbc ? */
+		break;
+	case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */
+		ipbc->rsl_conn[trx_id] = NULL;
+		bsc_conn = ipbc->bsc_rsl_conn[trx_id];
+		/* close the connection to the BSC */
+		bsc_unregister_fd(&bsc_conn->fd);
+		close(bsc_conn->fd.fd);
+		llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list)
+			msgb_free(msg);
+		talloc_free(bsc_conn);
+		ipbc->bsc_rsl_conn[trx_id] = NULL;
+		break;
+	case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */
+		ipbc->bsc_oml_conn = NULL;
+		bsc_conn = ipbc->oml_conn;
+		/* start reconnect timer */
+		bsc_schedule_timer(&ipp->reconn_timer, 5, 0);
+		break;
+	case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */
+		ipbc->bsc_rsl_conn[trx_id] = NULL;
+		bsc_conn = ipbc->rsl_conn[trx_id];
+		/* start reconnect timer */
+		bsc_schedule_timer(&ipp->reconn_timer, 5, 0);
+		break;
+	default:
+		bsc_conn = NULL;
+		break;
+	}
+
+	talloc_free(ipc);
+}
+
+static int handle_tcp_read(struct bsc_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	struct ipa_proxy_conn *bsc_conn;
+	struct msgb *msg;
+	struct ipaccess_head *hh;
+	int ret = 0;
+	char *btsbsc;
+
+	if ((bfd->priv_nr & 0xff) <= 2)
+		btsbsc = "BTS";
+	else
+		btsbsc = "BSC";
+
+	msg = ipaccess_read_msg(bfd, &ret);
+	if (!msg) {
+		if (ret == 0) {
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+			LOGPC(DINP, LOGL_NOTICE, "%s disappeared, "
+			     "dead socket\n", btsbsc);
+			handle_dead_socket(bfd);
+		}
+		return ret;
+	}
+
+	msgb_put(msg, ret);
+	logp_ipbc_uid(DMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+	DEBUGPC(DMI, "RX<-%s: %s\n", btsbsc, hexdump(msg->data, msg->len));
+
+	hh = (struct ipaccess_head *) msg->data;
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		ret = ipaccess_rcvmsg(ipc, msg, bfd);
+		if (ret < 0) {
+			bsc_unregister_fd(bfd);
+			close(bfd->fd);
+			bfd->fd = -1;
+			talloc_free(bfd);
+		}
+		/* we do not forward the CCM protocol through the
+		 * proxy but rather terminate it ourselves */
+		msgb_free(msg);
+		return ret;
+	}
+
+	if (!ipbc) {
+		LOGP(DINP, LOGL_ERROR,
+		     "received %s packet but no ipc->bts_conn?!?\n", btsbsc);
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	bsc_conn = ipc_by_priv_nr(ipbc, bfd->priv_nr);
+	if (bsc_conn) {
+		/* enqueue packet towards BSC */
+		msgb_enqueue(&bsc_conn->tx_queue, msg);
+		/* mark respective filedescriptor as 'we want to write' */
+		bsc_conn->fd.when |= BSC_FD_WRITE;
+	} else {
+		logp_ipbc_uid(DINP, LOGL_INFO, ipbc, bfd->priv_nr >> 8);
+		LOGPC(DINP, LOGL_INFO, "Dropping packet from %s, "
+		     "since remote connection is dead\n", btsbsc);
+		msgb_free(msg);
+	}
+
+	return ret;
+}
+
+/* a TCP socket is ready to be written to */
+static int handle_tcp_write(struct bsc_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	struct llist_head *lh;
+	struct msgb *msg;
+	char *btsbsc;
+	int ret;
+
+	if ((bfd->priv_nr & 0xff) <= 2)
+		btsbsc = "BTS";
+	else
+		btsbsc = "BSC";
+
+
+	/* get the next msg for this timeslot */
+	if (llist_empty(&ipc->tx_queue)) {
+		bfd->when &= ~BSC_FD_WRITE;
+		return 0;
+	}
+	lh = ipc->tx_queue.next;
+	llist_del(lh);
+	msg = llist_entry(lh, struct msgb, list);
+
+	logp_ipbc_uid(DMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+	DEBUGPC(DMI, "TX %04x: %s\n", bfd->priv_nr,
+		hexdump(msg->data, msg->len));
+
+	ret = send(bfd->fd, msg->data, msg->len, 0);
+	msgb_free(msg);
+
+	if (ret == 0) {
+		logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+		LOGP(DINP, LOGL_NOTICE, "%s disappeared, dead socket\n", btsbsc);
+		handle_dead_socket(bfd);
+	}
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int ipaccess_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ) {
+		rc = handle_tcp_read(bfd);
+		if (rc < 0)
+			return rc;
+	}
+	if (what & BSC_FD_WRITE)
+		rc = handle_tcp_write(bfd);
+
+	return rc;
+}
+
+/* callback of the listening filedescriptor */
+static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+	int ret;
+	struct ipa_proxy_conn *ipc;
+	struct bsc_fd *bfd;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (ret < 0) {
+		perror("accept");
+		return ret;
+	}
+	DEBUGP(DINP, "accept()ed new %s link from %s\n",
+		(listen_bfd->priv_nr & 0xff) == OML_FROM_BTS ? "OML" : "RSL",
+		inet_ntoa(sa.sin_addr));
+
+	ipc = alloc_conn();
+	if (!ipc) {
+		close(ret);
+		return -ENOMEM;
+	}
+
+	bfd = &ipc->fd;
+	bfd->fd = ret;
+	bfd->data = ipc;
+	bfd->priv_nr = listen_bfd->priv_nr;
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ;
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+		close(bfd->fd);
+		talloc_free(ipc);
+		return ret;
+	}
+
+	/* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+	ret = write(bfd->fd, id_req, sizeof(id_req));
+
+	return 0;
+}
+
+static int make_listen_sock(struct bsc_fd *bfd, u_int16_t port, int priv_nr,
+		     int (*cb)(struct bsc_fd *fd, unsigned int what))
+{
+	struct sockaddr_in addr;
+	int ret, on = 1;
+
+	bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	bfd->cb = cb;
+	bfd->when = BSC_FD_READ;
+	bfd->priv_nr = priv_nr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	if (!listen_ipaddr)
+		addr.sin_addr.s_addr = INADDR_ANY;
+	else
+		inet_aton(listen_ipaddr, &addr.sin_addr);
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not bind listen socket %s\n",
+			strerror(errno));
+		return -EIO;
+	}
+
+	ret = listen(bfd->fd, 1);
+	if (ret < 0) {
+		perror("listen");
+		return ret;
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		perror("register_listen_fd");
+		return ret;
+	}
+	return 0;
+}
+
+/* Actively connect to a BSC.  */
+static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data)
+{
+	struct ipa_proxy_conn *ipc;
+	struct bsc_fd *bfd;
+	int ret, on = 1;
+
+	ipc = alloc_conn();
+	if (!ipc)
+		return NULL;
+
+	ipc->bts_conn = data;
+
+	bfd = &ipc->fd;
+	bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd->data = ipc;
+	bfd->priv_nr = priv_nr;
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not connect socket\n");
+		close(bfd->fd);
+		talloc_free(ipc);
+		return NULL;
+	}
+
+	/* pre-fill tx_queue with identity request */
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		close(bfd->fd);
+		talloc_free(ipc);
+		return NULL;
+	}
+
+	return ipc;
+}
+
+static int ipaccess_proxy_setup(void)
+{
+	int ret;
+
+	ipp = talloc_zero(tall_bsc_ctx, struct ipa_proxy);
+	if (!ipp)
+		return -ENOMEM;
+	INIT_LLIST_HEAD(&ipp->bts_list);
+	ipp->reconn_timer.cb = reconn_tmr_cb;
+	ipp->reconn_timer.data = ipp;
+
+	/* Listen for OML connections */
+	ret = make_listen_sock(&ipp->oml_listen_fd, IPA_TCP_PORT_OML,
+				OML_FROM_BTS, listen_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	/* Listen for RSL connections */
+	ret = make_listen_sock(&ipp->rsl_listen_fd, IPA_TCP_PORT_RSL,
+				RSL_FROM_BTS, listen_fd_cb);
+
+	return ret;
+}
+
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	listen_ipaddr = "192.168.100.11";
+	bsc_ipaddr = "192.168.100.239";
+
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "ipaccess-proxy");
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+	log_parse_category_mask(stderr_target, "DINP:DMI");
+
+	rc = ipaccess_proxy_setup();
+	if (rc < 0)
+		exit(1);
+
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+
+	while (1) {
+		bsc_select_main(0);
+	}
+}
diff --git a/openbsc/src/isdnsync.c b/openbsc/src/isdnsync.c
new file mode 100644
index 0000000..d8819ac
--- /dev/null
+++ b/openbsc/src/isdnsync.c
@@ -0,0 +1,192 @@
+/* isdnsync.c
+ *
+ * Author       Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All rights reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include "mISDNif.h"
+#define MISDN_OLD_AF_COMPATIBILITY
+#define AF_COMPATIBILITY_FUNC
+#include "compat_af_isdn.h"
+
+int card = 0;
+int sock = -1;
+
+int mISDN_open(void)
+{
+	int			fd, ret;
+	struct mISDN_devinfo	devinfo;
+	struct sockaddr_mISDN	l2addr;
+
+	fd = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (fd < 0) {
+		fprintf(stderr, "could not open socket (%s)\n", strerror(errno));
+		return fd;
+	}
+	devinfo.id = card;
+	ret = ioctl(fd, IMGETDEVINFO, &devinfo);
+	if (ret < 0) {
+		fprintf(stderr,"could not send IOCTL IMGETCOUNT (%s)\n", strerror(errno));
+		close(fd);
+		return ret;
+	}
+	close(fd);
+	if (!(devinfo.Dprotocols & (1 << ISDN_P_TE_S0))
+	 && !(devinfo.Dprotocols & (1 << ISDN_P_TE_E1))) {
+		fprintf(stderr,"Interface does not support TE mode (%s)\n", strerror(errno));
+		close(fd);
+		return ret;
+	}
+	fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_TE);
+	if (fd < 0) {
+		fprintf(stderr,"could not open ISDN_P_LAPD_TE socket (%s)\n", strerror(errno));
+		return fd;
+	}
+	l2addr.family = AF_ISDN;
+	l2addr.dev = card;
+	l2addr.channel = 0;
+	l2addr.sapi = 0;
+	l2addr.tei = 0;
+	ret = bind(fd, (struct sockaddr *)&l2addr, sizeof(l2addr));
+	if (ret < 0) {
+		fprintf(stderr,"could not bind socket for card %d (%s)\n", card, strerror(errno));
+		close(fd);
+		return ret;
+	}
+	sock = fd;
+
+	return sock;
+}
+
+
+void mISDN_handle(void)
+{
+	int ret;
+	fd_set rfd;
+	struct timeval tv;
+	struct sockaddr_mISDN addr;
+	socklen_t alen;
+	unsigned char buffer[2048];
+	struct mISDNhead *hh = (struct mISDNhead *)buffer;
+	int l1 = 0, l2 = 0, tei = 0;
+
+	while(1) {
+again:
+		FD_ZERO(&rfd);
+		FD_SET(sock, &rfd);
+		tv.tv_sec = 2;
+		tv.tv_usec = 0;
+		ret = select(sock+1, &rfd, NULL, NULL, &tv);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			fprintf(stderr, "%s aborted: %s\n", __FUNCTION__, strerror(errno));
+			break;
+		}
+		if (FD_ISSET(sock, &rfd)) {
+			alen = sizeof(addr);
+			ret = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &alen);
+			if (ret < 0) {
+				fprintf(stderr, "%s read socket error %s\n", __FUNCTION__, strerror(errno));
+			} else if (ret < MISDN_HEADER_LEN) {
+					fprintf(stderr, "%s read socket shor frame\n", __FUNCTION__);
+			} else {
+				switch(hh->prim) {
+					case MPH_ACTIVATE_IND:
+					case PH_ACTIVATE_IND:
+						if (!l1) {
+							printf("PH_ACTIVATE\n");
+							printf("*** Sync available from interface :-)\n");
+							l1 = 1;
+						}
+						goto again;
+					break;
+					case MPH_DEACTIVATE_IND:
+					case PH_DEACTIVATE_IND:
+						if (l1) {
+							printf("PH_DEACTIVATE\n");
+							printf("*** Lost sync on interface        :-(\n");
+							l1 = 0;
+						}
+						goto again;
+					break;
+					case DL_ESTABLISH_IND:
+					case DL_ESTABLISH_CNF:
+						printf("DL_ESTABLISH\n");
+						l2 = 1;
+						goto again;
+					break;
+					case DL_RELEASE_IND:
+					case DL_RELEASE_CNF:
+						printf("DL_RELEASE\n");
+						l2 = 0;
+						goto again;
+					break;
+					case DL_INFORMATION_IND:
+						printf("DL_INFORMATION (tei %d sapi %d)\n", addr.tei, addr.sapi);
+						tei = 1;
+					break;
+					default:
+//						printf("prim %x\n", hh->prim);
+						goto again;
+				}
+			}
+		}
+		if (tei && !l2) {
+			hh->prim = DL_ESTABLISH_REQ;
+			printf("-> activating layer 2\n");
+			sendto(sock, buffer, MISDN_HEADER_LEN, 0, (struct sockaddr *) &addr, alen);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int ret;
+
+	if (argc <= 1)
+	{
+		printf("Usage: %s <card>\n\n", argv[0]);
+		printf("Opens given card number in TE-mode PTP and tries to keep layer 2 established.\n");
+		printf("This keeps layer 1 activated to retrieve a steady sync signal from network.\n");
+		return(0);
+	}
+
+	card = atoi(argv[1]);
+
+	init_af_isdn();
+
+	if ((ret = mISDN_open() < 0))
+		return(ret);
+
+	mISDN_handle();
+
+	close(sock);
+
+	return 0;
+}
diff --git a/openbsc/src/meas_rep.c b/openbsc/src/meas_rep.c
new file mode 100644
index 0000000..4b9cc1a
--- /dev/null
+++ b/openbsc/src/meas_rep.c
@@ -0,0 +1,114 @@
+/* Measurement Report Processing */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+
+static int get_field(const struct gsm_meas_rep *rep,
+		     enum meas_rep_field field)
+{
+	switch (field) {
+	case MEAS_REP_DL_RXLEV_FULL:
+		return rep->dl.full.rx_lev;
+	case MEAS_REP_DL_RXLEV_SUB:
+		return rep->dl.sub.rx_lev;
+	case MEAS_REP_DL_RXQUAL_FULL:
+		return rep->dl.full.rx_qual;
+	case MEAS_REP_DL_RXQUAL_SUB:
+		return rep->dl.sub.rx_qual;
+	case MEAS_REP_UL_RXLEV_FULL:
+		return rep->ul.full.rx_lev;
+	case MEAS_REP_UL_RXLEV_SUB:
+		return rep->ul.sub.rx_lev;
+	case MEAS_REP_UL_RXQUAL_FULL:
+		return rep->ul.full.rx_qual;
+	case MEAS_REP_UL_RXQUAL_SUB:
+		return rep->ul.sub.rx_qual;
+	}
+
+	return 0;
+}
+
+
+unsigned int calc_initial_idx(unsigned int array_size,
+			      unsigned int meas_rep_idx,
+			      unsigned int num_values)
+{
+	int offs, idx;
+
+	/* from which element do we need to start if we're interested
+	 * in an average of 'num' elements */
+	offs = meas_rep_idx - num_values;
+
+	if (offs < 0)
+		idx = array_size + offs;
+	else
+		idx = offs;
+
+	return idx;
+}
+
+/* obtain an average over the last 'num' fields in the meas reps */
+int get_meas_rep_avg(const struct gsm_lchan *lchan,
+		     enum meas_rep_field field, unsigned int num)
+{
+	unsigned int i, idx;
+	int avg = 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+				lchan->meas_rep_idx, num);
+
+	for (i = 0; i < num; i++) {
+		int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep);
+
+		avg += get_field(&lchan->meas_rep[j], field);
+	}
+
+	return avg / num;
+}
+
+/* Check if N out of M last values for FIELD are >= bd */
+int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
+			enum meas_rep_field field,
+			unsigned int n, unsigned int m, int be)
+{
+	unsigned int i, idx;
+	int count = 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+				lchan->meas_rep_idx, m);
+
+	for (i = 0; i < m; i++) {
+		int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep);
+		int val = get_field(&lchan->meas_rep[j], field);
+
+		if (val >= be)
+			count++;
+
+		if (count >= n)
+			return 1;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/mgcp.cfg b/openbsc/src/mgcp.cfg
new file mode 100644
index 0000000..678f546
--- /dev/null
+++ b/openbsc/src/mgcp.cfg
@@ -0,0 +1,19 @@
+!
+! MGCP configuration hand edited
+!   !
+password foo
+!
+line vty
+ no login
+!
+mgcp
+!  local ip 213.167.134.14
+  bts ip 172.16.252.43
+  bind ip 213.167.134.141
+  bind port 2427
+  bind early 1
+  rtp base 4000
+  sdp audio payload number 98
+  sdp audio payload name AMR/8000
+  number endpoints 31
+  loop 1
diff --git a/openbsc/src/mgcp/mgcp_main.c b/openbsc/src/mgcp/mgcp_main.c
new file mode 100644
index 0000000..f7f1f80
--- /dev/null
+++ b/openbsc/src/mgcp/mgcp_main.c
@@ -0,0 +1,273 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The main method to drive it as a standalone process      */
+
+/*
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/select.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/telnet_interface.h>
+#include <openbsc/vty.h>
+
+#include <vty/command.h>
+
+#include "../../bscconfig.h"
+
+/* this is here for the vty... it will never be called */
+void subscr_put() { abort(); }
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#warning "Make use of the rtp proxy code"
+
+static struct bsc_fd bfd;
+static struct mgcp_config *cfg;
+static int reset_endpoints = 0;
+
+const char *openbsc_version = "OpenBSC MGCP " PACKAGE_VERSION;
+const char *openbsc_copyright =
+	"Copyright (C) 2009-2010 Holger Freyther and On-Waves\n"
+	"Contributions by Daniel Willmann, Jan Lübbe,Stefan Schmidt\n"
+	"Dieter Spaar, Andreas Eversberg, Harald Welte\n\n"
+	"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
+	"This is free software: you are free to change and redistribute it.\n"
+	"There is NO WARRANTY, to the extent permitted by law.\n";
+
+static char *config_file = "mgcp.cfg";
+
+/* used by msgb and mgcp */
+void *tall_bsc_ctx = NULL;
+
+static void print_help()
+{
+	printf("Some useful help...\n");
+	printf(" -h --help is printing this text.\n");
+	printf(" -c --config-file filename The config file to use.\n");
+}
+
+static void print_mgcp_version()
+{
+	printf("%s\n\n", openbsc_version);
+	printf("%s", openbsc_copyright);
+}
+
+static void handle_options(int argc, char** argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"config-file", 1, 0, 'c'},
+			{"version", 0, 0, 'V'},
+			{0, 0, 0, 0},
+		};
+
+		c = getopt_long(argc, argv, "hc:V", long_options, &option_index);
+
+		if (c == -1)
+			break;
+
+		switch(c) {
+		case 'h':
+			print_help();
+			exit(0);
+			break;
+		case 'c':
+			config_file = talloc_strdup(tall_bsc_ctx, optarg);
+			break;
+		case 'V':
+			print_mgcp_version();
+			exit(0);
+			break;
+		default:
+			/* ignore */
+			break;
+		};
+	}
+}
+
+/* simply remember this */
+static int mgcp_rsip_cb(struct mgcp_config *cfg)
+{
+	reset_endpoints = 1;
+
+	return 0;
+}
+
+static int mgcp_change_cb(struct mgcp_config *cfg, int endpoint, int state, int local_rtp)
+{
+	if (state != MGCP_ENDP_MDCX)
+		return 0;
+
+	mgcp_send_dummy(&cfg->endpoints[endpoint]);
+	return 0;
+}
+
+static int read_call_agent(struct bsc_fd *fd, unsigned int what)
+{
+	struct sockaddr_in addr;
+	socklen_t slen = sizeof(addr);
+	struct msgb *msg;
+	struct msgb *resp;
+	int i;
+
+	msg = (struct msgb *) fd->data;
+
+	/* read one less so we can use it as a \0 */
+	int rc = recvfrom(bfd.fd, msg->data, msg->data_len - 1, 0,
+		(struct sockaddr *) &addr, &slen);
+	if (rc < 0) {
+		perror("Gateway failed to read");
+		return -1;
+	} else if (slen > sizeof(addr)) {
+		fprintf(stderr, "Gateway received message from outerspace: %d %d\n",
+			slen, sizeof(addr));
+		return -1;
+	}
+
+	/* handle message now */
+	msg->l2h = msgb_put(msg, rc);
+	resp = mgcp_handle_message(cfg, msg);
+	msgb_reset(msg);
+
+	if (resp) {
+		sendto(bfd.fd, resp->l2h, msgb_l2len(resp), 0, (struct sockaddr *) &addr, sizeof(addr));
+		msgb_free(resp);
+	}
+
+	if (reset_endpoints) {
+		LOGP(DMGCP, LOGL_NOTICE, "Asked to reset endpoints.\n");
+		reset_endpoints = 0;
+
+		/* is checking in_addr.s_addr == INADDR_LOOPBACK making it more secure? */
+		for (i = 1; i < cfg->number_endpoints; ++i)
+			mgcp_free_endp(&cfg->endpoints[i]);
+	}
+
+	return 0;
+}
+
+
+int main(int argc, char** argv)
+{
+	struct gsm_network dummy_network;
+	struct sockaddr_in addr;
+	int on = 1, rc;
+	struct log_target *stderr_target;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "mgcp-callagent");
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	cfg = mgcp_config_alloc();
+	if (!cfg)
+		return -1;
+
+	handle_options(argc, argv);
+
+	telnet_init(&dummy_network, 4243);
+        rc = mgcp_parse_config(config_file, cfg);
+	if (rc < 0)
+		return rc;
+
+	/* set some callbacks */
+	cfg->reset_cb = mgcp_rsip_cb;
+	cfg->change_cb = mgcp_change_cb;
+
+        /* we need to bind a socket */
+        if (rc == 0) {
+		bfd.when = BSC_FD_READ;
+		bfd.cb = read_call_agent;
+		bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+		if (bfd.fd < 0) {
+			perror("Gateway failed to listen");
+			return -1;
+		}
+
+		setsockopt(bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+		memset(&addr, 0, sizeof(addr));
+		addr.sin_family = AF_INET;
+		addr.sin_port = htons(cfg->source_port);
+		inet_aton(cfg->source_addr, &addr.sin_addr);
+
+		if (bind(bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+			perror("Gateway failed to bind");
+			return -1;
+		}
+
+		bfd.data = msgb_alloc(4096, "mgcp-msg");
+		if (!bfd.data) {
+			fprintf(stderr, "Gateway memory error.\n");
+			return -1;
+		}
+
+
+		if (bsc_register_fd(&bfd) != 0) {
+			LOGP(DMGCP, LOGL_FATAL, "Failed to register the fd\n");
+			return -1;
+		}
+
+		LOGP(DMGCP, LOGL_NOTICE, "Configured for MGCP.\n");
+	}
+
+	/* initialisation */
+	srand(time(NULL));
+
+	/* main loop */
+	while (1) {
+		bsc_select_main(0);
+	}
+
+
+	return 0;
+}
+
+struct gsm_network;
+int bsc_vty_init(struct gsm_network *dummy)
+{
+	cmd_init(1);
+	vty_init();
+
+	openbsc_vty_add_cmds();
+        mgcp_vty_init();
+	return 0;
+}
+
diff --git a/openbsc/src/mgcp/mgcp_network.c b/openbsc/src/mgcp/mgcp_network.c
new file mode 100644
index 0000000..cd745a7
--- /dev/null
+++ b/openbsc/src/mgcp/mgcp_network.c
@@ -0,0 +1,284 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <endian.h>
+#include <errno.h>
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#warning "Make use of the rtp proxy code"
+
+/* according to rtp_proxy.c RFC 3550 */
+struct rtp_hdr {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	u_int8_t  csrc_count:4,
+		  extension:1,
+		  padding:1,
+		  version:2;
+	u_int8_t  payload_type:7,
+		  marker:1;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	u_int8_t  version:2,
+		  padding:1,
+		  extension:1,
+		  csrc_count:4;
+	u_int8_t  marker:1,
+		  payload_type:7;
+#endif
+	u_int16_t sequence;
+	u_int32_t timestamp;
+	u_int32_t ssrc;
+} __attribute__((packed));
+
+
+enum {
+	DEST_NETWORK = 0,
+	DEST_BTS = 1,
+};
+
+enum {
+	PROTO_RTP,
+	PROTO_RTCP,
+};
+
+#define DUMMY_LOAD 0x23
+
+
+static int udp_send(int fd, struct in_addr *addr, int port, char *buf, int len)
+{
+	struct sockaddr_in out;
+	out.sin_family = AF_INET;
+	out.sin_port = port;
+	memcpy(&out.sin_addr, addr, sizeof(*addr));
+
+	return sendto(fd, buf, len, 0, (struct sockaddr *)&out, sizeof(out));
+}
+
+int mgcp_send_dummy(struct mgcp_endpoint *endp)
+{
+	static char buf[] = { DUMMY_LOAD };
+
+	return udp_send(endp->local_rtp.fd, &endp->remote,
+			endp->net_rtp, buf, 1);
+}
+
+static void patch_payload(int payload, char *data, int len)
+{
+	struct rtp_hdr *rtp_hdr;
+
+	if (len < sizeof(*rtp_hdr))
+		return;
+
+	if (payload < 0)
+		return;
+
+	rtp_hdr = (struct rtp_hdr *) data;
+	rtp_hdr->payload_type = payload;
+}
+
+/*
+ * There is data coming. We will have to figure out if it
+ * came from the BTS or the MediaGateway of the MSC. On top
+ * of that we need to figure out if it was RTP or RTCP.
+ *
+ * Currently we do not communicate with the BSC so we have
+ * no idea where the BTS is listening for RTP and need to
+ * do the classic routing trick. Wait for the first packet
+ * from the BTS and then go ahead.
+ */
+static int rtp_data_cb(struct bsc_fd *fd, unsigned int what)
+{
+	char buf[4096];
+	struct sockaddr_in addr;
+	socklen_t slen = sizeof(addr);
+	struct mgcp_endpoint *endp;
+	struct mgcp_config *cfg;
+	int rc, dest, proto;
+
+	endp = (struct mgcp_endpoint *) fd->data;
+	cfg = endp->cfg;
+
+	rc = recvfrom(fd->fd, &buf, sizeof(buf), 0,
+			    (struct sockaddr *) &addr, &slen);
+	if (rc < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to receive message on: 0x%x errno: %d/%s\n",
+			ENDPOINT_NUMBER(endp), errno, strerror(errno));
+		return -1;
+	}
+
+	/* do not forward aynthing... maybe there is a packet from the bts */
+	if (endp->ci == CI_UNUSED)
+		return -1;
+
+	/*
+	 * Figure out where to forward it to. This code assumes that we
+	 * have received the Connection Modify and know who is a legitimate
+	 * partner. According to the spec we could attempt to forward even
+	 * after the Create Connection but we will not as we are not really
+	 * able to tell if this is legitimate.
+	 */
+	#warning "Slight spec violation. With connection mode recvonly we should attempt to forward."
+	dest = memcmp(&addr.sin_addr, &endp->remote, sizeof(addr.sin_addr)) == 0 &&
+		    (endp->net_rtp == addr.sin_port || endp->net_rtcp == addr.sin_port)
+			? DEST_BTS : DEST_NETWORK;
+	proto = fd == &endp->local_rtp ? PROTO_RTP : PROTO_RTCP;
+
+	/* We have no idea who called us, maybe it is the BTS. */
+	if (dest == DEST_NETWORK && (endp->bts_rtp == 0 || cfg->forward_ip)) {
+		/* it was the BTS... */
+		if (!cfg->bts_ip
+		    || memcmp(&addr.sin_addr, &cfg->bts_in, sizeof(cfg->bts_in)) == 0
+		    || memcmp(&addr.sin_addr, &endp->bts, sizeof(endp->bts)) == 0) {
+			if (fd == &endp->local_rtp) {
+				endp->bts_rtp = addr.sin_port;
+			} else {
+				endp->bts_rtcp = addr.sin_port;
+			}
+
+			endp->bts = addr.sin_addr;
+			LOGP(DMGCP, LOGL_NOTICE, "Found BTS for endpoint: 0x%x on port: %d/%d of %s\n",
+				ENDPOINT_NUMBER(endp), ntohs(endp->bts_rtp), ntohs(endp->bts_rtcp),
+				inet_ntoa(addr.sin_addr));
+
+		}
+	}
+
+	/* throw away dummy message */
+	if (rc == 1 && buf[0] == DUMMY_LOAD) {
+		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy on 0x%x\n",
+			ENDPOINT_NUMBER(endp));
+		return 0;
+	}
+
+	/* do this before the loop handling */
+	if (dest == DEST_NETWORK)
+		++endp->in_bts;
+	else
+		++endp->in_remote;
+
+	/* dispatch */
+	if (cfg->audio_loop)
+		dest = !dest;
+
+	if (dest == DEST_NETWORK) {
+		patch_payload(endp->net_payload_type, buf, rc);
+		return udp_send(fd->fd, &endp->remote,
+			     proto == PROTO_RTP ? endp->net_rtp : endp->net_rtcp,
+			     buf, rc);
+	} else {
+		patch_payload(endp->bts_payload_type, buf, rc);
+		return udp_send(fd->fd, &endp->bts,
+			     proto == PROTO_RTP ? endp->bts_rtp : endp->bts_rtcp,
+			     buf, rc);
+	}
+}
+
+static int create_bind(const char *source_addr, struct bsc_fd *fd, int port)
+{
+	struct sockaddr_in addr;
+	int on = 1;
+
+	fd->fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (fd->fd < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create UDP port.\n");
+		return -1;
+	}
+
+	setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	inet_aton(source_addr, &addr.sin_addr);
+
+	if (bind(fd->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int bind_rtp(struct mgcp_endpoint *endp)
+{
+	struct mgcp_config *cfg = endp->cfg;
+
+	if (create_bind(cfg->source_addr, &endp->local_rtp, endp->rtp_port) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create RTP port: %s:%d on 0x%x\n",
+		       cfg->source_addr, endp->rtp_port, ENDPOINT_NUMBER(endp));
+		goto cleanup0;
+	}
+
+	if (create_bind(cfg->source_addr, &endp->local_rtcp, endp->rtp_port + 1) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create RTCP port: %s:%d on 0x%x\n",
+		       cfg->source_addr, endp->rtp_port + 1, ENDPOINT_NUMBER(endp));
+		goto cleanup1;
+	}
+
+	endp->local_rtp.cb = rtp_data_cb;
+	endp->local_rtp.data = endp;
+	endp->local_rtp.when = BSC_FD_READ;
+	if (bsc_register_fd(&endp->local_rtp) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to register RTP port %d on 0x%x\n",
+			endp->rtp_port, ENDPOINT_NUMBER(endp));
+		goto cleanup2;
+	}
+
+	endp->local_rtcp.cb = rtp_data_cb;
+	endp->local_rtcp.data = endp;
+	endp->local_rtcp.when = BSC_FD_READ;
+	if (bsc_register_fd(&endp->local_rtcp) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to register RTCP port %d on 0x%x\n",
+			endp->rtp_port + 1, ENDPOINT_NUMBER(endp));
+		goto cleanup3;
+	}
+
+	return 0;
+
+cleanup3:
+	bsc_unregister_fd(&endp->local_rtp);
+cleanup2:
+	close(endp->local_rtcp.fd);
+	endp->local_rtcp.fd = -1;
+cleanup1:
+	close(endp->local_rtp.fd);
+	endp->local_rtp.fd = -1;
+cleanup0:
+	return -1;
+}
+
+int mgcp_bind_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	endp->rtp_port = rtp_port;
+	return bind_rtp(endp);
+}
diff --git a/openbsc/src/mgcp/mgcp_protocol.c b/openbsc/src/mgcp/mgcp_protocol.c
new file mode 100644
index 0000000..d82bd68
--- /dev/null
+++ b/openbsc/src/mgcp/mgcp_protocol.c
@@ -0,0 +1,767 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/select.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+enum mgcp_connection_mode {
+	MGCP_CONN_NONE = 0,
+	MGCP_CONN_RECV_ONLY = 1,
+	MGCP_CONN_SEND_ONLY = 2,
+	MGCP_CONN_RECV_SEND = MGCP_CONN_RECV_ONLY | MGCP_CONN_SEND_ONLY,
+};
+
+/**
+ * Macro for tokenizing MGCP messages and SDP in one go.
+ *
+ */
+#define MSG_TOKENIZE_START \
+	line_start = 0;						\
+	for (i = 0; i < msgb_l3len(msg); ++i) {			\
+		/* we have a line end */			\
+		if (msg->l3h[i] == '\n') {			\
+			/* skip the first line */		\
+			if (line_start == 0) {			\
+				line_start = i + 1;		\
+				continue;			\
+			}					\
+								\
+			/* check if we have a proper param */	\
+			if (i - line_start == 1 && msg->l3h[line_start] == '\r') { \
+			} else if (i - line_start > 2		\
+			    && islower(msg->l3h[line_start])	\
+			    && msg->l3h[line_start + 1] == '=') { \
+			} else if (i - line_start < 3		\
+			    || msg->l3h[line_start + 1] != ':'	\
+			    || msg->l3h[line_start + 2] != ' ')	\
+				goto error;			\
+								\
+			msg->l3h[i] = '\0';			\
+			if (msg->l3h[i-1] == '\r')		\
+				msg->l3h[i-1] = '\0';
+
+#define MSG_TOKENIZE_END \
+			line_start = i + 1; \
+		}			    \
+	}
+
+
+struct mgcp_request {
+	char *name;
+	struct msgb *(*handle_request) (struct mgcp_config *cfg, struct msgb *msg);
+	char *debug_name;
+};
+
+#define MGCP_REQUEST(NAME, REQ, DEBUG_NAME) \
+	{ .name = NAME, .handle_request = REQ, .debug_name = DEBUG_NAME },
+
+static struct msgb *handle_audit_endpoint(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_create_con(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_modify_con(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_rsip(struct mgcp_config *cfg, struct msgb *msg);
+
+static int generate_call_id(struct mgcp_config *cfg)
+{
+	int i;
+
+	/* use the call id */
+	++cfg->last_call_id;
+
+	/* handle wrap around */
+	if (cfg->last_call_id == CI_UNUSED)
+		++cfg->last_call_id;
+
+	/* callstack can only be of size number_of_endpoints */
+	/* verify that the call id is free, e.g. in case of overrun */
+	for (i = 1; i < cfg->number_endpoints; ++i)
+		if (cfg->endpoints[i].ci == cfg->last_call_id)
+			return generate_call_id(cfg);
+
+	return cfg->last_call_id;
+}
+
+/*
+ * array of function pointers for handling various
+ * messages. In the future this might be binary sorted
+ * for performance reasons.
+ */
+static const struct mgcp_request mgcp_requests [] = {
+	MGCP_REQUEST("AUEP", handle_audit_endpoint, "AuditEndpoint")
+	MGCP_REQUEST("CRCX", handle_create_con, "CreateConnection")
+	MGCP_REQUEST("DLCX", handle_delete_con, "DeleteConnection")
+	MGCP_REQUEST("MDCX", handle_modify_con, "ModifiyConnection")
+
+	/* SPEC extension */
+	MGCP_REQUEST("RSIP", handle_rsip, "ReSetInProgress")
+};
+
+static struct msgb *mgcp_msgb_alloc(void)
+{
+	struct msgb *msg;
+	msg = msgb_alloc_headroom(4096, 128, "MGCP msg");
+	if (!msg)
+	    LOGP(DMGCP, LOGL_ERROR, "Failed to msgb for MGCP data.\n");
+
+	return msg;
+}
+
+struct msgb *mgcp_create_response_with_data(int code, const char *msg, const char *trans,
+				    const char *data)
+{
+	int len;
+	struct msgb *res;
+
+	res = mgcp_msgb_alloc();
+	if (!res)
+		return NULL;
+
+	if (data) {
+		len = snprintf((char *) res->data, 2048, "%d %s\n%s", code, trans, data);
+	} else {
+		len = snprintf((char *) res->data, 2048, "%d %s\n", code, trans);
+	}
+
+	res->l2h = msgb_put(res, len);
+	LOGP(DMGCP, LOGL_DEBUG, "Sending response: code: %d for '%s'\n", code, res->l2h);
+	return res;
+}
+
+static struct msgb *create_response(int code, const char *msg, const char *trans)
+{
+	return mgcp_create_response_with_data(code, msg, trans, NULL);
+}
+
+static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp,
+					     const char *msg, const char *trans_id)
+{
+	const char *addr = endp->cfg->local_ip;
+	char sdp_record[4096];
+
+	if (!addr)
+		addr = endp->cfg->source_addr;
+
+	snprintf(sdp_record, sizeof(sdp_record) - 1,
+			"I: %d\n\n"
+			"v=0\r\n"
+			"c=IN IP4 %s\r\n"
+			"m=audio %d RTP/AVP %d\r\n"
+			"a=rtpmap:%d %s\r\n",
+			endp->ci, addr, endp->rtp_port,
+			endp->bts_payload_type, endp->bts_payload_type,
+		        endp->cfg->audio_name);
+	return mgcp_create_response_with_data(200, msg, trans_id, sdp_record);
+}
+
+/*
+ * handle incoming messages:
+ *   - this can be a command (four letters, space, transaction id)
+ *   - or a response (three numbers, space, transaction id)
+ */
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg)
+{
+        int code;
+	struct msgb *resp = NULL;
+
+	if (msgb_l2len(msg) < 4) {
+		LOGP(DMGCP, LOGL_ERROR, "mgs too short: %d\n", msg->len);
+		return NULL;
+	}
+
+        /* attempt to treat it as a response */
+        if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) {
+		LOGP(DMGCP, LOGL_DEBUG, "Response: Code: %d\n", code);
+	} else {
+		int i, handled = 0;
+		msg->l3h = &msg->l2h[4];
+		for (i = 0; i < ARRAY_SIZE(mgcp_requests); ++i)
+			if (strncmp(mgcp_requests[i].name, (const char *) &msg->l2h[0], 4) == 0) {
+				handled = 1;
+				resp = mgcp_requests[i].handle_request(cfg, msg);
+				break;
+			}
+		if (!handled) {
+			LOGP(DMGCP, LOGL_NOTICE, "MSG with type: '%.4s' not handled\n", &msg->l2h[0]);
+		}
+	}
+
+	return resp;
+}
+
+/* string tokenizer for the poor */
+static int find_msg_pointers(struct msgb *msg, struct mgcp_msg_ptr *ptrs, int ptrs_length)
+{
+	int i, found = 0;
+
+	int whitespace = 1;
+	for (i = 0; i < msgb_l3len(msg) && ptrs_length > 0; ++i) {
+		/* if we have a space we found an end */
+		if (msg->l3h[i]	== ' ' || msg->l3h[i] == '\r' || msg->l3h[i] == '\n') {
+			if (!whitespace) {
+				++found;
+				whitespace = 1;
+				ptrs->length = i - ptrs->start - 1;
+				++ptrs;
+				--ptrs_length;
+			} else {
+			    /* skip any number of whitespace */
+			}
+
+			/* line end... stop */
+			if (msg->l3h[i] == '\r' || msg->l3h[i] == '\n')
+				break;
+		} else if (msg->l3h[i] == '\r' || msg->l3h[i] == '\n') {
+			/* line end, be done */
+			break;
+		} else if (whitespace) {
+			whitespace = 0;
+			ptrs->start = i;
+		}
+	}
+
+	if (ptrs_length == 0)
+		return -1;
+	return found;
+}
+
+static struct mgcp_endpoint *find_endpoint(struct mgcp_config *cfg, const char *mgcp)
+{
+	char *endptr = NULL;
+	unsigned int gw = INT_MAX;
+
+	gw = strtoul(mgcp, &endptr, 16);
+	if (gw == 0 || gw >= cfg->number_endpoints || strcmp(endptr, "@mgw") != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Not able to find endpoint: '%s'\n", mgcp);
+		return NULL;
+	}
+
+	return &cfg->endpoints[gw];
+}
+
+int mgcp_analyze_header(struct mgcp_config *cfg, struct msgb *msg,
+			struct mgcp_msg_ptr *ptr, int size,
+			const char **transaction_id, struct mgcp_endpoint **endp)
+{
+	int found;
+
+	*transaction_id = "000000";
+
+	if (size < 3) {
+		LOGP(DMGCP, LOGL_ERROR, "Not enough space in ptr\n");
+		return -1;
+	}
+
+	found = find_msg_pointers(msg, ptr, size);
+
+	if (found <= 3) {
+		LOGP(DMGCP, LOGL_ERROR, "Gateway: Not enough params. Found: %d\n", found);
+		return -1;
+	}
+
+	/*
+	 * replace the space with \0. the main method gurantess that
+	 * we still have + 1 for null termination
+	 */
+	msg->l3h[ptr[3].start + ptr[3].length + 1] = '\0';
+	msg->l3h[ptr[2].start + ptr[2].length + 1] = '\0';
+	msg->l3h[ptr[1].start + ptr[1].length + 1] = '\0';
+	msg->l3h[ptr[0].start + ptr[0].length + 1] = '\0';
+
+	if (strncmp("1.0", (const char *)&msg->l3h[ptr[3].start], 3) != 0
+	    || strncmp("MGCP", (const char *)&msg->l3h[ptr[2].start], 4) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Wrong MGCP version. Not handling: '%s' '%s'\n",
+			(const char *)&msg->l3h[ptr[3].start],
+			(const char *)&msg->l3h[ptr[2].start]);
+		return -1;
+	}
+
+	*transaction_id = (const char *)&msg->l3h[ptr[0].start];
+	if (endp) {
+		*endp = find_endpoint(cfg, (const char *)&msg->l3h[ptr[1].start]);
+		return *endp == NULL;
+	}
+	return 0;
+}
+
+static int verify_call_id(const struct mgcp_endpoint *endp,
+			  const char *callid)
+{
+	if (strcmp(endp->callid, callid) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "CallIDs does not match on 0x%x. '%s' != '%s'\n",
+			ENDPOINT_NUMBER(endp), endp->callid, callid);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int verify_ci(const struct mgcp_endpoint *endp,
+		     const char *ci)
+{
+	if (atoi(ci) != endp->ci) {
+		LOGP(DMGCP, LOGL_ERROR, "ConnectionIdentifiers do not match on 0x%x. %d != %s\n",
+			ENDPOINT_NUMBER(endp), endp->ci, ci);
+		return -1;
+	}
+
+	return 0;
+}
+
+static struct msgb *handle_audit_endpoint(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	int found, response;
+	const char *trans_id;
+	struct mgcp_endpoint *endp;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+	    response = 500;
+	else
+	    response = 200;
+
+	return create_response(response, "AUEP", trans_id);
+}
+
+static int parse_conn_mode(const char* msg, int *conn_mode)
+{
+	int ret = 0;
+	if (strcmp(msg, "recvonly") == 0)
+		*conn_mode = MGCP_CONN_RECV_ONLY;
+	else if (strcmp(msg, "sendrecv") == 0)
+		*conn_mode = MGCP_CONN_RECV_SEND;
+	else {
+		LOGP(DMGCP, LOGL_ERROR, "Unknown connection mode: '%s'\n", msg);
+		ret = -1;
+	}
+
+	return ret;
+}
+
+static struct msgb *handle_create_con(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	int found, i, line_start;
+	const char *trans_id;
+	struct mgcp_endpoint *endp;
+	int error_code = 500;
+	int port;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+		return create_response(500, "CRCX", trans_id);
+
+	if (endp->ci != CI_UNUSED) {
+		if (cfg->force_realloc) {
+			LOGP(DMGCP, LOGL_NOTICE, "Endpoint 0x%x already allocated. Forcing realloc.\n",
+			    ENDPOINT_NUMBER(endp));
+			mgcp_free_endp(endp);
+		} else {
+			LOGP(DMGCP, LOGL_ERROR, "Endpoint is already used. 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			return create_response(500, "CRCX", trans_id);
+		}
+	}
+
+	/* parse CallID C: and LocalParameters L: */
+	MSG_TOKENIZE_START
+	switch (msg->l3h[line_start]) {
+	case 'L':
+		endp->local_options = talloc_strdup(cfg->endpoints,
+			(const char *)&msg->l3h[line_start + 3]);
+		break;
+	case 'C':
+		endp->callid = talloc_strdup(cfg->endpoints,
+			(const char *)&msg->l3h[line_start + 3]);
+		break;
+	case 'M':
+		if (parse_conn_mode((const char *)&msg->l3h[line_start + 3],
+			    &endp->conn_mode) != 0) {
+		    error_code = 517;
+		    goto error2;
+		}
+		break;
+	default:
+		LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+			msg->l3h[line_start], msg->l3h[line_start],
+			ENDPOINT_NUMBER(endp));
+		break;
+	}
+	MSG_TOKENIZE_END
+
+	/* initialize */
+	endp->net_rtp = endp->net_rtcp = endp->bts_rtp = endp->bts_rtcp = 0;
+
+	/* set to zero until we get the info */
+	memset(&endp->remote, 0, sizeof(endp->remote));
+
+	/* bind to the port now */
+	port = rtp_calculate_port(ENDPOINT_NUMBER(endp), cfg->rtp_base_port);
+	if (cfg->early_bind)
+		endp->rtp_port = port;
+	else if (mgcp_bind_rtp_port(endp, port) != 0)
+		goto error2;
+
+	/* assign a local call identifier or fail */
+	endp->ci = generate_call_id(cfg);
+	if (endp->ci == CI_UNUSED)
+		goto error2;
+
+	endp->bts_payload_type = cfg->audio_payload;
+
+	/* policy CB */
+	if (cfg->policy_cb) {
+		switch (cfg->policy_cb(cfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX, trans_id)) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "CRCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			mgcp_free_endp(endp);
+			return create_response(500, "CRCX", trans_id);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	LOGP(DMGCP, LOGL_NOTICE, "Creating endpoint on: 0x%x CI: %u port: %u\n",
+		ENDPOINT_NUMBER(endp), endp->ci, endp->rtp_port);
+	if (cfg->change_cb)
+		cfg->change_cb(cfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX, endp->rtp_port);
+
+	return create_response_with_sdp(endp, "CRCX", trans_id);
+error:
+	LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d\n",
+		    hexdump(msg->l3h, msgb_l3len(msg)),
+		    ENDPOINT_NUMBER(endp), line_start, i);
+	return create_response(error_code, "CRCX", trans_id);
+
+error2:
+	LOGP(DMGCP, LOGL_NOTICE, "Resource error on 0x%x\n", ENDPOINT_NUMBER(endp));
+	return create_response(error_code, "CRCX", trans_id);
+}
+
+static struct msgb *handle_modify_con(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	int found, i, line_start;
+	const char *trans_id;
+	struct mgcp_endpoint *endp;
+	int error_code = 500;
+	int silent = 0;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+		return create_response(error_code, "MDCX", trans_id);
+
+	if (endp->ci == CI_UNUSED) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint is not holding a connection. 0x%x\n", ENDPOINT_NUMBER(endp));
+		return create_response(error_code, "MDCX", trans_id);
+	}
+
+	MSG_TOKENIZE_START
+	switch (msg->l3h[line_start]) {
+	case 'C': {
+		if (verify_call_id(endp, (const char *)&msg->l3h[line_start + 3]) != 0)
+			goto error3;
+		break;
+	}
+	case 'I': {
+		if (verify_ci(endp, (const char *)&msg->l3h[line_start + 3]) != 0)
+			goto error3;
+		break;
+	}
+	case 'L':
+		/* skip */
+		break;
+	case 'M':
+		if (parse_conn_mode((const char *)&msg->l3h[line_start + 3],
+			    &endp->conn_mode) != 0) {
+		    error_code = 517;
+		    goto error3;
+		}
+		break;
+	case 'Z':
+		silent = strcmp("noanswer", (const char *)&msg->l3h[line_start + 3]) == 0;
+		break;
+	case '\0':
+		/* SDP file begins */
+		break;
+	case 'a':
+	case 'o':
+	case 's':
+	case 't':
+	case 'v':
+		/* skip these SDP attributes */
+		break;
+	case 'm': {
+		int port;
+		int payload;
+		const char *param = (const char *)&msg->l3h[line_start];
+
+		if (sscanf(param, "m=audio %d RTP/AVP %d", &port, &payload) == 2) {
+			endp->net_rtp = htons(port);
+			endp->net_rtcp = htons(port + 1);
+			endp->net_payload_type = payload;
+		}
+		break;
+	}
+	case 'c': {
+		char ipv4[16];
+		const char *param = (const char *)&msg->l3h[line_start];
+
+		if (sscanf(param, "c=IN IP4 %15s", ipv4) == 1) {
+			inet_aton(ipv4, &endp->remote);
+		}
+		break;
+	}
+	default:
+		LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+			msg->l3h[line_start], msg->l3h[line_start],
+			ENDPOINT_NUMBER(endp));
+		break;
+	}
+	MSG_TOKENIZE_END
+
+	/* policy CB */
+	if (cfg->policy_cb) {
+		switch (cfg->policy_cb(cfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX, trans_id)) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "MDCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			if (silent)
+				goto out_silent;
+			return create_response(500, "MDCX", trans_id);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	/* modify */
+	LOGP(DMGCP, LOGL_NOTICE, "Modified endpoint on: 0x%x Server: %s:%u\n",
+		ENDPOINT_NUMBER(endp), inet_ntoa(endp->remote), ntohs(endp->net_rtp));
+	if (cfg->change_cb)
+		cfg->change_cb(cfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX, endp->rtp_port);
+	if (silent)
+		goto out_silent;
+
+	return create_response_with_sdp(endp, "MDCX", trans_id);
+
+error:
+	LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d %d\n",
+		    hexdump(msg->l3h, msgb_l3len(msg)),
+		    ENDPOINT_NUMBER(endp), line_start, i, msg->l3h[line_start]);
+	return create_response(error_code, "MDCX", trans_id);
+
+error3:
+	return create_response(error_code, "MDCX", trans_id);
+
+
+out_silent:
+	return NULL;
+}
+
+static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	int found, i, line_start;
+	const char *trans_id;
+	struct mgcp_endpoint *endp;
+	int error_code = 500;
+	int silent = 0;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+		return create_response(error_code, "DLCX", trans_id);
+
+	if (endp->ci == CI_UNUSED) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", ENDPOINT_NUMBER(endp));
+		return create_response(error_code, "DLCX", trans_id);
+	}
+
+	MSG_TOKENIZE_START
+	switch (msg->l3h[line_start]) {
+	case 'C': {
+		if (verify_call_id(endp, (const char *)&msg->l3h[line_start + 3]) != 0)
+			goto error3;
+		break;
+	}
+	case 'I': {
+		if (verify_ci(endp, (const char *)&msg->l3h[line_start + 3]) != 0)
+			goto error3;
+		break;
+	case 'Z':
+		silent = strcmp("noanswer", (const char *)&msg->l3h[line_start + 3]) == 0;
+		break;
+	}
+	default:
+		LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+			msg->l3h[line_start], msg->l3h[line_start],
+			ENDPOINT_NUMBER(endp));
+		break;
+	}
+	MSG_TOKENIZE_END
+
+	/* policy CB */
+	if (cfg->policy_cb) {
+		switch (cfg->policy_cb(cfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX, trans_id)) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "DLCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			if (silent)
+				goto out_silent;
+			return create_response(500, "DLCX", trans_id);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	/* free the connection */
+	LOGP(DMGCP, LOGL_NOTICE, "Deleted endpoint on: 0x%x Server: %s:%u\n",
+		ENDPOINT_NUMBER(endp), inet_ntoa(endp->remote), ntohs(endp->net_rtp));
+	mgcp_free_endp(endp);
+	if (cfg->change_cb)
+		cfg->change_cb(cfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX, endp->rtp_port);
+
+	if (silent)
+		goto out_silent;
+	return create_response(250, "DLCX", trans_id);
+
+error:
+	LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d\n",
+		    hexdump(msg->l3h, msgb_l3len(msg)),
+		    ENDPOINT_NUMBER(endp), line_start, i);
+	return create_response(error_code, "DLCX", trans_id);
+
+error3:
+	return create_response(error_code, "DLCX", trans_id);
+
+out_silent:
+	return NULL;
+}
+
+static struct msgb *handle_rsip(struct mgcp_config *cfg, struct msgb *msg)
+{
+	if (cfg->reset_cb)
+		cfg->reset_cb(cfg);
+	return NULL;
+}
+
+struct mgcp_config *mgcp_config_alloc(void)
+{
+	struct mgcp_config *cfg;
+
+	cfg = talloc_zero(NULL, struct mgcp_config);
+	if (!cfg) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to allocate config.\n");
+		return NULL;
+	}
+
+	cfg->source_port = 2427;
+	cfg->source_addr = talloc_strdup(cfg, "0.0.0.0");
+	cfg->audio_name = talloc_strdup(cfg, "GSM-EFR/8000");
+	cfg->audio_payload = 97;
+	cfg->rtp_base_port = RTP_PORT_DEFAULT;
+
+	return cfg;
+}
+
+int mgcp_endpoints_allocate(struct mgcp_config *cfg)
+{
+	int i;
+
+	/* Initialize all endpoints */
+	cfg->endpoints = _talloc_zero_array(cfg,
+				       sizeof(struct mgcp_endpoint),
+				       cfg->number_endpoints, "endpoints");
+	if (!cfg->endpoints)
+		return -1;
+
+	for (i = 0; i < cfg->number_endpoints; ++i) {
+		cfg->endpoints[i].local_rtp.fd = -1;
+		cfg->endpoints[i].local_rtcp.fd = -1;
+		cfg->endpoints[i].ci = CI_UNUSED;
+		cfg->endpoints[i].cfg = cfg;
+		cfg->endpoints[i].net_payload_type = -1;
+		cfg->endpoints[i].bts_payload_type = -1;
+	}
+
+	return 0;
+}
+
+void mgcp_free_endp(struct mgcp_endpoint *endp)
+{
+	LOGP(DMGCP, LOGL_DEBUG, "Deleting endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp));
+	endp->ci= CI_UNUSED;
+
+	if (endp->callid) {
+		talloc_free(endp->callid);
+		endp->callid = NULL;
+	}
+
+	if (endp->local_options) {
+		talloc_free(endp->local_options);
+		endp->local_options = NULL;
+	}
+
+	if (!endp->cfg->early_bind) {
+		bsc_unregister_fd(&endp->local_rtp);
+		bsc_unregister_fd(&endp->local_rtcp);
+	}
+
+	endp->net_rtp = endp->net_rtcp = endp->bts_rtp = endp->bts_rtcp = 0;
+	endp->net_payload_type = endp->bts_payload_type = -1;
+	endp->in_bts = endp->in_remote = 0;
+	memset(&endp->remote, 0, sizeof(endp->remote));
+	memset(&endp->bts, 0, sizeof(endp->bts));
+}
diff --git a/openbsc/src/mgcp/mgcp_vty.c b/openbsc/src/mgcp/mgcp_vty.c
new file mode 100644
index 0000000..5b90d52
--- /dev/null
+++ b/openbsc/src/mgcp/mgcp_vty.c
@@ -0,0 +1,354 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include <vty/command.h>
+#include <vty/vty.h>
+
+#include <string.h>
+
+static struct mgcp_config *g_cfg = NULL;
+
+/*
+ * vty code for mgcp below
+ */
+struct cmd_node mgcp_node = {
+	MGCP_NODE,
+	"%s(mgcp)#",
+	1,
+};
+
+static int config_write_mgcp(struct vty *vty)
+{
+	vty_out(vty, "mgcp%s", VTY_NEWLINE);
+	if (g_cfg->local_ip)
+		vty_out(vty, "  local ip %s%s", g_cfg->local_ip, VTY_NEWLINE);
+	if (g_cfg->bts_ip && strlen(g_cfg->bts_ip) != 0)
+		vty_out(vty, "  bts ip %s%s", g_cfg->bts_ip, VTY_NEWLINE);
+	vty_out(vty, "  bind ip %s%s", g_cfg->source_addr, VTY_NEWLINE);
+	vty_out(vty, "  bind port %u%s", g_cfg->source_port, VTY_NEWLINE);
+	vty_out(vty, "  bind early %u%s", !!g_cfg->early_bind, VTY_NEWLINE);
+	vty_out(vty, "  rtp base %u%s", g_cfg->rtp_base_port, VTY_NEWLINE);
+	if (g_cfg->audio_payload != -1)
+		vty_out(vty, "  sdp audio payload number %d%s", g_cfg->audio_payload, VTY_NEWLINE);
+	if (g_cfg->audio_name)
+		vty_out(vty, "  sdp audio payload name %s%s", g_cfg->audio_name, VTY_NEWLINE);
+	vty_out(vty, "  loop %u%s", !!g_cfg->audio_loop, VTY_NEWLINE);
+	vty_out(vty, "  number endpoints %u%s", g_cfg->number_endpoints - 1, VTY_NEWLINE);
+	if (g_cfg->forward_ip)
+		vty_out(vty, "  forward audio ip %s%s", g_cfg->forward_ip, VTY_NEWLINE);
+	if (g_cfg->forward_port != 0)
+		vty_out(vty, "  forward audio port %d%s", g_cfg->forward_port, VTY_NEWLINE);
+	if (g_cfg->call_agent_addr)
+		vty_out(vty, "  call agent ip %s%s", g_cfg->call_agent_addr, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_mcgp, show_mgcp_cmd, "show mgcp",
+      SHOW_STR "Display information about the MGCP Media Gateway")
+{
+	int i;
+
+	vty_out(vty, "MGCP is up and running with %u endpoints:%s", g_cfg->number_endpoints - 1, VTY_NEWLINE);
+	for (i = 1; i < g_cfg->number_endpoints; ++i) {
+		struct mgcp_endpoint *endp = &g_cfg->endpoints[i];
+		vty_out(vty, " Endpoint 0x%.2x: CI: %d net: %u/%u bts: %u/%u on %s traffic received bts: %u  remote: %u%s",
+			i, endp->ci,
+			ntohs(endp->net_rtp), ntohs(endp->net_rtcp),
+			ntohs(endp->bts_rtp), ntohs(endp->bts_rtcp),
+			inet_ntoa(endp->bts), endp->in_bts, endp->in_remote,
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp,
+      cfg_mgcp_cmd,
+      "mgcp",
+      "Configure the MGCP")
+{
+	vty->node = MGCP_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_local_ip,
+      cfg_mgcp_local_ip_cmd,
+      "local ip IP",
+      "Set the IP to be used in SDP records")
+{
+	if (g_cfg->local_ip)
+		talloc_free(g_cfg->local_ip);
+	g_cfg->local_ip = talloc_strdup(g_cfg, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bts_ip,
+      cfg_mgcp_bts_ip_cmd,
+      "bts ip IP",
+      "Set the IP of the BTS for RTP forwarding")
+{
+	if (g_cfg->bts_ip)
+		talloc_free(g_cfg->bts_ip);
+	g_cfg->bts_ip = talloc_strdup(g_cfg, argv[0]);
+	inet_aton(g_cfg->bts_ip, &g_cfg->bts_in);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_ip,
+      cfg_mgcp_bind_ip_cmd,
+      "bind ip IP",
+      "Bind the MGCP to this local addr")
+{
+	if (g_cfg->source_addr)
+		talloc_free(g_cfg->source_addr);
+	g_cfg->source_addr = talloc_strdup(g_cfg, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_port,
+      cfg_mgcp_bind_port_cmd,
+      "bind port <0-65534>",
+      "Bind the MGCP to this port")
+{
+	unsigned int port = atoi(argv[0]);
+	if (port > 65534) {
+		vty_out(vty, "%% wrong bind port '%s'%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	g_cfg->source_port = port;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_early,
+      cfg_mgcp_bind_early_cmd,
+      "bind early (0|1)",
+      "Bind all RTP ports early")
+{
+	unsigned int bind = atoi(argv[0]);
+	if (bind != 0 && bind != 1) {
+		vty_out(vty, "%% param must be 0 or 1.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	g_cfg->early_bind = bind == 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_base_port,
+      cfg_mgcp_rtp_base_port_cmd,
+      "rtp base <0-65534>",
+      "Base port to use")
+{
+	unsigned int port = atoi(argv[0]);
+	if (port > 65534) {
+		vty_out(vty, "%% wrong base port '%s'%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	g_cfg->rtp_base_port = port;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_sdp_payload_number,
+      cfg_mgcp_sdp_payload_number_cmd,
+      "sdp audio payload number <1-255>",
+      "Set the audio codec to use")
+{
+	unsigned int payload = atoi(argv[0]);
+	if (payload > 255) {
+		vty_out(vty, "%% wrong payload number '%s'%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	g_cfg->audio_payload = payload;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_sdp_payload_name,
+      cfg_mgcp_sdp_payload_name_cmd,
+      "sdp audio payload name NAME",
+      "Set the audio name to use")
+{
+	if (g_cfg->audio_name)
+		talloc_free(g_cfg->audio_name);
+	g_cfg->audio_name = talloc_strdup(g_cfg, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_loop,
+      cfg_mgcp_loop_cmd,
+      "loop (0|1)",
+      "Loop the audio")
+{
+	g_cfg->audio_loop = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_number_endp,
+      cfg_mgcp_number_endp_cmd,
+      "number endpoints <0-65534>",
+      "The number of endpoints to allocate. This is not dynamic.")
+{
+	/* + 1 as we start counting at one */
+	g_cfg->number_endpoints = atoi(argv[0]) + 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_forward_ip,
+      cfg_mgcp_forward_ip_cmd,
+      "forward audio ip IP",
+      "Forward packets from and to the IP. This disables most of the MGCP feature.")
+{
+	if (g_cfg->forward_ip)
+		talloc_free(g_cfg->forward_ip);
+	g_cfg->forward_ip = talloc_strdup(g_cfg, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_forward_port,
+      cfg_mgcp_forward_port_cmd,
+      "forward audio port <1-15000>",
+      "Forward packets from and to the port. This disables most of the MGCP feature.")
+{
+	g_cfg->forward_port = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_agent_addr,
+      cfg_mgcp_agent_addr_cmd,
+      "call agent ip IP",
+      "Set the address of the call agent.")
+{
+	if (g_cfg->call_agent_addr)
+		talloc_free(g_cfg->call_agent_addr);
+	g_cfg->call_agent_addr = talloc_strdup(g_cfg, argv[0]);
+	return CMD_SUCCESS;
+}
+
+int mgcp_vty_init(void)
+{
+	install_element(VIEW_NODE, &show_mgcp_cmd);
+
+	install_element(CONFIG_NODE, &cfg_mgcp_cmd);
+	install_node(&mgcp_node, config_write_mgcp);
+	install_default(MGCP_NODE);
+	install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bts_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_early_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_base_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_loop_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_number_endp_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_forward_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_forward_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd);
+	return 0;
+}
+
+int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg)
+{
+	int i, rc;
+
+	g_cfg = cfg;
+	rc = vty_read_config_file(config_file);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+
+	if (!g_cfg->bts_ip)
+		fprintf(stderr, "No BTS ip address specified. This will allow everyone to connect.\n");
+
+	if (!g_cfg->source_addr) {
+		fprintf(stderr, "You need to specify a bind address.\n");
+		return -1;
+	}
+
+	if (mgcp_endpoints_allocate(g_cfg) != 0) {
+		fprintf(stderr, "Failed to allocate endpoints: %d. Quitting.\n", g_cfg->number_endpoints);
+		return -1;
+	}
+
+	/*
+	 * This application supports two modes.
+	 *    1.) a true MGCP gateway with support for AUEP, CRCX, MDCX, DLCX
+	 *    2.) plain forwarding of RTP packets on the endpoints.
+	 * both modes are mutual exclusive
+	 */
+	if (g_cfg->forward_ip) {
+		int port = g_cfg->rtp_base_port;
+		if (g_cfg->forward_port != 0)
+			port = g_cfg->forward_port;
+
+		if (!g_cfg->early_bind) {
+			LOGP(DMGCP, LOGL_NOTICE, "Forwarding requires early bind.\n");
+			return -1;
+		}
+
+		/*
+		 * Store the forward IP and assign a ci. For early bind
+		 * the sockets will be created after this.
+		 */
+		for (i = 1; i < g_cfg->number_endpoints; ++i) {
+			struct mgcp_endpoint *endp = &g_cfg->endpoints[i];
+			inet_aton(g_cfg->forward_ip, &endp->remote);
+			endp->ci = CI_UNUSED + 23;
+			endp->net_rtp = htons(rtp_calculate_port(ENDPOINT_NUMBER(endp), port));
+			endp->net_rtcp = htons(rtp_calculate_port(ENDPOINT_NUMBER(endp), port) + 1);
+		}
+
+		LOGP(DMGCP, LOGL_NOTICE, "Configured for Audio Forwarding.\n");
+	}
+
+	/* early bind */
+	if (g_cfg->early_bind) {
+		for (i = 1; i < g_cfg->number_endpoints; ++i) {
+			struct mgcp_endpoint *endp = &g_cfg->endpoints[i];
+			int rtp_port;
+
+			rtp_port = rtp_calculate_port(ENDPOINT_NUMBER(endp), g_cfg->rtp_base_port);
+			if (mgcp_bind_rtp_port(endp, rtp_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port);
+				return -1;
+			}
+		}
+	}
+
+	return !!g_cfg->forward_ip;
+}
+
diff --git a/openbsc/src/mncc.c b/openbsc/src/mncc.c
new file mode 100644
index 0000000..afd5364
--- /dev/null
+++ b/openbsc/src/mncc.c
@@ -0,0 +1,468 @@
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+void *tall_call_ctx;
+
+static struct mncc_names {
+	char *name;
+	int value;
+} mncc_names[] = {
+	{"MNCC_SETUP_REQ",	0x0101},
+	{"MNCC_SETUP_IND",	0x0102},
+	{"MNCC_SETUP_RSP",	0x0103},
+	{"MNCC_SETUP_CNF",	0x0104},
+	{"MNCC_SETUP_COMPL_REQ",0x0105},
+	{"MNCC_SETUP_COMPL_IND",0x0106},
+	{"MNCC_CALL_CONF_IND",	0x0107},
+	{"MNCC_CALL_PROC_REQ",	0x0108},
+	{"MNCC_PROGRESS_REQ",	0x0109},
+	{"MNCC_ALERT_REQ",	0x010a},
+	{"MNCC_ALERT_IND",	0x010b},
+	{"MNCC_NOTIFY_REQ",	0x010c},
+	{"MNCC_NOTIFY_IND",	0x010d},
+	{"MNCC_DISC_REQ",	0x010e},
+	{"MNCC_DISC_IND",	0x010f},
+	{"MNCC_REL_REQ",	0x0110},
+	{"MNCC_REL_IND",	0x0111},
+	{"MNCC_REL_CNF",	0x0112},
+	{"MNCC_FACILITY_REQ",	0x0113},
+	{"MNCC_FACILITY_IND",	0x0114},
+	{"MNCC_START_DTMF_IND",	0x0115},
+	{"MNCC_START_DTMF_RSP",	0x0116},
+	{"MNCC_START_DTMF_REJ",	0x0117},
+	{"MNCC_STOP_DTMF_IND",	0x0118},
+	{"MNCC_STOP_DTMF_RSP",	0x0119},
+	{"MNCC_MODIFY_REQ",	0x011a},
+	{"MNCC_MODIFY_IND",	0x011b},
+	{"MNCC_MODIFY_RSP",	0x011c},
+	{"MNCC_MODIFY_CNF",	0x011d},
+	{"MNCC_MODIFY_REJ",	0x011e},
+	{"MNCC_HOLD_IND",	0x011f},
+	{"MNCC_HOLD_CNF",	0x0120},
+	{"MNCC_HOLD_REJ",	0x0121},
+	{"MNCC_RETRIEVE_IND",	0x0122},
+	{"MNCC_RETRIEVE_CNF",	0x0123},
+	{"MNCC_RETRIEVE_REJ",	0x0124},
+	{"MNCC_USERINFO_REQ",	0x0125},
+	{"MNCC_USERINFO_IND",	0x0126},
+	{"MNCC_REJ_REQ",	0x0127},
+	{"MNCC_REJ_IND",	0x0128},
+
+	{"MNCC_BRIDGE",		0x0200},
+	{"MNCC_FRAME_RECV",	0x0201},
+	{"MNCC_FRAME_DROP",	0x0202},
+	{"MNCC_LCHAN_MODIFY",	0x0203},
+
+	{"GSM_TCH_FRAME",	0x0300},
+
+	{NULL, 0} };
+
+static LLIST_HEAD(call_list);
+
+static u_int32_t new_callref = 0x00000001;
+
+char *get_mncc_name(int value)
+{
+	int i;
+
+	for (i = 0; mncc_names[i].name; i++) {
+		if (mncc_names[i].value == value)
+			return mncc_names[i].name;
+	}
+
+	return "MNCC_Unknown";
+}
+
+static void free_call(struct gsm_call *call)
+{
+	llist_del(&call->entry);
+	DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref);
+	talloc_free(call);
+}
+
+
+struct gsm_call *get_call_ref(u_int32_t callref)
+{
+	struct gsm_call *callt;
+
+	llist_for_each_entry(callt, &call_list, entry) {
+		if (callt->callref == callref)
+			return callt;
+	}
+	return NULL;
+}
+
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val)
+{
+	data->fields |= MNCC_F_CAUSE;
+	data->cause.location = loc;
+	data->cause.value = val;
+}
+
+/* on incoming call, look up database and send setup to remote subscr. */
+static int mncc_setup_ind(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *setup)
+{
+	struct gsm_mncc mncc;
+	struct gsm_call *remote;
+
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+
+	/* already have remote call */
+	if (call->remote_ref)
+		return 0;
+	
+	/* transfer mode 1 would be packet mode, which was never specified */
+	if (setup->bearer_cap.mode != 0) {
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support "
+			"packet mode\n", call->callref);
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		goto out_reject;
+	}
+
+	/* we currently only do speech */
+	if (setup->bearer_cap.transfer != GSM_MNCC_BCAP_SPEECH) {
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) We only support "
+			"voice calls\n", call->callref);
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		goto out_reject;
+	}
+
+	/* create remote call */
+	if (!(remote = talloc(tall_call_ctx, struct gsm_call))) {
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		goto out_reject;
+	}
+	llist_add_tail(&remote->entry, &call_list);
+	remote->net = call->net;
+	remote->callref = new_callref++;
+	DEBUGP(DMNCC, "(call %x) Creating new remote instance %x.\n",
+		call->callref, remote->callref);
+
+	/* link remote call */
+	call->remote_ref = remote->callref;
+	remote->remote_ref = call->callref;
+
+	/* modify mode */
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+	mncc.lchan_mode = GSM48_CMODE_SPEECH_EFR;
+	DEBUGP(DMNCC, "(call %x) Modify channel mode.\n", call->callref);
+	mncc_send(call->net, MNCC_LCHAN_MODIFY, &mncc);
+
+	/* send call proceeding */
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+	DEBUGP(DMNCC, "(call %x) Accepting call.\n", call->callref);
+	mncc_send(call->net, MNCC_CALL_PROC_REQ, &mncc);
+
+	/* send setup to remote */
+//	setup->fields |= MNCC_F_SIGNAL;
+//	setup->signal = GSM48_SIGNAL_DIALTONE;
+	setup->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding SETUP to remote.\n", call->callref);
+	return mncc_send(remote->net, MNCC_SETUP_REQ, setup);
+
+out_reject:
+	mncc_send(call->net, MNCC_REJ_REQ, &mncc);
+	free_call(call);
+	return 0;
+}
+
+static int mncc_alert_ind(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *alert)
+{
+	struct gsm_call *remote;
+
+	/* send alerting to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	alert->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding ALERT to remote.\n", call->callref);
+	return mncc_send(remote->net, MNCC_ALERT_REQ, alert);
+}
+
+static int mncc_notify_ind(struct gsm_call *call, int msg_type,
+			   struct gsm_mncc *notify)
+{
+	struct gsm_call *remote;
+
+	/* send notify to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	notify->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding NOTIF to remote.\n", call->callref);
+	return mncc_send(remote->net, MNCC_NOTIFY_REQ, notify);
+}
+
+static int mncc_setup_cnf(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *connect)
+{
+	struct gsm_mncc connect_ack, frame_recv;
+	struct gsm_network *net = call->net;
+	struct gsm_call *remote;
+	u_int32_t refs[2];
+
+	/* acknowledge connect */
+	memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+	connect_ack.callref = call->callref;
+	DEBUGP(DMNCC, "(call %x) Acknowledge SETUP.\n", call->callref);
+	mncc_send(call->net, MNCC_SETUP_COMPL_REQ, &connect_ack);
+
+	/* send connect message to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	connect->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Sending CONNECT to remote.\n", call->callref);
+	mncc_send(remote->net, MNCC_SETUP_RSP, connect);
+
+	/* bridge tch */
+	refs[0] = call->callref;
+	refs[1] = call->remote_ref;
+	DEBUGP(DMNCC, "(call %x) Bridging with remote.\n", call->callref);
+
+	/* in direct mode, we always have to bridge the channels */
+	if (ipacc_rtp_direct)
+		return mncc_send(call->net, MNCC_BRIDGE, refs);
+
+	/* proxy mode */
+	if (!net->handover.active) {
+		/* in the no-handover case, we can bridge, i.e. use
+		 * the old RTP proxy code */
+		return mncc_send(call->net, MNCC_BRIDGE, refs);
+	} else {
+		/* in case of handover, we need to re-write the RTP
+		 * SSRC, sequence and timestamp values and thus
+		 * need to enable RTP receive for both directions */
+		memset(&frame_recv, 0, sizeof(struct gsm_mncc));
+		frame_recv.callref = call->callref;
+		mncc_send(call->net, MNCC_FRAME_RECV, &frame_recv);
+		frame_recv.callref = call->remote_ref;
+		return mncc_send(call->net, MNCC_FRAME_RECV, &frame_recv);
+	}
+}
+
+static int mncc_disc_ind(struct gsm_call *call, int msg_type,
+			 struct gsm_mncc *disc)
+{
+	struct gsm_call *remote;
+
+	/* send release */
+	DEBUGP(DMNCC, "(call %x) Releasing call with cause %d\n",
+		call->callref, disc->cause.value);
+	mncc_send(call->net, MNCC_REL_REQ, disc);
+
+	/* send disc to remote */
+	if (!(remote = get_call_ref(call->remote_ref))) {
+		return 0;
+	}
+	disc->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Disconnecting remote with cause %d\n",
+		remote->callref, disc->cause.value);
+	return mncc_send(remote->net, MNCC_DISC_REQ, disc);
+}
+
+static int mncc_rel_ind(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+	struct gsm_call *remote;
+
+	/* send release to remote */
+	if (!(remote = get_call_ref(call->remote_ref))) {
+		free_call(call);
+		return 0;
+	}
+	rel->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Releasing remote with cause %d\n",
+		call->callref, rel->cause.value);
+	mncc_send(remote->net, MNCC_REL_REQ, rel);
+
+	free_call(call);
+
+	return 0;
+}
+
+static int mncc_rel_cnf(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+	free_call(call);
+	return 0;
+}
+
+/* receiving a TCH/F frame from the BSC code */
+static int mncc_rcv_tchf(struct gsm_call *call, int msg_type,
+			 struct gsm_data_frame *dfr)
+{
+	struct gsm_trans *remote_trans;
+
+	remote_trans = trans_find_by_callref(call->net, call->remote_ref);
+
+	/* this shouldn't really happen */
+	if (!remote_trans || !remote_trans->conn) {
+		LOGP(DMNCC, LOGL_ERROR, "No transaction or transaction without lchan?!?\n");
+		return -EIO;
+	}
+
+	/* RTP socket of remote end has meanwhile died */
+	if (!remote_trans->conn->lchan->abis_ip.rtp_socket)
+		return -EIO;
+
+	return rtp_send_frame(remote_trans->conn->lchan->abis_ip.rtp_socket, dfr);
+}
+
+
+int mncc_recv(struct gsm_network *net, int msg_type, void *arg)
+{
+	struct gsm_mncc *data = arg;
+	int callref;
+	struct gsm_call *call = NULL, *callt;
+	int rc = 0;
+
+	/* Special messages */
+	switch(msg_type) {
+	}
+	
+	/* find callref */
+	callref = data->callref;
+	llist_for_each_entry(callt, &call_list, entry) {
+		if (callt->callref == callref) {
+			call = callt;
+			break;
+		}
+	}
+
+	/* create callref, if setup is received */
+	if (!call) {
+		if (msg_type != MNCC_SETUP_IND)
+			return 0; /* drop */
+		/* create call */
+		if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) {
+			struct gsm_mncc rel;
+			
+			memset(&rel, 0, sizeof(struct gsm_mncc));
+			rel.callref = callref;
+			mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+				       GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+			mncc_send(net, MNCC_REL_REQ, &rel);
+			return 0;
+		}
+		llist_add_tail(&call->entry, &call_list);
+		call->net = net;
+		call->callref = callref;
+		DEBUGP(DMNCC, "(call %x) Call created.\n", call->callref);
+	}
+
+	switch (msg_type) {
+	case GSM_TCHF_FRAME:
+	case GSM_TCHF_FRAME_EFR:
+		break;
+	default:
+		DEBUGP(DMNCC, "(call %x) Received message %s\n", call->callref,
+			get_mncc_name(msg_type));
+		break;
+	}
+
+	switch(msg_type) {
+	case MNCC_SETUP_IND:
+		rc = mncc_setup_ind(call, msg_type, arg);
+		break;
+	case MNCC_SETUP_CNF:
+		rc = mncc_setup_cnf(call, msg_type, arg);
+		break;
+	case MNCC_SETUP_COMPL_IND:
+		break;
+	case MNCC_CALL_CONF_IND:
+		/* we now need to MODIFY the channel */
+		data->lchan_mode = GSM48_CMODE_SPEECH_EFR;
+		mncc_send(call->net, MNCC_LCHAN_MODIFY, data);
+		break;
+	case MNCC_ALERT_IND:
+		rc = mncc_alert_ind(call, msg_type, arg);
+		break;
+	case MNCC_NOTIFY_IND:
+		rc = mncc_notify_ind(call, msg_type, arg);
+		break;
+	case MNCC_DISC_IND:
+		rc = mncc_disc_ind(call, msg_type, arg);
+		break;
+	case MNCC_REL_IND:
+	case MNCC_REJ_IND:
+		rc = mncc_rel_ind(call, msg_type, arg);
+		break;
+	case MNCC_REL_CNF:
+		rc = mncc_rel_cnf(call, msg_type, arg);
+		break;
+	case MNCC_FACILITY_IND:
+		break;
+	case MNCC_START_DTMF_IND:
+		break;
+	case MNCC_STOP_DTMF_IND:
+		break;
+	case MNCC_MODIFY_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting MODIFY with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_send(net, MNCC_MODIFY_REJ, data);
+		break;
+	case MNCC_MODIFY_CNF:
+		break;
+	case MNCC_HOLD_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting HOLD with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_send(net, MNCC_HOLD_REJ, data);
+		break;
+	case MNCC_RETRIEVE_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting RETRIEVE with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_send(net, MNCC_RETRIEVE_REJ, data);
+		break;
+	case GSM_TCHF_FRAME:
+	case GSM_TCHF_FRAME_EFR:
+		rc = mncc_rcv_tchf(call, msg_type, arg);
+		break;
+	default:
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message unhandled\n", callref);
+		break;
+	}
+
+	return rc;
+}
diff --git a/openbsc/src/openbsc.cfg.1-1 b/openbsc/src/openbsc.cfg.1-1
new file mode 100644
index 0000000..a25804f
--- /dev/null
+++ b/openbsc/src/openbsc.cfg.1-1
@@ -0,0 +1,54 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ timer t3101 10
+ timer t3113 60
+ bts 0
+  type bs11
+  band GSM900
+  cell_identity 1
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 25
+  trx 0
+   arfcn 121
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 1
+    timeslot 0
+     phys_chan_config CCCH+SDCCH4
+     e1 line 0 timeslot 1 sub-slot full
+    timeslot 1
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 2 sub-slot 1
+    timeslot 2
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 2 sub-slot 2
+    timeslot 3
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 2 sub-slot 3
+    timeslot 4
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 0
+    timeslot 5
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 1
+    timeslot 6
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 2
+    timeslot 7
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 3
+
diff --git a/openbsc/src/openbsc.cfg.1-2 b/openbsc/src/openbsc.cfg.1-2
new file mode 100644
index 0000000..84d50c7
--- /dev/null
+++ b/openbsc/src/openbsc.cfg.1-2
@@ -0,0 +1,82 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ timer t3101 10
+ timer t3113 60
+ bts 0
+  type bs11
+  band GSM900
+  cell_identity 1
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 25
+  trx 0
+   arfcn 121
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 1
+    timeslot 0
+     phys_chan_config CCCH+SDCCH4
+     e1 line 0 timeslot 1 sub-slot full
+    timeslot 1
+     phys_chan_config SDCCH8
+     e1 line 0 timeslot 2 sub-slot 1
+    timeslot 2
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 2 sub-slot 2
+    timeslot 3
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 2 sub-slot 3
+    timeslot 4
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 0
+    timeslot 5
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 1
+    timeslot 6
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 2
+    timeslot 7
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 3
+  trx 1
+   arfcn 123
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 2
+    timeslot 0
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 4 sub-slot 0
+    timeslot 1
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 4 sub-slot 1
+    timeslot 2
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 4 sub-slot 2
+    timeslot 3
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 4 sub-slot 3
+    timeslot 4
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 5 sub-slot 0
+    timeslot 5
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 5 sub-slot 1
+    timeslot 6
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 5 sub-slot 2
+    timeslot 7
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 5 sub-slot 3
diff --git a/openbsc/src/openbsc.cfg.2-2 b/openbsc/src/openbsc.cfg.2-2
new file mode 100644
index 0000000..9ae8003
--- /dev/null
+++ b/openbsc/src/openbsc.cfg.2-2
@@ -0,0 +1,148 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ timer t3101 10
+ timer t3113 60
+ bts 0
+  type bs11
+  band GSM900
+  cell_identity 1
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  oml e1 line 0 timeslot 1 sub-slot full
+  oml e1 tei 25
+  trx 0
+   arfcn 121
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 1
+    timeslot 0
+     phys_chan_config CCCH+SDCCH4
+     e1 line 0 timeslot 1 sub-slot full
+    timeslot 1
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 2 sub-slot 1
+    timeslot 2
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 2 sub-slot 2
+    timeslot 3
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 2 sub-slot 3
+    timeslot 4
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 0
+    timeslot 5
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 1
+    timeslot 6
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 2
+    timeslot 7
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 3 sub-slot 3
+  trx 1
+   arfcn 123
+   max_power_red 0
+   rsl e1 line 0 timeslot 1 sub-slot full
+   rsl e1 tei 2
+    timeslot 0
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 4 sub-slot 0
+    timeslot 1
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 4 sub-slot 1
+    timeslot 2
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 4 sub-slot 2
+    timeslot 3
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 4 sub-slot 3
+    timeslot 4
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 5 sub-slot 0
+    timeslot 5
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 5 sub-slot 1
+    timeslot 6
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 5 sub-slot 2
+    timeslot 7
+     phys_chan_config TCH/F
+     e1 line 0 timeslot 5 sub-slot 3
+ bts 1
+  type bs11
+  band GSM900
+  location_area_code 2
+  training_sequence_code 7
+  base_station_id_code 63
+  oml e1 line 1 timeslot 6 sub-slot full
+  oml e1 tei 25
+  trx 0
+   arfcn 122
+   max_power_red 0
+   rsl e1 line 1 timeslot 6 sub-slot full
+   rsl e1 tei 1
+    timeslot 0
+     phys_chan_config CCCH+SDCCH4
+     e1 line 1 timeslot 7 sub-slot 0
+    timeslot 1
+     phys_chan_config SDCCH8
+     e1 line 1 timeslot 7 sub-slot 1
+    timeslot 2
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 7 sub-slot 2
+    timeslot 3
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 7 sub-slot 3
+    timeslot 4
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 8 sub-slot 0
+    timeslot 5
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 8 sub-slot 1
+    timeslot 6
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 8 sub-slot 2
+    timeslot 7
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 8 sub-slot 3
+  trx 1
+   arfcn 124
+   max_power_red 0
+   rsl e1 line 1 timeslot 6 sub-slot full
+   rsl e1 tei 2
+    timeslot 0
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 9 sub-slot 0
+    timeslot 1
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 9 sub-slot 1
+    timeslot 2
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 9 sub-slot 2
+    timeslot 3
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 9 sub-slot 3
+    timeslot 4
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 10 sub-slot 0
+    timeslot 5
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 10 sub-slot 1
+    timeslot 6
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 10 sub-slot 2
+    timeslot 7
+     phys_chan_config TCH/F
+     e1 line 1 timeslot 10 sub-slot 3
diff --git a/openbsc/src/openbsc.cfg.nanobts b/openbsc/src/openbsc.cfg.nanobts
new file mode 100644
index 0000000..da0ba74
--- /dev/null
+++ b/openbsc/src/openbsc.cfg.nanobts
@@ -0,0 +1,75 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 0
+ rrlp mode none
+ mm info 1
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ timer t3101 10
+ timer t3103 0
+ timer t3105 0
+ timer t3107 0
+ timer t3109 0
+ timer t3111 0
+ timer t3113 60
+ timer t3115 0
+ timer t3117 0
+ timer t3119 0
+ timer t3141 0
+ bts 0
+  type nanobts
+  band DCS1800
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator ascending
+  rach tx integer 9
+  rach max transmission 7
+  ip.access unit_id 1801 0
+  oml ip.access stream_id 255
+  gprs mode none
+  trx 0
+   rf_locked 0
+   arfcn 514
+   nominal power 23
+   max_power_red 20
+   rsl e1 tei 0
+    timeslot 0
+     phys_chan_config CCCH+SDCCH4
+    timeslot 1
+     phys_chan_config SDCCH8
+    timeslot 2
+     phys_chan_config TCH/F
+    timeslot 3
+     phys_chan_config TCH/F
+    timeslot 4
+     phys_chan_config TCH/F
+    timeslot 5
+     phys_chan_config TCH/F
+    timeslot 6
+     phys_chan_config TCH/F
+    timeslot 7
+     phys_chan_config TCH/F
diff --git a/openbsc/src/openbsc.cfg.nanobts.multitrx b/openbsc/src/openbsc.cfg.nanobts.multitrx
new file mode 100644
index 0000000..6e27ff5
--- /dev/null
+++ b/openbsc/src/openbsc.cfg.nanobts.multitrx
@@ -0,0 +1,97 @@
+!
+! OpenBSC configuration saved from vty
+!   !
+password foo
+!
+line vty
+ no login
+!
+network
+ network country code 1
+ mobile network code 1
+ short name OpenBSC
+ long name OpenBSC
+ auth policy closed
+ location updating reject cause 13
+ encryption a5 0
+ neci 0
+ rrlp mode none
+ mm info 0
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ timer t3101 10
+ timer t3103 0
+ timer t3105 0
+ timer t3107 0
+ timer t3109 0
+ timer t3111 0
+ timer t3113 60
+ timer t3115 0
+ timer t3117 0
+ timer t3119 0
+ timer t3141 0
+ bts 0
+  type nanobts
+  band DCS1800
+  cell_identity 0
+  location_area_code 1
+  training_sequence_code 7
+  base_station_id_code 63
+  ms max power 15
+  cell reselection hysteresis 4
+  rxlev access min 0
+  channel allocator ascending
+  rach tx integer 9
+  rach max transmission 7
+  ip.access unit_id 1800 0
+  oml ip.access stream_id 255
+  gprs mode none
+  trx 0
+   rf_locked 0
+   arfcn 871
+   nominal power 23
+   max_power_red 0
+   rsl e1 tei 0
+    timeslot 0
+     phys_chan_config CCCH+SDCCH4
+    timeslot 1
+     phys_chan_config SDCCH8
+    timeslot 2
+     phys_chan_config TCH/F
+    timeslot 3
+     phys_chan_config TCH/F
+    timeslot 4
+     phys_chan_config TCH/F
+    timeslot 5
+     phys_chan_config TCH/F
+    timeslot 6
+     phys_chan_config TCH/F
+    timeslot 7
+     phys_chan_config TCH/F
+  trx 1
+   rf_locked 0
+   arfcn 873
+   nominal power 23
+   max_power_red 0
+   rsl e1 tei 0
+    timeslot 0
+     phys_chan_config SDCCH8
+    timeslot 1
+     phys_chan_config TCH/F
+    timeslot 2
+     phys_chan_config TCH/F
+    timeslot 3
+     phys_chan_config TCH/F
+    timeslot 4
+     phys_chan_config TCH/F
+    timeslot 5
+     phys_chan_config TCH/F
+    timeslot 6
+     phys_chan_config TCH/F
+    timeslot 7
+     phys_chan_config TCH/F
diff --git a/openbsc/src/paging.c b/openbsc/src/paging.c
new file mode 100644
index 0000000..12ed903
--- /dev/null
+++ b/openbsc/src/paging.c
@@ -0,0 +1,329 @@
+/* Paging helper and manager.... */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*
+ * Relevant specs:
+ *     12.21:
+ *       - 9.4.12 for CCCH Local Threshold
+ *
+ *     05.58:
+ *       - 8.5.2 CCCH Load indication
+ *       - 9.3.15 Paging Load
+ *
+ * Approach:
+ *       - Send paging command to subscriber
+ *       - On Channel Request we will remember the reason
+ *       - After the ACK we will request the identity
+ *	 - Then we will send assign the gsm_subscriber and
+ *	 - and call a callback
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <openbsc/paging.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+
+void *tall_paging_ctx;
+
+static unsigned int calculate_group(struct gsm_bts *bts, struct gsm_subscriber *subscr)
+{
+	int ccch_conf;
+	int bs_cc_chans;
+	int blocks;
+	unsigned int group;
+	
+	ccch_conf = bts->si_common.chan_desc.ccch_conf;
+	bs_cc_chans = rsl_ccch_conf_to_bs_cc_chans(ccch_conf);
+	/* code word + 2, as 2 channels equals 0x0 */
+	blocks = rsl_number_of_paging_subchannels(bts);
+	group = get_paging_group(str_to_imsi(subscr->imsi),
+					bs_cc_chans, blocks);
+	return group;
+}
+
+/*
+ * Kill one paging request update the internal list...
+ */
+static void paging_remove_request(struct gsm_bts_paging_state *paging_bts,
+				struct gsm_paging_request *to_be_deleted)
+{
+	bsc_del_timer(&to_be_deleted->T3113);
+	llist_del(&to_be_deleted->entry);
+	subscr_put(to_be_deleted->subscr);
+	talloc_free(to_be_deleted);
+}
+
+static void page_ms(struct gsm_paging_request *request)
+{
+	u_int8_t mi[128];
+	unsigned int mi_len;
+	unsigned int page_group;
+
+	LOGP(DPAG, LOGL_INFO, "Going to send paging commands: imsi: '%s' tmsi: '0x%x'\n",
+		request->subscr->imsi, request->subscr->tmsi);
+
+	if (request->subscr->tmsi == GSM_RESERVED_TMSI)
+		mi_len = gsm48_generate_mid_from_imsi(mi, request->subscr->imsi);
+	else
+		mi_len = gsm48_generate_mid_from_tmsi(mi, request->subscr->tmsi);
+
+	page_group = calculate_group(request->bts, request->subscr);
+	rsl_paging_cmd(request->bts, page_group, mi_len, mi,
+			request->chan_type);
+}
+
+/*
+ * This is kicked by the periodic PAGING LOAD Indicator
+ * coming from abis_rsl.c
+ *
+ * We attempt to iterate once over the list of items but
+ * only upto available_slots.
+ */
+static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts)
+{
+	struct gsm_paging_request *initial_request = NULL;
+	struct gsm_paging_request *current_request = NULL;
+
+	/*
+	 * Determine if the pending_requests list is empty and
+	 * return then.
+	 */
+	if (llist_empty(&paging_bts->pending_requests)) {
+		/* since the list is empty, no need to reschedule the timer */
+		return;
+	}
+
+	/*
+	 * In case the BTS does not provide us with load indication just fill
+	 * up our slots for this round. We should be able to page 20 subscribers
+	 * every two seconds. So we will just give the BTS some extra credit.
+	 * We will have to see how often we run out of this credit, so we might
+	 * need a low watermark and then add credit or give 20 every run when
+	 * the bts sets an option for that.
+	 */
+	if (paging_bts->available_slots == 0) {
+		LOGP(DPAG, LOGL_NOTICE, "No slots available on bts nr %d\n",
+		     paging_bts->bts->nr);
+		paging_bts->available_slots = 20;
+	}
+
+	initial_request = llist_entry(paging_bts->pending_requests.next,
+				      struct gsm_paging_request, entry);
+	current_request = initial_request;
+
+	do {
+		/* handle the paging request now */
+		page_ms(current_request);
+		paging_bts->available_slots--;
+
+		/* take the current and add it to the back */
+		llist_del(&current_request->entry);
+		llist_add_tail(&current_request->entry, &paging_bts->pending_requests);
+
+		/* take the next request */
+		current_request = llist_entry(paging_bts->pending_requests.next,
+					      struct gsm_paging_request, entry);
+	} while (paging_bts->available_slots > 0
+		    &&  initial_request != current_request);
+
+	bsc_schedule_timer(&paging_bts->work_timer, 2, 0);
+}
+
+static void paging_worker(void *data)
+{
+	struct gsm_bts_paging_state *paging_bts = data;
+
+	paging_handle_pending_requests(paging_bts);
+}
+
+void paging_init(struct gsm_bts *bts)
+{
+	bts->paging.bts = bts;
+	INIT_LLIST_HEAD(&bts->paging.pending_requests);
+	bts->paging.work_timer.cb = paging_worker;
+	bts->paging.work_timer.data = &bts->paging;
+
+	/* Large number, until we get a proper message */
+	bts->paging.available_slots = 20;
+}
+
+static int paging_pending_request(struct gsm_bts_paging_state *bts,
+				struct gsm_subscriber *subscr) {
+	struct gsm_paging_request *req;
+
+	llist_for_each_entry(req, &bts->pending_requests, entry) {
+		if (subscr == req->subscr)
+			return 1;
+	}
+
+	return 0;	
+}
+
+static void paging_T3113_expired(void *data)
+{
+	struct gsm_paging_request *req = (struct gsm_paging_request *)data;
+	struct paging_signal_data sig_data;
+	void *cbfn_param;
+	gsm_cbfn *cbfn;
+
+	LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n",
+		req, req->subscr->imsi);
+	
+	sig_data.subscr = req->subscr;
+	sig_data.bts	= req->bts;
+	sig_data.lchan	= NULL;
+
+	/* must be destroyed before calling cbfn, to prevent double free */
+	counter_inc(req->bts->network->stats.paging.expired);
+	cbfn_param = req->cbfn_param;
+	cbfn = req->cbfn;
+	paging_remove_request(&req->bts->paging, req);
+
+
+	dispatch_signal(SS_PAGING, S_PAGING_EXPIRED, &sig_data);
+	if (cbfn)
+		cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_EXPIRED, NULL, NULL,
+			  cbfn_param);
+}
+
+static int _paging_request(struct gsm_bts *bts, struct gsm_subscriber *subscr,
+			    int type, gsm_cbfn *cbfn, void *data)
+{
+	struct gsm_bts_paging_state *bts_entry = &bts->paging;
+	struct gsm_paging_request *req;
+
+	if (paging_pending_request(bts_entry, subscr)) {
+		LOGP(DPAG, LOGL_INFO, "Paging request already pending for %s\n", subscr->imsi);
+		return -EEXIST;
+	}
+
+	LOGP(DPAG, LOGL_DEBUG, "Start paging of subscriber %llu on bts %d.\n",
+		subscr->id, bts->nr);
+	req = talloc_zero(tall_paging_ctx, struct gsm_paging_request);
+	req->subscr = subscr_get(subscr);
+	req->bts = bts;
+	req->chan_type = type;
+	req->cbfn = cbfn;
+	req->cbfn_param = data;
+	req->T3113.cb = paging_T3113_expired;
+	req->T3113.data = req;
+	bsc_schedule_timer(&req->T3113, bts->network->T3113, 0);
+	llist_add_tail(&req->entry, &bts_entry->pending_requests);
+
+	if (!bsc_timer_pending(&bts_entry->work_timer))
+		bsc_schedule_timer(&bts_entry->work_timer, 2, 0);
+
+	return 0;
+}
+
+int paging_request(struct gsm_network *network, struct gsm_subscriber *subscr,
+		   int type, gsm_cbfn *cbfn, void *data)
+{
+	struct gsm_bts *bts = NULL;
+	int num_pages = 0;
+
+	counter_inc(network->stats.paging.attempted);
+
+	/* start paging subscriber on all BTS within Location Area */
+	do {
+		int rc;
+
+		bts = gsm_bts_by_lac(network, subscr->lac, bts);
+		if (!bts)
+			break;
+
+		/* skip all currently inactive TRX */
+		if (!trx_is_usable(bts->c0))
+			continue;
+
+		num_pages++;
+
+		/* Trigger paging, pass any error to caller */
+		rc = _paging_request(bts, subscr, type, cbfn, data);
+		if (rc < 0)
+			return rc;
+	} while (1);
+
+	if (num_pages == 0)
+		counter_inc(network->stats.paging.detached);
+
+	return num_pages;
+}
+
+
+/* we consciously ignore the type of the request here */
+static void _paging_request_stop(struct gsm_bts *bts, struct gsm_subscriber *subscr,
+				 struct gsm_lchan *lchan)
+{
+	struct gsm_bts_paging_state *bts_entry = &bts->paging;
+	struct gsm_paging_request *req, *req2;
+
+	llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests,
+				 entry) {
+		if (req->subscr == subscr) {
+			if (lchan && req->cbfn) {
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d, calling cbfn.\n", bts->nr);
+				req->cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+					  NULL, lchan, req->cbfn_param);
+			} else
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d silently.\n", bts->nr);
+			paging_remove_request(&bts->paging, req);
+			break;
+		}
+	}
+}
+
+/* Stop paging on all other bts' */
+void paging_request_stop(struct gsm_bts *_bts, struct gsm_subscriber *subscr,
+			 struct gsm_lchan *lchan)
+{
+	struct gsm_bts *bts = NULL;
+
+	if (_bts)
+		_paging_request_stop(_bts, subscr, lchan);
+
+	do {
+		/*
+		 * FIXME: Don't use the lac of the subscriber...
+		 * as it might have magically changed the lac.. use the
+		 * location area of the _bts as reconfiguration of the
+		 * network is probably happening less often.
+		 */
+		bts = gsm_bts_by_lac(subscr->net, subscr->lac, bts);
+		if (!bts)
+			break;
+
+		/* Stop paging */
+		if (bts != _bts)
+			_paging_request_stop(bts, subscr, NULL);
+	} while (1);
+}
+
+void paging_update_buffer_space(struct gsm_bts *bts, u_int16_t free_slots)
+{
+	bts->paging.available_slots = free_slots;
+}
diff --git a/openbsc/src/rest_octets.c b/openbsc/src/rest_octets.c
new file mode 100644
index 0000000..039d2c8
--- /dev/null
+++ b/openbsc/src/rest_octets.c
@@ -0,0 +1,425 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface,
+ * rest octet handling according to
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/bitvec.h>
+#include <openbsc/rest_octets.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(u_int8_t *data, u_int8_t *nch_pos)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 1;
+
+	if (nch_pos) {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, *nch_pos, 5);
+	} else
+		bitvec_set_bit(&bv, L);
+
+	bitvec_spare_padding(&bv, 7);
+	return bv.data_len;
+}
+
+/* Append selection parameters to bitvec */
+static void append_selection_params(struct bitvec *bv,
+				    const struct gsm48_si_selection_params *sp)
+{
+	if (sp->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_bit(bv, sp->cbq);
+		bitvec_set_uint(bv, sp->cell_resel_off, 6);
+		bitvec_set_uint(bv, sp->temp_offs, 3);
+		bitvec_set_uint(bv, sp->penalty_time, 5);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+/* Append power offset to bitvec */
+static void append_power_offset(struct bitvec *bv,
+				const struct gsm48_si_power_offset *po)
+{
+	if (po->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_uint(bv, po->power_offset, 2);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+/* Append GPRS indicator to bitvec */
+static void append_gprs_ind(struct bitvec *bv,
+			    const struct gsm48_si3_gprs_ind *gi)
+{
+	if (gi->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_uint(bv, gi->ra_colour, 3);
+		/* 0 == SI13 in BCCH Norm, 1 == SI13 sent on BCCH Ext */
+		bitvec_set_bit(bv, gi->si13_position);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+
+/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
+int rest_octets_si3(u_int8_t *data, const struct gsm48_si_ro_info *si3)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 4;
+
+	/* Optional Selection Parameters */
+	append_selection_params(&bv, &si3->selection_params);
+
+	/* Optional Power Offset */
+	append_power_offset(&bv, &si3->power_offset);
+
+	/* Do we have a SI2ter on the BCCH? */
+	if (si3->si2ter_indicator)
+		bitvec_set_bit(&bv, H);
+	else
+		bitvec_set_bit(&bv, L);
+
+	/* Early Classmark Sending Control */
+	if (si3->early_cm_ctrl)
+		bitvec_set_bit(&bv, H);
+	else
+		bitvec_set_bit(&bv, L);
+
+	/* Do we have a SI Type 9 on the BCCH? */
+	if (si3->scheduling.present) {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, si3->scheduling.where, 3);
+	} else
+		bitvec_set_bit(&bv, L);
+
+	/* GPRS Indicator */
+	append_gprs_ind(&bv, &si3->gprs_ind);
+
+	bitvec_spare_padding(&bv, (bv.data_len*8)-1);
+	return bv.data_len;
+}
+
+static int append_lsa_params(struct bitvec *bv,
+			     const struct gsm48_lsa_params *lsa_params)
+{
+	/* FIXME */
+	return -1;
+}
+
+/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
+int rest_octets_si4(u_int8_t *data, const struct gsm48_si_ro_info *si4)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 10; /* FIXME: up to ? */
+
+	/* SI4 Rest Octets O */
+	append_selection_params(&bv, &si4->selection_params);
+	append_power_offset(&bv, &si4->power_offset);
+	append_gprs_ind(&bv, &si4->gprs_ind);
+
+	if (0 /* FIXME */) {
+		/* H and SI4 Rest Octets S */
+		bitvec_set_bit(&bv, H);
+
+		/* LSA Parameters */
+		if (si4->lsa_params.present) {
+			bitvec_set_bit(&bv, H);
+			append_lsa_params(&bv, &si4->lsa_params);
+		} else
+			bitvec_set_bit(&bv, L);
+
+		/* Cell Identity */
+		if (1) {
+			bitvec_set_bit(&bv, H);
+			bitvec_set_uint(&bv, si4->cell_id, 16);
+		} else
+			bitvec_set_bit(&bv, L);
+
+		/* LSA ID Information */
+		if (0) {
+			bitvec_set_bit(&bv, H);
+			/* FIXME */
+		} else
+			bitvec_set_bit(&bv, L);
+	} else {
+		/* L and break indicator */
+		bitvec_set_bit(&bv, L);
+		bitvec_set_bit(&bv, si4->break_ind ? H : L);
+	}
+
+	return bv.data_len;
+}
+
+/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a:
+   < GPRS Mobile Allocation IE > ::=
+     < HSN : bit (6) >
+     { 0 | 1 < RFL number list : < RFL number list struct > > }
+     { 0 < MA_LENGTH : bit (6) >
+         < MA_BITMAP: bit (val(MA_LENGTH) + 1) >
+     | 1 { 0 | 1 <ARFCN index list : < ARFCN index list struct > > } } ;
+
+     < RFL number list struct > :: =
+       < RFL_NUMBER : bit (4) >
+       { 0 | 1 < RFL number list struct > } ;
+     < ARFCN index list struct > ::=
+       < ARFCN_INDEX : bit(6) >
+       { 0 | 1 < ARFCN index list struct > } ;
+ */
+static int append_gprs_mobile_alloc(struct bitvec *bv)
+{
+	/* Hopping Sequence Number */
+	bitvec_set_uint(bv, 0, 6);
+
+	if (0) {
+		/* We want to use a RFL number list */
+		bitvec_set_bit(bv, 1);
+		/* FIXME: RFL number list */
+	} else
+		bitvec_set_bit(bv, 0);
+
+	if (0) {
+		/* We want to use a MA_BITMAP */
+		bitvec_set_bit(bv, 0);
+		/* FIXME: MA_LENGTH, MA_BITMAP, ... */
+	} else {
+		bitvec_set_bit(bv, 1);
+		if (0) {
+			/* We want to provide an ARFCN index list */
+			bitvec_set_bit(bv, 1);
+			/* FIXME */
+		} else
+			bitvec_set_bit(bv, 0);
+	}
+	return 0;
+}
+
+static int encode_t3192(unsigned int t3192)
+{
+	if (t3192 == 0)
+		return 3;
+	else if (t3192 <= 80)
+		return 4;
+	else if (t3192 <= 120)
+		return 5;
+	else if (t3192 <= 160)
+		return 6;
+	else if (t3192 <= 200)
+		return 7;
+	else if (t3192 <= 500)
+		return 0;
+	else if (t3192 <= 1000)
+		return 1;
+	else if (t3192 <= 1500)
+		return 2;
+	else
+		return -EINVAL;
+}
+
+static int encode_drx_timer(unsigned int drx)
+{
+	if (drx == 0)
+		return 0;
+	else if (drx == 1)
+		return 1;
+	else if (drx == 2)
+		return 2;
+	else if (drx <= 4)
+		return 3;
+	else if (drx <= 8)
+		return 4;
+	else if (drx <= 16)
+		return 5;
+	else if (drx <= 32)
+		return 6;
+	else if (drx <= 64)
+		return 7;
+	else
+		return -EINVAL;
+}
+
+/* GPRS Cell Options as per TS 04.60 Chapter 12.24
+	< GPRS Cell Options IE > ::=
+		< NMO : bit(2) >
+		< T3168 : bit(3) >
+		< T3192 : bit(3) >
+		< DRX_TIMER_MAX: bit(3) >
+		< ACCESS_BURST_TYPE: bit >
+		< CONTROL_ACK_TYPE : bit >
+		< BS_CV_MAX: bit(4) >
+		{ 0 | 1 < PAN_DEC : bit(3) >
+			< PAN_INC : bit(3) >
+			< PAN_MAX : bit(3) >
+		{ 0 | 1 < Extension Length : bit(6) >
+			< bit (val(Extension Length) + 1
+			& { < Extension Information > ! { bit ** = <no string> } } ;
+	< Extension Information > ::=
+		{ 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit >
+			< BEP_PERIOD : bit(4) > }
+		< PFC_FEATURE_MODE : bit >
+		< DTM_SUPPORT : bit >
+		<BSS_PAGING_COORDINATION: bit >
+		<spare bit > ** ;
+ */
+static int append_gprs_cell_opt(struct bitvec *bv,
+				const struct gprs_cell_options *gco)
+{
+	int t3192, drx_timer_max;
+
+	t3192 = encode_t3192(gco->t3192);
+	if (t3192 < 0)
+		return t3192;
+
+	drx_timer_max = encode_drx_timer(gco->drx_timer_max);
+	if (drx_timer_max < 0)
+		return drx_timer_max;
+
+	bitvec_set_uint(bv, gco->nmo, 2);
+	bitvec_set_uint(bv, gco->t3168 / 500, 3);
+	bitvec_set_uint(bv, t3192, 3);
+	bitvec_set_uint(bv, drx_timer_max, 3);
+	/* ACCESS_BURST_TYPE: Hard-code 8bit */
+	bitvec_set_bit(bv, 0);
+	/* CONTROL_ACK_TYPE: Hard-code to RLC/MAC control block */
+	bitvec_set_bit(bv, 1);
+	bitvec_set_uint(bv, gco->bs_cv_max, 4);
+
+	/* hard-code no PAN_{DEC,INC,MAX} */
+	bitvec_set_bit(bv, 0);
+
+	if (!gco->ext_info_present) {
+		/* no extension information */
+		bitvec_set_bit(bv, 0);
+	} else {
+		/* extension information */
+		bitvec_set_bit(bv, 1);
+		if (!gco->ext_info.egprs_supported) {
+			/* 6bit length of extension */
+			bitvec_set_uint(bv, (1 + 3)-1, 6);
+			/* EGPRS supported in the cell */
+			bitvec_set_bit(bv, 0);
+		} else {
+			/* 6bit length of extension */
+			bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
+			/* EGPRS supported in the cell */
+			bitvec_set_bit(bv, 1);
+			/* 1bit EGPRS PACKET CHANNEL REQUEST */
+			bitvec_set_bit(bv, gco->ext_info.use_egprs_p_ch_req);
+			/* 4bit BEP PERIOD */
+			bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
+		}
+		bitvec_set_bit(bv, gco->ext_info.pfc_supported);
+		bitvec_set_bit(bv, gco->ext_info.dtm_supported);
+		bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
+	}
+
+	return 0;
+}
+
+static void append_gprs_pwr_ctrl_pars(struct bitvec *bv,
+				      const struct gprs_power_ctrl_pars *pcp)
+{
+	bitvec_set_uint(bv, pcp->alpha, 4);
+	bitvec_set_uint(bv, pcp->t_avg_w, 5);
+	bitvec_set_uint(bv, pcp->t_avg_t, 5);
+	bitvec_set_uint(bv, pcp->pc_meas_chan, 1);
+	bitvec_set_uint(bv, pcp->n_avg_i, 4);
+}
+
+/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */
+int rest_octets_si13(u_int8_t *data, const struct gsm48_si13_info *si13)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 20;
+
+	if (0) {
+		/* No rest octets */
+		bitvec_set_bit(&bv, L);
+	} else {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, si13->bcch_change_mark, 3);
+		bitvec_set_uint(&bv, si13->si_change_field, 4);
+		if (1) {
+			bitvec_set_bit(&bv, 0);
+		} else {
+			bitvec_set_bit(&bv, 1);
+			bitvec_set_uint(&bv, si13->bcch_change_mark, 2);
+			append_gprs_mobile_alloc(&bv);
+		}
+		if (!si13->pbcch_present) {
+			/* PBCCH not present in cell */
+			bitvec_set_bit(&bv, 0);
+			bitvec_set_uint(&bv, si13->no_pbcch.rac, 8);
+			bitvec_set_bit(&bv, si13->no_pbcch.spgc_ccch_sup);
+			bitvec_set_uint(&bv, si13->no_pbcch.prio_acc_thr, 3);
+			bitvec_set_uint(&bv, si13->no_pbcch.net_ctrl_ord, 2);
+			append_gprs_cell_opt(&bv, &si13->cell_opts);
+			append_gprs_pwr_ctrl_pars(&bv, &si13->pwr_ctrl_pars);
+		} else {
+			/* PBCCH present in cell */
+			bitvec_set_bit(&bv, 1);
+			bitvec_set_uint(&bv, si13->pbcch.psi1_rep_per, 4);
+			/* PBCCH Descripiton */
+			bitvec_set_uint(&bv, si13->pbcch.pb, 4);
+			bitvec_set_uint(&bv, si13->pbcch.tsc, 3);
+			bitvec_set_uint(&bv, si13->pbcch.tn, 3);
+			switch (si13->pbcch.carrier_type) {
+			case PBCCH_BCCH:
+				bitvec_set_bit(&bv, 0);
+				bitvec_set_bit(&bv, 0);
+				break;
+			case PBCCH_ARFCN:
+				bitvec_set_bit(&bv, 0);
+				bitvec_set_bit(&bv, 1);
+				bitvec_set_uint(&bv, si13->pbcch.arfcn, 10);
+				break;
+			case PBCCH_MAIO:
+				bitvec_set_bit(&bv, 1);
+				bitvec_set_uint(&bv, si13->pbcch.maio, 6);
+				break;
+			}
+		}
+		/* 3GPP TS 44.018 Release 6 / 10.5.2.37b */
+		bitvec_set_bit(&bv, H);	/* added Release 99 */
+		/* claim our SGSN is compatible with Release 99, as EDGE and EGPRS
+		 * was only added in this Release */
+		bitvec_set_bit(&bv, 1);
+	}
+	bitvec_spare_padding(&bv, (bv.data_len*8)-1);
+	return bv.data_len;
+}
diff --git a/openbsc/src/rrlp.c b/openbsc/src/rrlp.c
new file mode 100644
index 0000000..3504451
--- /dev/null
+++ b/openbsc/src/rrlp.c
@@ -0,0 +1,106 @@
+/* Radio Resource LCS (Location) Protocol, GMS TS 04.31 */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+
+/* RRLP msPositionReq, nsBased,
+ *	Accuracy=60, Method=gps, ResponseTime=2, oneSet */
+static const u_int8_t ms_based_pos_req[] = { 0x40, 0x01, 0x78, 0xa8 };
+
+/* RRLP msPositionReq, msBasedPref,
+	Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const u_int8_t ms_pref_pos_req[]  = { 0x40, 0x02, 0x79, 0x50 };
+
+/* RRLP msPositionReq, msAssistedPref,
+	Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const u_int8_t ass_pref_pos_req[] = { 0x40, 0x03, 0x79, 0x50 };
+
+static int send_rrlp_req(struct gsm_lchan *lchan)
+{
+	struct gsm_network *net = lchan->ts->trx->bts->network;
+	const u_int8_t *req;
+
+	switch (net->rrlp.mode) {
+	case RRLP_MODE_MS_BASED:
+		req = ms_based_pos_req;
+		break;
+	case RRLP_MODE_MS_PREF:
+		req = ms_pref_pos_req;
+		break;
+	case RRLP_MODE_ASS_PREF:
+		req = ass_pref_pos_req;
+		break;
+	case RRLP_MODE_NONE:
+	default:
+		return 0;
+	}
+
+	return gsm48_send_rr_app_info(lchan, 0x00,
+				      sizeof(ms_based_pos_req), req);
+}
+
+static int subscr_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr;
+	struct gsm_lchan *lchan;
+
+	switch (signal) {
+	case S_SUBSCR_ATTACHED:
+		/* A subscriber has attached. */
+		subscr = signal_data;
+		lchan = lchan_for_subscr(subscr);
+		if (!lchan)
+			break;
+		send_rrlp_req(lchan);
+		break;
+	}
+	return 0;
+}
+
+static int paging_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct paging_signal_data *psig_data = signal_data;
+
+	switch (signal) {
+	case S_PAGING_SUCCEEDED:
+		/* A subscriber has attached. */
+		send_rrlp_req(psig_data->lchan);
+		break;
+	case S_PAGING_EXPIRED:
+		break;
+	}
+	return 0;
+}
+
+void on_dso_load_rrlp(void)
+{
+	register_signal_handler(SS_SUBSCR, subscr_sig_cb, NULL);
+	register_signal_handler(SS_PAGING, paging_sig_cb, NULL);
+}
diff --git a/openbsc/src/rs232.c b/openbsc/src/rs232.c
new file mode 100644
index 0000000..22adf56
--- /dev/null
+++ b/openbsc/src/rs232.c
@@ -0,0 +1,249 @@
+/* OpenBSC BS-11 T-Link interface using POSIX serial port */
+
+/* (C) 2008-2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <termios.h>
+#include <fcntl.h>
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/rs232.h>
+
+/* adaption layer from GSM 08.59 + 12.21 to RS232 */
+
+struct serial_handle {
+	struct bsc_fd fd;
+	struct llist_head tx_queue;
+
+	struct msgb *rx_msg;
+	unsigned int rxmsg_bytes_missing;
+
+	unsigned int delay_ms;
+	struct gsm_bts *bts;
+};
+
+/* FIXME: this needs to go */
+static struct serial_handle _ser_handle, *ser_handle = &_ser_handle;
+
+#define LAPD_HDR_LEN	10
+
+static int handle_ser_write(struct bsc_fd *bfd);
+
+/* callback from abis_nm */
+int _abis_nm_sendmsg(struct msgb *msg)
+{
+	struct serial_handle *sh = ser_handle;
+	u_int8_t *lapd;
+	unsigned int len;
+
+	msg->l2h = msg->data;
+
+	/* prepend LAPD header */
+	lapd = msgb_push(msg, LAPD_HDR_LEN);
+
+	len = msg->len - 2;
+
+	lapd[0] = (len >> 8) & 0xff;
+	lapd[1] = len & 0xff; /* length of bytes startign at lapd[2] */
+	lapd[2] = 0x00;
+	lapd[3] = 0x07;
+	lapd[4] = 0x01;
+	lapd[5] = 0x3e;
+	lapd[6] = 0x00;
+	lapd[7] = 0x00;
+	lapd[8] = msg->len - 10; /* length of bytes starting at lapd[10] */
+	lapd[9] = lapd[8] ^ 0x38;
+
+	msgb_enqueue(&sh->tx_queue, msg);
+	sh->fd.when |= BSC_FD_WRITE;
+
+	/* we try to immediately send */
+	handle_ser_write(&sh->fd);
+
+	return 0;
+}
+
+/* select.c callback in case we can write to the RS232 */
+static int handle_ser_write(struct bsc_fd *bfd)
+{
+	struct serial_handle *sh = bfd->data;
+	struct msgb *msg;
+	int written;
+
+	msg = msgb_dequeue(&sh->tx_queue);
+	if (!msg) {
+		bfd->when &= ~BSC_FD_WRITE;
+		return 0;
+	}
+
+	DEBUGP(DMI, "RS232 TX: %s\n", hexdump(msg->data, msg->len));
+
+	/* send over serial line */
+	written = write(bfd->fd, msg->data, msg->len);
+	if (written < msg->len) {
+		perror("short write:");
+		msgb_free(msg);
+		return -1;
+	}
+
+	msgb_free(msg);
+	usleep(sh->delay_ms*1000);
+
+	return 0;
+}
+
+#define SERIAL_ALLOC_SIZE	300
+
+/* select.c callback in case we can read from the RS232 */
+static int handle_ser_read(struct bsc_fd *bfd)
+{
+	struct serial_handle *sh = bfd->data;
+	struct msgb *msg;
+	int rc = 0;
+
+	if (!sh->rx_msg) {
+		sh->rx_msg = msgb_alloc(SERIAL_ALLOC_SIZE, "RS232 Rx");
+		sh->rx_msg->l2h = NULL;
+		sh->rx_msg->trx = sh->bts->c0;
+	}
+	msg = sh->rx_msg;
+
+	/* first read two byes to obtain length */
+	if (msg->len < 2) {
+		rc = read(sh->fd.fd, msg->tail, 2 - msg->len);
+		if (rc < 0) {
+			perror("ERROR reading from serial port");
+			msgb_free(msg);
+			return rc;
+		}
+		msgb_put(msg, rc);
+
+		if (msg->len >= 2) {
+			/* parse LAPD payload length */
+			if (msg->data[0] != 0)
+				fprintf(stderr, "Suspicious header byte 0: 0x%02x\n",
+					msg->data[0]);
+
+			sh->rxmsg_bytes_missing = msg->data[0] << 8;
+			sh->rxmsg_bytes_missing += msg->data[1];
+
+			if (sh->rxmsg_bytes_missing < LAPD_HDR_LEN -2)
+				fprintf(stderr, "Invalid length in hdr: %u\n",
+					sh->rxmsg_bytes_missing);
+		}
+	} else {
+		/* try to read as many of the missing bytes as are available */
+		rc = read(sh->fd.fd, msg->tail, sh->rxmsg_bytes_missing);
+		if (rc < 0) {
+			perror("ERROR reading from serial port");
+			msgb_free(msg);
+			return rc;
+		}
+		msgb_put(msg, rc);
+		sh->rxmsg_bytes_missing -= rc;
+
+		if (sh->rxmsg_bytes_missing == 0) {
+			/* we have one complete message now */
+			sh->rx_msg = NULL;
+
+			if (msg->len > LAPD_HDR_LEN)
+				msg->l2h = msg->data + LAPD_HDR_LEN;
+
+			DEBUGP(DMI, "RS232 RX: %s\n", hexdump(msg->data, msg->len));
+			rc = handle_serial_msg(msg);
+		}
+	}
+
+	return rc;
+}
+
+/* select.c callback */
+static int serial_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_ser_read(bfd);
+
+	if (rc < 0)
+		return rc;
+
+	if (what & BSC_FD_WRITE)
+		rc = handle_ser_write(bfd);
+
+	return rc;
+}
+
+int rs232_setup(const char *serial_port, unsigned int delay_ms,
+		struct gsm_bts *bts)
+{
+	int rc, serial_fd;
+	struct termios tio;
+
+	serial_fd = open(serial_port, O_RDWR);
+	if (serial_fd < 0) {
+		perror("cannot open serial port:");
+		return serial_fd;
+	}
+
+	/* set baudrate */
+	rc = tcgetattr(serial_fd, &tio);
+	if (rc < 0) {
+		perror("tcgetattr()");
+		return rc;
+	}
+	cfsetispeed(&tio, B19200);
+	cfsetospeed(&tio, B19200);
+	tio.c_cflag |=  (CREAD | CLOCAL | CS8);
+	tio.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS);
+	tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+	tio.c_iflag |=  (INPCK | ISTRIP);
+	tio.c_iflag &= ~(ISTRIP | IXON | IXOFF | IGNBRK | INLCR | ICRNL | IGNCR);
+	tio.c_oflag &= ~(OPOST);
+	rc = tcsetattr(serial_fd, TCSADRAIN, &tio);
+	if (rc < 0) {
+		perror("tcsetattr()");
+		return rc;
+	}
+
+	INIT_LLIST_HEAD(&ser_handle->tx_queue);
+	ser_handle->fd.fd = serial_fd;
+	ser_handle->fd.when = BSC_FD_READ;
+	ser_handle->fd.cb = serial_fd_cb;
+	ser_handle->fd.data = ser_handle;
+	ser_handle->delay_ms = delay_ms;
+	ser_handle->bts = bts;
+	rc = bsc_register_fd(&ser_handle->fd);
+	if (rc < 0) {
+		fprintf(stderr, "could not register FD: %s\n",
+			strerror(rc));
+		return rc;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/rtp_proxy.c b/openbsc/src/rtp_proxy.c
new file mode 100644
index 0000000..924173d
--- /dev/null
+++ b/openbsc/src/rtp_proxy.c
@@ -0,0 +1,721 @@
+/* RTP proxy handling for ip.access nanoBTS */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <endian.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>    /* gettimeofday() */
+#include <unistd.h>      /* get..() */
+#include <time.h>        /* clock() */
+#include <sys/utsname.h> /* uname() */
+
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/select.h>
+#include <openbsc/debug.h>
+#include <openbsc/rtp_proxy.h>
+
+static LLIST_HEAD(rtp_sockets);
+
+/* should we mangle the CNAME inside SDES of RTCP packets? We disable
+ * this by default, as it seems to be not needed */
+static int mangle_rtcp_cname = 0;
+
+enum rtp_bfd_priv {
+	RTP_PRIV_NONE,
+	RTP_PRIV_RTP,
+	RTP_PRIV_RTCP
+};
+
+#define RTP_ALLOC_SIZE	1500
+
+/* according to RFC 1889 */
+struct rtcp_hdr {
+	u_int8_t byte0;
+	u_int8_t type;
+	u_int16_t length;
+} __attribute__((packed));
+
+#define RTCP_TYPE_SDES	202
+	
+#define RTCP_IE_CNAME	1
+
+/* according to RFC 3550 */
+struct rtp_hdr {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	u_int8_t  csrc_count:4,
+		  extension:1,
+		  padding:1,
+		  version:2;
+	u_int8_t  payload_type:7,
+		  marker:1;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	u_int8_t  version:2,
+		  padding:1,
+		  extension:1,
+		  csrc_count:4;
+	u_int8_t  marker:1,
+		  payload_type:7;
+#endif
+	u_int16_t sequence;
+	u_int32_t timestamp;
+	u_int32_t ssrc;
+} __attribute__((packed));
+
+struct rtp_x_hdr {
+	u_int16_t by_profile;
+	u_int16_t length;
+} __attribute__((packed));
+
+#define RTP_VERSION	2
+
+/* decode an rtp frame and create a new buffer with payload */
+static int rtp_decode(struct msgb *msg, u_int32_t callref, struct msgb **data)
+{
+	struct msgb *new_msg;
+	struct gsm_data_frame *frame;
+	struct rtp_hdr *rtph = (struct rtp_hdr *)msg->data;
+	struct rtp_x_hdr *rtpxh;
+	u_int8_t *payload;
+	int payload_len;
+	int msg_type;
+	int x_len;
+
+	if (msg->len < 12) {
+		DEBUGPC(DMUX, "received RTP frame too short (len = %d)\n",
+			msg->len);
+		return -EINVAL;
+	}
+	if (rtph->version != RTP_VERSION) {
+		DEBUGPC(DMUX, "received RTP version %d not supported.\n",
+			rtph->version);
+		return -EINVAL;
+	}
+	payload = msg->data + sizeof(struct rtp_hdr) + (rtph->csrc_count << 2);
+	payload_len = msg->len - sizeof(struct rtp_hdr) - (rtph->csrc_count << 2);
+	if (payload_len < 0) {
+		DEBUGPC(DMUX, "received RTP frame too short (len = %d, "
+			"csrc count = %d)\n", msg->len, rtph->csrc_count);
+		return -EINVAL;
+	}
+	if (rtph->extension) {
+		if (payload_len < sizeof(struct rtp_x_hdr)) {
+			DEBUGPC(DMUX, "received RTP frame too short for "
+				"extension header\n");
+			return -EINVAL;
+		}
+		rtpxh = (struct rtp_x_hdr *)payload;
+		x_len = ntohs(rtpxh->length) * 4 + sizeof(struct rtp_x_hdr);
+		payload += x_len;
+		payload_len -= x_len;
+		if (payload_len < 0) {
+			DEBUGPC(DMUX, "received RTP frame too short, "
+				"extension header exceeds frame length\n");
+			return -EINVAL;
+		}
+	}
+	if (rtph->padding) {
+		if (payload_len < 0) {
+			DEBUGPC(DMUX, "received RTP frame too short for "
+				"padding length\n");
+			return -EINVAL;
+		}
+		payload_len -= payload[payload_len - 1];
+		if (payload_len < 0) {
+			DEBUGPC(DMUX, "received RTP frame with padding "
+				"greater than payload\n");
+			return -EINVAL;
+		}
+	}
+
+	switch (rtph->payload_type) {
+	case RTP_PT_GSM_FULL:
+		msg_type = GSM_TCHF_FRAME;
+		if (payload_len != 33) {
+			DEBUGPC(DMUX, "received RTP full rate frame with "
+				"payload length != 32 (len = %d)\n",
+				payload_len);
+			return -EINVAL;
+		}
+		break;
+	case RTP_PT_GSM_EFR:
+		msg_type = GSM_TCHF_FRAME_EFR;
+		break;
+	default:
+		DEBUGPC(DMUX, "received RTP frame with unknown payload "
+			"type %d\n", rtph->payload_type);
+		return -EINVAL;
+	}
+
+	new_msg = msgb_alloc(sizeof(struct gsm_data_frame) + payload_len,
+				"GSM-DATA");
+	if (!new_msg)
+		return -ENOMEM;
+	frame = (struct gsm_data_frame *)(new_msg->data);
+	frame->msg_type = msg_type;
+	frame->callref = callref;
+	memcpy(frame->data, payload, payload_len);
+	msgb_put(new_msg, sizeof(struct gsm_data_frame) + payload_len);
+
+	*data = new_msg;
+	return 0;
+}
+
+/* "to - from" */
+static void tv_difference(struct timeval *diff, const struct timeval *from,
+			  const struct timeval *__to)
+{
+	struct timeval _to = *__to, *to = &_to;
+
+	if (to->tv_usec < from->tv_usec) {
+		to->tv_sec -= 1;
+		to->tv_usec += 1000000;
+	}
+
+	diff->tv_usec = to->tv_usec - from->tv_usec;
+	diff->tv_sec = to->tv_sec - from->tv_sec;
+}
+
+/* encode and send a rtp frame */
+int rtp_send_frame(struct rtp_socket *rs, struct gsm_data_frame *frame)
+{
+	struct rtp_sub_socket *rss = &rs->rtp;
+	struct msgb *msg;
+	struct rtp_hdr *rtph;
+	int payload_type;
+	int payload_len;
+	int duration; /* in samples */
+
+	if (rs->tx_action != RTP_SEND_DOWNSTREAM) {
+		/* initialize sequences */
+		rs->tx_action = RTP_SEND_DOWNSTREAM;
+		rs->transmit.ssrc = rand();
+		rs->transmit.sequence = random();
+		rs->transmit.timestamp = random();
+	}
+
+	switch (frame->msg_type) {
+	case GSM_TCHF_FRAME:
+		payload_type = RTP_PT_GSM_FULL;
+		payload_len = 33;
+		duration = 160;
+		break;
+	case GSM_TCHF_FRAME_EFR:
+		payload_type = RTP_PT_GSM_EFR;
+		payload_len = 31;
+		duration = 160;
+		break;
+	default:
+		DEBUGPC(DMUX, "unsupported message type %d\n",
+			frame->msg_type);
+		return -EINVAL;
+	}
+
+	{
+		struct timeval tv, tv_diff;
+		long int usec_diff, frame_diff;
+
+		gettimeofday(&tv, NULL);
+		tv_difference(&tv_diff, &rs->transmit.last_tv, &tv);
+		rs->transmit.last_tv = tv;
+
+		usec_diff = tv_diff.tv_sec * 1000000 + tv_diff.tv_usec;
+		frame_diff = (usec_diff / 20000);
+
+		if (abs(frame_diff) > 1) {
+			long int frame_diff_excess = frame_diff - 1;
+
+			LOGP(DMUX, LOGL_NOTICE,
+				"Correcting frame difference of %ld frames\n", frame_diff_excess);
+			rs->transmit.sequence += frame_diff_excess;
+			rs->transmit.timestamp += frame_diff_excess * duration;
+		}
+	}
+
+	msg = msgb_alloc(sizeof(struct rtp_hdr) + payload_len, "RTP-GSM-FULL");
+	if (!msg)
+		return -ENOMEM;
+	rtph = (struct rtp_hdr *)msg->data;
+	rtph->version = RTP_VERSION;
+	rtph->padding = 0;
+	rtph->extension = 0;
+	rtph->csrc_count = 0;
+	rtph->marker = 0;
+	rtph->payload_type = payload_type;
+	rtph->sequence = htons(rs->transmit.sequence++);
+	rtph->timestamp = htonl(rs->transmit.timestamp);
+	rs->transmit.timestamp += duration;
+	rtph->ssrc = htonl(rs->transmit.ssrc);
+	memcpy(msg->data + sizeof(struct rtp_hdr), frame->data, payload_len);
+	msgb_put(msg, sizeof(struct rtp_hdr) + payload_len);
+	msgb_enqueue(&rss->tx_queue, msg);
+	rss->bfd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+/* iterate over all chunks in one RTCP message, look for CNAME IEs and
+ * replace all of those with 'new_cname' */
+static int rtcp_sdes_cname_mangle(struct msgb *msg, struct rtcp_hdr *rh,
+				  u_int16_t *rtcp_len, const char *new_cname)
+{
+	u_int8_t *rtcp_end;
+	u_int8_t *cur = (u_int8_t *) rh;
+	u_int8_t tag, len = 0;
+
+	rtcp_end = cur + *rtcp_len;
+	/* move cur to end of RTP header */
+	cur += sizeof(*rh);
+
+	/* iterate over Chunks */
+	while (cur+4 < rtcp_end) {
+		/* skip four bytes SSRC/CSRC */
+		cur += 4;
+	
+		/* iterate over IE's inside the chunk */
+		while (cur+1 < rtcp_end) {
+			tag = *cur++;
+			if (tag == 0) {
+				/* end of chunk, skip additional zero */
+				while (*cur++ == 0) { }
+				break;
+			}
+			len = *cur++;
+	
+			if (tag == RTCP_IE_CNAME) {
+				/* we've found the CNAME, lets mangle it */
+				if (len < strlen(new_cname)) {
+					/* we need to make more space */
+					int increase = strlen(new_cname) - len;
+
+					msgb_push(msg, increase);
+					memmove(cur+len+increase, cur+len,
+						rtcp_end - (cur+len));
+					/* FIXME: we have to respect RTCP
+					 * padding/alignment rules! */
+					len += increase;
+					*(cur-1) += increase;
+					rtcp_end += increase;
+					*rtcp_len += increase;
+				}
+				/* copy new CNAME into message */
+				memcpy(cur, new_cname, strlen(new_cname));
+				/* FIXME: zero the padding in case new CNAME
+				 * is smaller than old one !!! */
+			}
+			cur += len;
+		}
+	}
+
+	return 0;
+}
+
+static int rtcp_mangle(struct msgb *msg, struct rtp_socket *rs)
+{
+	struct rtp_sub_socket *rss = &rs->rtcp;
+	struct rtcp_hdr *rtph;
+	u_int16_t old_len;
+	int rc;
+
+	if (!mangle_rtcp_cname)
+		return 0;
+
+	printf("RTCP\n");
+	/* iterate over list of RTCP messages */
+	rtph = (struct rtcp_hdr *)msg->data;
+	while ((void *)rtph + sizeof(*rtph) <= (void *)msg->data + msg->len) {
+		old_len = (ntohs(rtph->length) + 1) * 4;
+		if ((void *)rtph + old_len > (void *)msg->data + msg->len) {
+			DEBUGPC(DMUX, "received RTCP packet too short for "
+				"length element\n");
+			return -EINVAL;
+		}
+		if (rtph->type == RTCP_TYPE_SDES) {
+			char new_cname[255];
+			strncpy(new_cname, inet_ntoa(rss->sin_local.sin_addr),
+				sizeof(new_cname));
+			new_cname[sizeof(new_cname)-1] = '\0';
+			rc = rtcp_sdes_cname_mangle(msg, rtph, &old_len,
+						    new_cname);
+			if (rc < 0)
+				return rc;
+		}
+		rtph = (void *)rtph + old_len;
+	}
+
+	return 0;
+}
+
+/* read from incoming RTP/RTCP socket */
+static int rtp_socket_read(struct rtp_socket *rs, struct rtp_sub_socket *rss)
+{
+	int rc;
+	struct msgb *msg = msgb_alloc(RTP_ALLOC_SIZE, "RTP/RTCP");
+	struct msgb *new_msg;
+	struct rtp_sub_socket *other_rss;
+
+	if (!msg)
+		return -ENOMEM;
+
+	rc = read(rss->bfd.fd, msg->data, RTP_ALLOC_SIZE);
+	if (rc <= 0) {
+		rss->bfd.when &= ~BSC_FD_READ;
+		return rc;
+	}
+
+	msgb_put(msg, rc);
+
+	switch (rs->rx_action) {
+	case RTP_PROXY:
+		if (!rs->proxy.other_sock) {
+			rc = -EIO;
+			goto out_free;
+		}
+		if (rss->bfd.priv_nr == RTP_PRIV_RTP)
+			other_rss = &rs->proxy.other_sock->rtp;
+		else if (rss->bfd.priv_nr == RTP_PRIV_RTCP) {
+			other_rss = &rs->proxy.other_sock->rtcp;
+			/* modify RTCP SDES CNAME */
+			rc = rtcp_mangle(msg, rs);
+			if (rc < 0)
+				goto out_free;
+		} else {
+			rc = -EINVAL;
+			goto out_free;
+		}
+		msgb_enqueue(&other_rss->tx_queue, msg);
+		other_rss->bfd.when |= BSC_FD_WRITE;
+		break;
+
+	case RTP_RECV_UPSTREAM:
+		if (!rs->receive.callref || !rs->receive.net) {
+			rc = -EIO;
+			goto out_free;
+		}
+		if (rss->bfd.priv_nr == RTP_PRIV_RTCP) {
+			if (!mangle_rtcp_cname) {
+				msgb_free(msg);
+				break;
+			}
+			/* modify RTCP SDES CNAME */
+			rc = rtcp_mangle(msg, rs);
+			if (rc < 0)
+				goto out_free;
+			msgb_enqueue(&rss->tx_queue, msg);
+			rss->bfd.when |= BSC_FD_WRITE;
+			break;
+		}
+		if (rss->bfd.priv_nr != RTP_PRIV_RTP) {
+			rc = -EINVAL;
+			goto out_free;
+		}
+		rc = rtp_decode(msg, rs->receive.callref, &new_msg);
+		if (rc < 0)
+			goto out_free;
+		msgb_free(msg);
+		msgb_enqueue(&rs->receive.net->upqueue, new_msg);
+		break;
+
+	case RTP_NONE: /* if socket exists, but disabled by app */
+		msgb_free(msg);
+		break;
+	}
+
+	return 0;
+
+out_free:
+	msgb_free(msg);
+	return rc;
+}
+
+/* write from tx_queue to RTP/RTCP socket */
+static int rtp_socket_write(struct rtp_socket *rs, struct rtp_sub_socket *rss)
+{
+	struct msgb *msg;
+	int written;
+
+	msg = msgb_dequeue(&rss->tx_queue);
+	if (!msg) {
+		rss->bfd.when &= ~BSC_FD_WRITE;
+		return 0;
+	}
+
+	written = write(rss->bfd.fd, msg->data, msg->len);
+	if (written < msg->len) {
+		LOGP(DMIB, LOGL_ERROR, "short write");
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	msgb_free(msg);
+
+	return 0;
+}
+
+
+/* callback for the select.c:bfd_* layer */
+static int rtp_bfd_cb(struct bsc_fd *bfd, unsigned int flags)
+{
+	struct rtp_socket *rs = bfd->data;
+	struct rtp_sub_socket *rss;
+
+	switch (bfd->priv_nr) {
+	case RTP_PRIV_RTP:
+		rss = &rs->rtp;
+		break;
+	case RTP_PRIV_RTCP:
+		rss = &rs->rtcp;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (flags & BSC_FD_READ)
+		rtp_socket_read(rs, rss);
+
+	if (flags & BSC_FD_WRITE)
+		rtp_socket_write(rs, rss);
+
+	return 0;
+}
+
+static void init_rss(struct rtp_sub_socket *rss,
+		     struct rtp_socket *rs, int fd, int priv_nr)
+{
+	/* initialize bfd */
+	rss->bfd.fd = fd;
+	rss->bfd.data = rs;
+	rss->bfd.priv_nr = priv_nr;
+	rss->bfd.cb = rtp_bfd_cb;
+}
+
+struct rtp_socket *rtp_socket_create(void)
+{
+	int rc;
+	struct rtp_socket *rs;
+
+	DEBUGP(DMUX, "rtp_socket_create(): ");
+
+	rs = talloc_zero(tall_bsc_ctx, struct rtp_socket);
+	if (!rs)
+		return NULL;
+
+	INIT_LLIST_HEAD(&rs->rtp.tx_queue);
+	INIT_LLIST_HEAD(&rs->rtcp.tx_queue);
+
+	rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (rc < 0)
+		goto out_free;
+
+	init_rss(&rs->rtp, rs, rc, RTP_PRIV_RTP);
+	rc = bsc_register_fd(&rs->rtp.bfd);
+	if (rc < 0)
+		goto out_rtp_socket;
+
+	rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (rc < 0)
+		goto out_rtp_bfd;
+
+	init_rss(&rs->rtcp, rs, rc, RTP_PRIV_RTCP);
+	rc = bsc_register_fd(&rs->rtcp.bfd);
+	if (rc < 0)
+		goto out_rtcp_socket;
+
+	DEBUGPC(DMUX, "success\n");
+
+	rc = rtp_socket_bind(rs, INADDR_ANY);
+	if (rc < 0)
+		goto out_rtcp_bfd;
+
+	return rs;
+
+out_rtcp_bfd:
+	bsc_unregister_fd(&rs->rtcp.bfd);
+out_rtcp_socket:
+	close(rs->rtcp.bfd.fd);
+out_rtp_bfd:
+	bsc_unregister_fd(&rs->rtp.bfd);
+out_rtp_socket:
+	close(rs->rtp.bfd.fd);
+out_free:
+	talloc_free(rs);
+	DEBUGPC(DMUX, "failed\n");
+	return NULL;
+}
+
+static int rtp_sub_socket_bind(struct rtp_sub_socket *rss, u_int32_t ip,
+				u_int16_t port)
+{
+	int rc;
+	socklen_t alen = sizeof(rss->sin_local);
+
+	rss->sin_local.sin_family = AF_INET;
+	rss->sin_local.sin_addr.s_addr = htonl(ip);
+	rss->sin_local.sin_port = htons(port);
+	rss->bfd.when |= BSC_FD_READ;
+
+	rc = bind(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+		  sizeof(rss->sin_local));
+	if (rc < 0)
+		return rc;
+
+	/* retrieve the address we actually bound to, in case we
+	 * passed INADDR_ANY as IP address */
+	return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+			   &alen);
+}
+
+#define RTP_PORT_BASE	30000
+static unsigned int next_udp_port = RTP_PORT_BASE;
+
+/* bind a RTP socket to a local address */
+int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip)
+{
+	int rc = -EIO;
+	struct in_addr ia;
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DMUX, "rtp_socket_bind(rs=%p, IP=%s): ", rs,
+		inet_ntoa(ia));
+
+	/* try to bind to a consecutive pair of ports */
+	for (next_udp_port = next_udp_port % 0xffff;
+	     next_udp_port < 0xffff; next_udp_port += 2) {
+		rc = rtp_sub_socket_bind(&rs->rtp, ip, next_udp_port);
+		if (rc != 0)
+			continue;
+
+		rc = rtp_sub_socket_bind(&rs->rtcp, ip, next_udp_port+1);
+		if (rc == 0)
+			break;
+	}
+	if (rc < 0) {
+		DEBUGPC(DMUX, "failed\n");
+		return rc;
+	}
+
+	ia.s_addr = rs->rtp.sin_local.sin_addr.s_addr;
+	DEBUGPC(DMUX, "BOUND_IP=%s, BOUND_PORT=%u\n",
+		inet_ntoa(ia), ntohs(rs->rtp.sin_local.sin_port));
+	return ntohs(rs->rtp.sin_local.sin_port);
+}
+
+static int rtp_sub_socket_connect(struct rtp_sub_socket *rss,
+				  u_int32_t ip, u_int16_t port)
+{
+	int rc;
+	socklen_t alen = sizeof(rss->sin_local);
+
+	rss->sin_remote.sin_family = AF_INET;
+	rss->sin_remote.sin_addr.s_addr = htonl(ip);
+	rss->sin_remote.sin_port = htons(port);
+
+	rc = connect(rss->bfd.fd, (struct sockaddr *) &rss->sin_remote,
+		     sizeof(rss->sin_remote));
+	if (rc < 0)
+		return rc;
+
+	return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+			   &alen);
+}
+
+/* 'connect' a RTP socket to a remote peer */
+int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port)
+{
+	int rc;
+	struct in_addr ia;
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DMUX, "rtp_socket_connect(rs=%p, ip=%s, port=%u)\n",
+		rs, inet_ntoa(ia), port);
+
+	rc = rtp_sub_socket_connect(&rs->rtp, ip, port);
+	if (rc < 0)
+		return rc;
+
+	return rtp_sub_socket_connect(&rs->rtcp, ip, port+1);
+}
+
+/* bind two RTP/RTCP sockets together */
+int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other)
+{
+	DEBUGP(DMUX, "rtp_socket_proxy(this=%p, other=%p)\n",
+		this, other);
+
+	this->rx_action = RTP_PROXY;
+	this->proxy.other_sock = other;
+
+	other->rx_action = RTP_PROXY;
+	other->proxy.other_sock = this;
+
+	return 0;
+}
+
+/* bind RTP/RTCP socket to application */
+int rtp_socket_upstream(struct rtp_socket *this, struct gsm_network *net,
+			u_int32_t callref)
+{
+	DEBUGP(DMUX, "rtp_socket_proxy(this=%p, callref=%u)\n",
+		this, callref);
+
+	if (callref) {
+		this->rx_action = RTP_RECV_UPSTREAM;
+		this->receive.net = net;
+		this->receive.callref = callref;
+	} else
+		this->rx_action = RTP_NONE;
+
+	return 0;
+}
+
+static void free_tx_queue(struct rtp_sub_socket *rss)
+{
+	struct msgb *msg;
+	
+	while ((msg = msgb_dequeue(&rss->tx_queue)))
+		msgb_free(msg);
+}
+
+int rtp_socket_free(struct rtp_socket *rs)
+{
+	DEBUGP(DMUX, "rtp_socket_free(rs=%p)\n", rs);
+
+	/* make sure we don't leave references dangling to us */
+	if (rs->rx_action == RTP_PROXY &&
+	    rs->proxy.other_sock)
+		rs->proxy.other_sock->proxy.other_sock = NULL;
+
+	bsc_unregister_fd(&rs->rtp.bfd);
+	close(rs->rtp.bfd.fd);
+	free_tx_queue(&rs->rtp);
+
+	bsc_unregister_fd(&rs->rtcp.bfd);
+	close(rs->rtcp.bfd.fd);
+	free_tx_queue(&rs->rtcp);
+
+	talloc_free(rs);
+
+	return 0;
+}
diff --git a/openbsc/src/sccp/sccp.c b/openbsc/src/sccp/sccp.c
new file mode 100644
index 0000000..de18614
--- /dev/null
+++ b/openbsc/src/sccp/sccp.c
@@ -0,0 +1,1357 @@
+/*
+ * SCCP management code
+ *
+ * (C) 2009, 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009, 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <string.h>
+
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+
+#include <sccp/sccp.h>
+
+
+static void *tall_sccp_ctx;
+static LLIST_HEAD(sccp_connections);
+
+#define SCCP_MSG_SIZE 4096
+#define SCCP_MSG_HEADROOM 128
+
+/* global data */
+const struct sockaddr_sccp sccp_ssn_bssap = {
+	.sccp_family	= 0,
+	.sccp_ssn	= SCCP_SSN_BSSAP,
+};
+
+struct sccp_system {
+	/* layer3 -> layer2 */
+	void (*write_data)(struct msgb *data, void *context);
+	void *write_context;
+};
+
+
+static struct sccp_system sccp_system = {
+	.write_data = NULL,
+};
+
+struct sccp_data_callback {
+	/* connection based */
+	int (*accept_cb)(struct sccp_connection *, void *);
+	void *accept_context;
+
+	/* connection less */
+	int (*read_cb)(struct msgb *, unsigned int, void *);
+	void *read_context;
+
+	u_int8_t ssn;
+	struct llist_head callback;
+};
+
+static LLIST_HEAD(sccp_callbacks);
+
+static struct sccp_data_callback *_find_ssn(u_int8_t ssn)
+{
+	struct sccp_data_callback *cb;
+
+	llist_for_each_entry(cb, &sccp_callbacks, callback) {
+		if (cb->ssn == ssn)
+			return cb;
+	}
+
+	/* need to add one */
+	cb = talloc_zero(tall_sccp_ctx, struct sccp_data_callback);
+	if (!cb) {
+		LOGP(DSCCP, LOGL_ERROR, "Failed to allocate sccp callback.\n");
+		return NULL;
+	}
+
+	cb->ssn = ssn;
+	llist_add_tail(&cb->callback, &sccp_callbacks);
+	return cb;
+}
+
+
+static void _send_msg(struct msgb *msg)
+{
+	sccp_system.write_data(msg, sccp_system.write_context);
+}
+
+/*
+ * parsing routines
+ */
+static int copy_address(struct sccp_address *addr, u_int8_t offset, struct msgb *msgb)
+{
+	struct sccp_called_party_address *party;
+
+	int room = msgb_l2len(msgb) - offset;
+	u_int8_t read = 0;
+	u_int8_t length;
+
+	if (room <= 0) {
+		LOGP(DSCCP, LOGL_ERROR, "Not enough room for an address: %u\n", room);
+		return -1;
+	}
+
+	length = msgb->l2h[offset];
+	if (room <= length) {
+		LOGP(DSCCP, LOGL_ERROR, "Not enough room for optional data %u %u\n", room, length);
+		return -1;
+	}
+
+
+	party = (struct sccp_called_party_address *)(msgb->l2h + offset + 1);
+	if (party->point_code_indicator) {
+		if (length <= read + 2) {
+		    LOGP(DSCCP, LOGL_ERROR, "POI does not fit %u\n", length);
+		    return -1;
+		}
+
+
+		memcpy(&addr->poi, &party->data[read], 2);
+		read += 2;
+	}
+
+	if (party->ssn_indicator) {
+		if (length <= read + 1) {
+		    LOGP(DSCCP, LOGL_ERROR, "SSN does not fit %u\n", length);
+		    return -1;
+		}
+
+		addr->ssn = party->data[read];
+		read += 1;
+	}
+
+	if (party->global_title_indicator) {
+		LOGP(DSCCP, LOGL_ERROR, "GTI not supported %u\n", *(u_int8_t *)party);
+		return -1;
+	}
+
+	addr->address = *party;
+	return 0;
+}
+
+static int check_address(struct sccp_address *addr)
+{
+	/* ignore point_code_indicator... it should be zero... but */
+	if (addr->address.ssn_indicator != 1
+	    || addr->address.global_title_indicator == 1
+	    || addr->address.routing_indicator != 1) {
+		LOGP(DSCCP, LOGL_ERROR,
+			"Invalid called address according to 08.06: 0x%x 0x%x\n",
+			*(u_int8_t *)&addr->address, addr->ssn);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int _sccp_parse_optional_data(const int offset,
+				     struct msgb *msgb, struct sccp_optional_data *data)
+{
+	u_int16_t room = msgb_l2len(msgb) - offset;
+	u_int16_t read = 0;
+
+	while (room > read) {
+		u_int8_t type = msgb->l2h[offset + read];
+		if (type == SCCP_PNC_END_OF_OPTIONAL)
+			return 0;
+
+		if (read + 1 >= room) {
+			LOGP(DSCCP, LOGL_ERROR, "no place for length\n");
+			return 0;
+		}
+
+		u_int8_t length = msgb->l2h[offset + read + 1];
+		read += 2 + length;
+
+
+		if (room <= read) {
+			LOGP(DSCCP, LOGL_ERROR,
+			       "no space for the data: type: %d read: %d room: %d l2: %d\n",
+			       type, read, room, msgb_l2len(msgb));
+			return 0;
+		}
+
+		if (type == SCCP_PNC_DATA) {
+			data->data_len = length;
+			data->data_start = offset + read - length;
+		}
+
+	}
+
+	return -1;
+}
+
+int _sccp_parse_connection_request(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static const u_int32_t header_size =
+			sizeof(struct sccp_connection_request);
+	static const u_int32_t optional_offset =
+			offsetof(struct sccp_connection_request, optional_start);
+	static const u_int32_t called_offset =
+			offsetof(struct sccp_connection_request, variable_called);
+
+	struct sccp_connection_request *req = (struct sccp_connection_request *)msgb->l2h;
+	struct sccp_optional_data optional_data;
+
+	/* header check */
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
+		        msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	/* copy out the calling and called address. Add the offset */
+	if (copy_address(&result->called, called_offset + req->variable_called, msgb) != 0)
+		return -1;
+
+	if (check_address(&result->called) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "Invalid called address according to 08.06: 0x%x 0x%x\n",
+			*(u_int8_t *)&result->called.address, result->called.ssn);
+		return -1;
+	}
+
+	result->source_local_reference = &req->source_local_reference;
+
+	/*
+	 * parse optional data.
+	 */
+	memset(&optional_data, 0, sizeof(optional_data));
+	if (_sccp_parse_optional_data(optional_offset + req->optional_start, msgb, &optional_data) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n");
+		return -1;
+	}
+
+	if (optional_data.data_len != 0) {
+		msgb->l3h = &msgb->l2h[optional_data.data_start];
+		result->data_len = optional_data.data_len;
+	} else {
+		result->data_len = 0;
+	}
+
+	return 0;
+}
+
+int _sccp_parse_connection_released(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static int header_size = sizeof(struct sccp_connection_released);
+	static int optional_offset = offsetof(struct sccp_connection_released, optional_start);
+
+	struct sccp_optional_data optional_data;
+	struct sccp_connection_released *rls = (struct sccp_connection_released *) msgb->l2h;
+
+	/* we don't have enough size for the struct */
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb > header_size %u %u\n",
+		        msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	memset(&optional_data, 0, sizeof(optional_data));
+	if (_sccp_parse_optional_data(optional_offset + rls->optional_start, msgb, &optional_data) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n");
+		return -1;
+	}
+
+	result->source_local_reference = &rls->source_local_reference;
+	result->destination_local_reference = &rls->destination_local_reference;
+
+	if (optional_data.data_len != 0) {
+		msgb->l3h = &msgb->l2h[optional_data.data_start];
+		result->data_len = optional_data.data_len;
+	} else {
+		result->data_len = 0;
+	}
+
+	return 0;
+}
+
+int _sccp_parse_connection_refused(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static const u_int32_t header_size =
+			sizeof(struct sccp_connection_refused);
+	static int optional_offset = offsetof(struct sccp_connection_refused, optional_start);
+
+	struct sccp_optional_data optional_data;
+	struct sccp_connection_refused *ref;
+
+	/* header check */
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
+		        msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	ref = (struct sccp_connection_refused *) msgb->l2h;
+
+	result->destination_local_reference = &ref->destination_local_reference;
+
+	memset(&optional_data, 0, sizeof(optional_data));
+	if (_sccp_parse_optional_data(optional_offset + ref->optional_start, msgb, &optional_data) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n");
+		return -1;
+	}
+
+	/* optional data */
+	if (optional_data.data_len != 0) {
+		msgb->l3h = &msgb->l2h[optional_data.data_start];
+		result->data_len = optional_data.data_len;
+	} else {
+		result->data_len = 0;
+	}
+
+	return 0;
+}
+
+int _sccp_parse_connection_confirm(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static u_int32_t header_size =
+		    sizeof(struct sccp_connection_confirm);
+	static const u_int32_t optional_offset =
+			offsetof(struct sccp_connection_confirm, optional_start);
+
+	struct sccp_optional_data optional_data;
+	struct sccp_connection_confirm *con;
+
+	/* header check */
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
+		        msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	con = (struct sccp_connection_confirm *) msgb->l2h;
+	result->destination_local_reference = &con->destination_local_reference;
+	result->source_local_reference = &con->source_local_reference;
+
+	memset(&optional_data, 0, sizeof(optional_data));
+	if (_sccp_parse_optional_data(optional_offset + con->optional_start, msgb, &optional_data) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "parsing of optional data failed.\n");
+		return -1;
+	}
+
+	if (optional_data.data_len != 0) {
+		msgb->l3h = &msgb->l2h[optional_data.data_start];
+		result->data_len = optional_data.data_len;
+	} else {
+		result->data_len = 0;
+	}
+
+	return 0;
+}
+
+int _sccp_parse_connection_release_complete(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static int header_size = sizeof(struct sccp_connection_release_complete);
+
+	struct sccp_connection_release_complete *cmpl;
+
+	/* header check */
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
+		        msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	cmpl = (struct sccp_connection_release_complete *) msgb->l2h;
+	result->source_local_reference = &cmpl->source_local_reference;
+	result->destination_local_reference = &cmpl->destination_local_reference;
+
+	return 0;
+}
+
+int _sccp_parse_connection_dt1(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static int header_size = sizeof(struct sccp_data_form1);
+	static int variable_offset = offsetof(struct sccp_data_form1, variable_start);
+
+	struct sccp_data_form1 *dt1 = (struct sccp_data_form1 *)msgb->l2h;
+
+	/* we don't have enough size for the struct */
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb > header_size %u %u\n",
+		        msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	if (dt1->segmenting != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "This packet has segmenting, not supported: %d\n", dt1->segmenting);
+		return -1;
+	}
+
+	result->destination_local_reference = &dt1->destination_local_reference;
+
+	/* some more  size checks in here */
+	if (msgb_l2len(msgb) < variable_offset + dt1->variable_start + 1) {
+		LOGP(DSCCP, LOGL_ERROR, "Not enough space for variable start: %u %u\n",
+			msgb_l2len(msgb), dt1->variable_start);
+		return -1;
+	}
+
+	result->data_len = msgb->l2h[variable_offset + dt1->variable_start];
+	msgb->l3h = &msgb->l2h[dt1->variable_start + variable_offset + 1];
+
+	if (msgb_l3len(msgb) < result->data_len) {
+		LOGP(DSCCP, LOGL_ERROR, "Not enough room for the payload: %u %u\n",
+			msgb_l3len(msgb), result->data_len);
+		return -1;
+	}
+
+	return 0;
+}
+
+int _sccp_parse_udt(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static const u_int32_t header_size = sizeof(struct sccp_data_unitdata);
+	static const u_int32_t called_offset = offsetof(struct sccp_data_unitdata, variable_called);
+	static const u_int32_t calling_offset = offsetof(struct sccp_data_unitdata, variable_calling);
+	static const u_int32_t data_offset = offsetof(struct sccp_data_unitdata, variable_data);
+
+	struct sccp_data_unitdata *udt = (struct sccp_data_unitdata *)msgb->l2h;
+
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
+		        msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	/* copy out the calling and called address. Add the off */
+	if (copy_address(&result->called, called_offset + udt->variable_called, msgb) != 0)
+		return -1;
+
+	if (check_address(&result->called) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "Invalid called address according to 08.06: 0x%x 0x%x\n",
+			*(u_int8_t *)&result->called.address, result->called.ssn);
+		return -1;
+	}
+
+	if (copy_address(&result->calling, calling_offset + udt->variable_calling, msgb) != 0)
+		return -1;
+
+	if (check_address(&result->calling) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "Invalid called address according to 08.06: 0x%x 0x%x\n",
+			*(u_int8_t *)&result->called.address, result->called.ssn);
+	}
+
+	/* we don't have enough size for the data */
+	if (msgb_l2len(msgb) < data_offset + udt->variable_data + 1) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header + offset %u %u %u\n",
+			msgb_l2len(msgb), header_size, udt->variable_data);
+		return -1;
+	}
+
+
+	msgb->l3h = &udt->data[udt->variable_data];
+	result->data_len = msgb_l3len(msgb);
+
+	if (msgb_l3len(msgb) !=  msgb->l3h[-1]) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb is truncated is: %u should: %u\n",
+			msgb_l3len(msgb), msgb->l3h[-1]);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int _sccp_parse_it(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static const u_int32_t header_size = sizeof(struct sccp_data_it);
+
+	struct sccp_data_it *it;
+
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
+		        msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	it = (struct sccp_data_it *) msgb->l2h;
+	result->data_len = 0;
+	result->source_local_reference = &it->source_local_reference;
+	result->destination_local_reference = &it->destination_local_reference;
+	return 0;
+}
+
+static int _sccp_parse_err(struct msgb *msgb, struct sccp_parse_result *result)
+{
+	static const u_int32_t header_size = sizeof(struct sccp_proto_err);
+
+	struct sccp_proto_err *err;
+
+	if (msgb_l2len(msgb) < header_size) {
+		LOGP(DSCCP, LOGL_ERROR, "msgb < header_size %u %u\n",
+		     msgb_l2len(msgb), header_size);
+		return -1;
+	}
+
+	err = (struct sccp_proto_err *) msgb->l2h;
+	result->data_len = 0;
+	result->destination_local_reference = &err->destination_local_reference;
+	return 0;
+}
+
+/*
+ * Send UDT. Currently we have a fixed address...
+ */
+static int _sccp_send_data(int class, const struct sockaddr_sccp *in,
+			   const struct sockaddr_sccp *out, struct msgb *payload)
+{
+	struct sccp_data_unitdata *udt;
+	u_int8_t *data;
+
+	if (msgb_l3len(payload) > 256) {
+		LOGP(DSCCP, LOGL_ERROR, "The payload is too big for one udt\n");
+		return -1;
+	}
+
+	struct msgb *msg = msgb_alloc_headroom(SCCP_MSG_SIZE,
+					       SCCP_MSG_HEADROOM, "sccp: udt");
+	msg->l2h = &msg->data[0];
+	udt = (struct sccp_data_unitdata *)msgb_put(msg, sizeof(*udt));
+
+	udt->type = SCCP_MSG_TYPE_UDT;
+	udt->proto_class = class;
+	udt->variable_called = 3;
+	udt->variable_calling = 5;
+	udt->variable_data = 7;
+
+	/* for variable data we start with a size and the data */
+	data = msgb_put(msg, 1 + 2);
+	data[0] = 2;
+	data[1] = 0x42;
+	data[2] = out->sccp_ssn;
+
+	data = msgb_put(msg, 1 + 2);
+	data[0] = 2;
+	data[1] = 0x42;
+	data[2] = in->sccp_ssn;
+
+	/* copy the payload */
+	data = msgb_put(msg, 1 + msgb_l3len(payload));
+	data[0] = msgb_l3len(payload);
+	memcpy(&data[1], payload->l3h, msgb_l3len(payload));
+
+	_send_msg(msg);
+	return 0;
+}
+
+static int _sccp_handle_read(struct msgb *msgb)
+{
+	struct sccp_data_callback *cb;
+	struct sccp_parse_result result;
+
+	if (_sccp_parse_udt(msgb, &result) != 0)
+		return -1;
+
+	cb = _find_ssn(result.called.ssn);
+	if (!cb || !cb->read_cb) {
+		LOGP(DSCCP, LOGL_ERROR, "No routing for UDT for called SSN: %u\n", result.called.ssn);
+		return -1;
+	}
+
+	/* sanity check */
+	return cb->read_cb(msgb, msgb_l3len(msgb), cb->read_context);
+}
+
+/*
+ * handle connection orientated methods
+ */
+static int source_local_reference_is_free(struct sccp_source_reference *reference)
+{
+	struct sccp_connection *connection;
+
+	llist_for_each_entry(connection, &sccp_connections, list) {
+		if (memcmp(reference, &connection->source_local_reference, sizeof(*reference)) == 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+static int destination_local_reference_is_free(struct sccp_source_reference *reference)
+{
+	struct sccp_connection *connection;
+
+	llist_for_each_entry(connection, &sccp_connections, list) {
+		if (memcmp(reference, &connection->destination_local_reference, sizeof(*reference)) == 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+static int assign_source_local_reference(struct sccp_connection *connection)
+{
+	static u_int32_t last_ref = 0x30000;
+	int wrapped = 0;
+
+	do {
+		struct sccp_source_reference reference;
+		reference.octet1 = (last_ref >>  0) & 0xff;
+		reference.octet2 = (last_ref >>  8) & 0xff;
+		reference.octet3 = (last_ref >> 16) & 0xff;
+
+		++last_ref;
+		/* do not use the reversed word and wrap around */
+		if ((last_ref & 0x00FFFFFF) == 0x00FFFFFF) {
+			LOGP(DSCCP, LOGL_DEBUG, "Wrapped searching for a free code\n");
+			last_ref = 0;
+			++wrapped;
+		}
+
+		if (source_local_reference_is_free(&reference) == 0) {
+			connection->source_local_reference = reference;
+			return 0;
+		}
+	} while (wrapped != 2);
+
+	LOGP(DSCCP, LOGL_ERROR, "Finding a free reference failed\n");
+	return -1;
+}
+
+static void _sccp_set_connection_state(struct sccp_connection *connection, int new_state)
+{
+	int old_state = connection->connection_state;
+
+	connection->connection_state = new_state;
+	if (connection->state_cb)
+		connection->state_cb(connection, old_state);
+}
+
+static int _sccp_send_refuse(struct sccp_source_reference *src_ref, int cause)
+{
+	struct msgb *msgb;
+	struct sccp_connection_refused *ref;
+	u_int8_t *data;
+
+	msgb = msgb_alloc_headroom(SCCP_MSG_SIZE,
+				   SCCP_MSG_HEADROOM, "sccp ref");
+	msgb->l2h = &msgb->data[0];
+
+	ref = (struct sccp_connection_refused *) msgb_put(msgb, sizeof(*ref));
+	ref->type = SCCP_MSG_TYPE_CREF;
+	memcpy(&ref->destination_local_reference, src_ref,
+	       sizeof(struct sccp_source_reference));
+	ref->cause = cause;
+	ref->optional_start = 1;
+
+	data = msgb_put(msgb, 1);
+	data[0] = SCCP_PNC_END_OF_OPTIONAL;
+
+	_send_msg(msgb);
+	return 0;
+}
+
+static int _sccp_send_connection_confirm(struct sccp_connection *connection)
+{
+	struct msgb *response;
+	struct sccp_connection_confirm *confirm;
+	u_int8_t *optional_data;
+
+	if (assign_source_local_reference(connection) != 0)
+		return -1;
+
+	response = msgb_alloc_headroom(SCCP_MSG_SIZE,
+				       SCCP_MSG_HEADROOM, "sccp confirm");
+	response->l2h = &response->data[0];
+
+	confirm = (struct sccp_connection_confirm *) msgb_put(response, sizeof(*confirm));
+
+	confirm->type = SCCP_MSG_TYPE_CC;
+	memcpy(&confirm->destination_local_reference,
+	       &connection->destination_local_reference,
+	       sizeof(connection->destination_local_reference));
+	memcpy(&confirm->source_local_reference,
+	       &connection->source_local_reference,
+	       sizeof(connection->source_local_reference));
+	confirm->proto_class = 2;
+	confirm->optional_start = 1;
+
+	optional_data = (u_int8_t *) msgb_put(response, 1);
+	optional_data[0] = SCCP_PNC_END_OF_OPTIONAL;
+
+	_send_msg(response);
+	_sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_ESTABLISHED);
+	return 0;
+}
+
+static int _sccp_send_connection_request(struct sccp_connection *connection,
+					 const struct sockaddr_sccp *called, struct msgb *msg)
+{
+	struct msgb *request;
+	struct sccp_connection_request *req;
+	u_int8_t *data;
+	u_int8_t extra_size = 3 + 1;
+
+
+	if (msg && (msgb_l3len(msg) < 3 || msgb_l3len(msg) > 130)) {
+		LOGP(DSCCP, LOGL_ERROR, "Invalid amount of data... %d\n", msgb_l3len(msg));
+		return -1;
+	}
+
+	/* try to find a id */
+	if (assign_source_local_reference(connection) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "Assigning a local reference failed.\n");
+		_sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_SETUP_ERROR);
+		return -1;
+	}
+
+
+	if (msg)
+		extra_size += 2 + msgb_l3len(msg);
+	request = msgb_alloc_headroom(SCCP_MSG_SIZE,
+				      SCCP_MSG_HEADROOM, "sccp connection request");
+	request->l2h = &request->data[0];
+	req = (struct sccp_connection_request *) msgb_put(request, sizeof(*req));
+
+	req->type = SCCP_MSG_TYPE_CR;
+	memcpy(&req->source_local_reference, &connection->source_local_reference,
+	       sizeof(connection->source_local_reference));
+	req->proto_class = 2;
+	req->variable_called = 2;
+	req->optional_start = 4;
+
+	/* write the called party address */
+	data = msgb_put(request, 1 + 2);
+	data[0] = 2;
+	data[1] = 0x42;
+	data[2] = called->sccp_ssn;
+
+	/* write the payload */
+	if (msg) {
+	    data = msgb_put(request, 2 + msgb_l3len(msg));
+	    data[0] = SCCP_PNC_DATA;
+	    data[1] = msgb_l3len(msg);
+	    memcpy(&data[2], msg->l3h, msgb_l3len(msg));
+	}
+
+	data = msgb_put(request, 1);
+	data[0] = SCCP_PNC_END_OF_OPTIONAL;
+
+	llist_add_tail(&connection->list, &sccp_connections);
+	_sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_REQUEST);
+
+	_send_msg(request);
+	return 0;
+}
+
+static int _sccp_send_connection_data(struct sccp_connection *conn, struct msgb *_data)
+{
+	struct msgb *msgb;
+	struct sccp_data_form1 *dt1;
+	u_int8_t *data;
+	int extra_size;
+
+	if (msgb_l3len(_data) < 2 || msgb_l3len(_data) > 256) {
+		LOGP(DSCCP, LOGL_ERROR, "data size too big, segmenting unimplemented.\n");
+		return -1;
+	}
+
+	extra_size = 1 + msgb_l3len(_data);
+	msgb = msgb_alloc_headroom(SCCP_MSG_SIZE,
+				   SCCP_MSG_HEADROOM, "sccp dt1");
+	msgb->l2h = &msgb->data[0];
+
+	dt1 = (struct sccp_data_form1 *) msgb_put(msgb, sizeof(*dt1));
+	dt1->type = SCCP_MSG_TYPE_DT1;
+	memcpy(&dt1->destination_local_reference, &conn->destination_local_reference,
+	       sizeof(struct sccp_source_reference));
+	dt1->segmenting = 0;
+
+	/* copy the data */
+	dt1->variable_start = 1;
+	data = msgb_put(msgb, extra_size);
+	data[0] = extra_size - 1;
+	memcpy(&data[1], _data->l3h, extra_size - 1);
+
+	_send_msg(msgb);
+	return 0;
+}
+
+static int _sccp_send_connection_it(struct sccp_connection *conn)
+{
+	struct msgb *msgb;
+	struct sccp_data_it *it;
+
+	msgb = msgb_alloc_headroom(SCCP_MSG_SIZE,
+				   SCCP_MSG_HEADROOM, "sccp it");
+	msgb->l2h = &msgb->data[0];
+	it = (struct sccp_data_it *) msgb_put(msgb, sizeof(*it));
+	it->type = SCCP_MSG_TYPE_IT;
+	memcpy(&it->destination_local_reference, &conn->destination_local_reference,
+		sizeof(struct sccp_source_reference));
+	memcpy(&it->source_local_reference, &conn->source_local_reference,
+		sizeof(struct sccp_source_reference));
+
+	it->proto_class = 0x2;
+	it->sequencing[0] = it->sequencing[1] = 0;
+	it->credit = 0;
+
+	_send_msg(msgb);
+	return 0;
+}
+
+static int _sccp_send_connection_released(struct sccp_connection *conn, int cause)
+{
+	struct msgb *msg;
+	struct sccp_connection_released *rel;
+	u_int8_t *data;
+
+	msg = msgb_alloc_headroom(SCCP_MSG_SIZE, SCCP_MSG_HEADROOM,
+				  "sccp: connection released");
+	msg->l2h = &msg->data[0];
+	rel = (struct sccp_connection_released *) msgb_put(msg, sizeof(*rel));
+	rel->type = SCCP_MSG_TYPE_RLSD;
+	rel->release_cause = cause;
+
+	/* copy the source references */
+	memcpy(&rel->destination_local_reference, &conn->destination_local_reference,
+	       sizeof(struct sccp_source_reference));
+	memcpy(&rel->source_local_reference, &conn->source_local_reference,
+	       sizeof(struct sccp_source_reference));
+
+	data = msgb_put(msg, 1);
+	data[0] = SCCP_PNC_END_OF_OPTIONAL;
+
+	_sccp_set_connection_state(conn, SCCP_CONNECTION_STATE_RELEASE);
+	_send_msg(msg);
+	return 0;
+}
+
+/*
+ * Open a connection. The following is going to happen:
+ *
+ *	- Verify the packet, e.g. that we have no other connection
+ *	  that id.
+ *      - Ask the user if he wants to accept the connection
+ *      - Try to open the connection by assigning a source local reference
+ *        and sending the packet
+ */
+static int _sccp_handle_connection_request(struct msgb *msgb)
+{
+	struct sccp_parse_result result;
+
+	struct sccp_data_callback *cb;
+	struct sccp_connection *connection;
+
+	if (_sccp_parse_connection_request(msgb, &result) != 0)
+		return -1;
+
+	cb = _find_ssn(result.called.ssn);
+	if (!cb || !cb->accept_cb) {
+		LOGP(DSCCP, LOGL_ERROR, "No routing for CR for called SSN: %u\n", result.called.ssn);
+		return -1;
+	}
+
+	/* check if the system wants this connection */
+	connection = talloc_zero(tall_sccp_ctx, struct sccp_connection);
+	if (!connection) {
+		LOGP(DSCCP, LOGL_ERROR, "Allocation failed\n");
+		return -1;
+	}
+
+	/*
+	 * sanity checks:
+	 *	- Is the source_local_reference in any other connection?
+	 * then will call accept, assign a "destination" local reference
+	 * and send a connection confirm, otherwise we will send a refuseed
+	 * one....
+	 */
+	if (destination_local_reference_is_free(result.source_local_reference) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "Need to reject connection with existing reference\n");
+		_sccp_send_refuse(result.source_local_reference, SCCP_REFUSAL_SCCP_FAILURE);
+		talloc_free(connection);
+		return -1;
+	}
+
+	connection->incoming = 1;
+	connection->destination_local_reference = *result.source_local_reference;
+
+	if (cb->accept_cb(connection, cb->accept_context) != 0) {
+		_sccp_send_refuse(result.source_local_reference, SCCP_REFUSAL_END_USER_ORIGINATED);
+		_sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_REFUSED);
+		talloc_free(connection);
+		return 0;
+	}
+
+
+	llist_add_tail(&connection->list, &sccp_connections);
+
+	if (_sccp_send_connection_confirm(connection) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "Sending confirm failed... no available source reference?\n");
+
+		_sccp_send_refuse(result.source_local_reference, SCCP_REFUSAL_SCCP_FAILURE);
+		_sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_REFUSED);
+		llist_del(&connection->list);
+		talloc_free(connection);
+
+		return -1;
+	}
+
+	/*
+	 * If we have data let us forward things.
+	 */
+	if (result.data_len != 0 && connection->data_cb) {
+		connection->data_cb(connection, msgb, result.data_len);
+	}
+
+	return 0;
+}
+
+/* Handle the release confirmed */
+static int _sccp_handle_connection_release_complete(struct msgb *msgb)
+{
+	struct sccp_parse_result result;
+	struct sccp_connection *conn;
+
+	if (_sccp_parse_connection_release_complete(msgb, &result) != 0)
+		return -1;
+
+	/* find the connection */
+	llist_for_each_entry(conn, &sccp_connections, list) {
+		if (conn->data_cb
+		    && memcmp(&conn->source_local_reference,
+			      result.destination_local_reference,
+			      sizeof(conn->source_local_reference)) == 0
+		    && memcmp(&conn->destination_local_reference,
+			      result.source_local_reference,
+			      sizeof(conn->destination_local_reference)) == 0) {
+		    goto found;
+		}
+	}
+
+
+	LOGP(DSCCP, LOGL_ERROR, "Release complete of unknown connection\n");
+	return -1;
+
+found:
+	llist_del(&conn->list);
+	_sccp_set_connection_state(conn, SCCP_CONNECTION_STATE_RELEASE_COMPLETE);
+	return 0;
+}
+
+/* Handle the Data Form 1 message */
+static int _sccp_handle_connection_dt1(struct msgb *msgb)
+{
+	struct sccp_parse_result result;
+	struct sccp_connection *conn;
+
+	if (_sccp_parse_connection_dt1(msgb, &result) != 0)
+		return -1;
+
+	/* lookup if we have a connection with the given reference */
+	llist_for_each_entry(conn, &sccp_connections, list) {
+		if (conn->data_cb
+		    && memcmp(&conn->source_local_reference,
+			      result.destination_local_reference,
+			      sizeof(conn->source_local_reference)) == 0) {
+			goto found;
+		}
+	}
+
+	LOGP(DSCCP, LOGL_ERROR, "No connection found for dt1 data\n");
+	return -1;
+
+found:
+	conn->data_cb(conn, msgb, result.data_len);
+	return 0;
+}
+
+/* confirm a connection release */
+static int _sccp_send_connection_release_complete(struct sccp_connection *connection)
+{
+	struct msgb *msgb;
+	struct sccp_connection_release_complete *rlc;
+
+	msgb = msgb_alloc_headroom(SCCP_MSG_SIZE,
+				   SCCP_MSG_HEADROOM, "sccp rlc");
+	msgb->l2h = &msgb->data[0];
+
+	rlc = (struct sccp_connection_release_complete *) msgb_put(msgb, sizeof(*rlc));
+	rlc->type = SCCP_MSG_TYPE_RLC;
+	memcpy(&rlc->destination_local_reference,
+	       &connection->destination_local_reference, sizeof(struct sccp_source_reference));
+	memcpy(&rlc->source_local_reference,
+	       &connection->source_local_reference, sizeof(struct sccp_source_reference));
+
+	_send_msg(msgb);
+
+	/*
+	 * Remove from the list of active connections and set the state. User code
+	 * should now free the entry.
+	 */
+	llist_del(&connection->list);
+	_sccp_set_connection_state(connection, SCCP_CONNECTION_STATE_RELEASE_COMPLETE);
+	return 0;
+}
+
+/* connection released, send a released confirm */
+static int _sccp_handle_connection_released(struct msgb *msgb)
+{
+	struct sccp_parse_result result;
+	struct sccp_connection *conn;
+
+	if (_sccp_parse_connection_released(msgb, &result) == -1)
+		return -1;
+
+	/* lookup if we have a connection with the given reference */
+	llist_for_each_entry(conn, &sccp_connections, list) {
+		if (conn->data_cb
+		    && memcmp(&conn->source_local_reference,
+			      result.destination_local_reference,
+			      sizeof(conn->source_local_reference)) == 0
+		    && memcmp(&conn->destination_local_reference,
+			      result.source_local_reference,
+			      sizeof(conn->destination_local_reference)) == 0) {
+		    goto found;
+		}
+	}
+
+
+	LOGP(DSCCP, LOGL_ERROR, "Unknown connection was released.\n");
+	return -1;
+
+	/* we have found a connection */
+found:
+	/* optional data */
+	if (result.data_len != 0 && conn->data_cb) {
+		conn->data_cb(conn, msgb, result.data_len);
+	}
+
+	/* generate a response */
+	if (_sccp_send_connection_release_complete(conn) != 0) {
+		LOGP(DSCCP, LOGL_ERROR, "Sending release confirmed failed\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int _sccp_handle_connection_refused(struct msgb *msgb)
+{
+	struct sccp_parse_result result;
+	struct sccp_connection *conn;
+
+	if (_sccp_parse_connection_refused(msgb, &result) != 0)
+		return -1;
+
+	/* lookup if we have a connection with the given reference */
+	llist_for_each_entry(conn, &sccp_connections, list) {
+		if (conn->incoming == 0 && conn->data_cb
+		    && memcmp(&conn->source_local_reference,
+			      result.destination_local_reference,
+			      sizeof(conn->source_local_reference)) == 0) {
+		    goto found;
+		}
+	}
+
+	LOGP(DSCCP, LOGL_ERROR, "Refused but no connection found\n");
+	return -1;
+
+found:
+	/* optional data */
+	if (result.data_len != 0 && conn->data_cb) {
+		conn->data_cb(conn, msgb, result.data_len);
+	}
+
+
+	llist_del(&conn->list);
+	_sccp_set_connection_state(conn, SCCP_CONNECTION_STATE_REFUSED);
+	return 0;
+}
+
+static int _sccp_handle_connection_confirm(struct msgb *msgb)
+{
+	struct sccp_parse_result result;
+	struct sccp_connection *conn;
+
+	if (_sccp_parse_connection_confirm(msgb, &result) != 0)
+		return -1;
+
+	/* lookup if we have a connection with the given reference */
+	llist_for_each_entry(conn, &sccp_connections, list) {
+		if (conn->incoming == 0 && conn->data_cb
+		    && memcmp(&conn->source_local_reference,
+			      result.destination_local_reference,
+			      sizeof(conn->source_local_reference)) == 0) {
+		    goto found;
+		}
+	}
+
+	LOGP(DSCCP, LOGL_ERROR, "Confirmed but no connection found\n");
+	return -1;
+
+found:
+	/* copy the addresses of the connection */
+	conn->destination_local_reference = *result.source_local_reference;
+	_sccp_set_connection_state(conn, SCCP_CONNECTION_STATE_ESTABLISHED);
+
+	/* optional data */
+	if (result.data_len != 0 && conn->data_cb) {
+		conn->data_cb(conn, msgb, result.data_len);
+	}
+
+	return 0;
+}
+
+
+int sccp_system_init(void (*outgoing)(struct msgb *data, void *ctx), void *ctx)
+{
+	sccp_system.write_data = outgoing;
+	sccp_system.write_context = ctx;
+
+	return 0;
+}
+
+/* oh my god a real SCCP packet. need to dispatch it now */
+int sccp_system_incoming(struct msgb *msgb)
+{
+	if (msgb_l2len(msgb) < 1 ) {
+		LOGP(DSCCP, LOGL_ERROR, "Too short packet\n");
+		return -1;
+	}
+
+	int type = msgb->l2h[0];
+
+	switch(type) {
+	case SCCP_MSG_TYPE_CR:
+		return _sccp_handle_connection_request(msgb);
+		break;
+	case SCCP_MSG_TYPE_RLSD:
+		return _sccp_handle_connection_released(msgb);
+		break;
+	case SCCP_MSG_TYPE_CREF:
+		return _sccp_handle_connection_refused(msgb);
+		break;
+	case SCCP_MSG_TYPE_CC:
+		return _sccp_handle_connection_confirm(msgb);
+		break;
+	case SCCP_MSG_TYPE_RLC:
+		return _sccp_handle_connection_release_complete(msgb);
+		break;
+	case SCCP_MSG_TYPE_DT1:
+		return _sccp_handle_connection_dt1(msgb);
+		break;
+	case SCCP_MSG_TYPE_UDT:
+		return _sccp_handle_read(msgb);
+		break;
+	default:
+		LOGP(DSCCP, LOGL_ERROR, "unimplemented msg type: %d\n", type);
+	};
+
+	return -1;
+}
+
+/* create a packet from the data */
+int sccp_connection_write(struct sccp_connection *connection, struct msgb *data)
+{
+	if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM
+	    || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
+		LOGP(DSCCP, LOGL_ERROR, "sccp_connection_write: Wrong connection state: %p %d\n",
+		       connection, connection->connection_state);
+		return -1;
+	}
+
+	return _sccp_send_connection_data(connection, data);
+}
+
+/*
+ * Send a Inactivity Test message. The owner of the connection
+ * should start a timer and call this method regularily. Calling
+ * this every 60 seconds should be good enough.
+ */
+int sccp_connection_send_it(struct sccp_connection *connection)
+{
+	if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM
+	    || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
+		LOGP(DSCCP, LOGL_ERROR, "sccp_connection_write: Wrong connection state: %p %d\n",
+		       connection, connection->connection_state);
+		return -1;
+	}
+
+	return _sccp_send_connection_it(connection);
+}
+
+/* send a connection release and wait for the connection released */
+int sccp_connection_close(struct sccp_connection *connection, int cause)
+{
+	if (connection->connection_state < SCCP_CONNECTION_STATE_CONFIRM
+	    || connection->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
+		LOGP(DSCCP, LOGL_ERROR, "Can not close the connection. It was never opened: %p %d\n",
+			connection, connection->connection_state);
+		return -1;
+	}
+
+	return _sccp_send_connection_released(connection, cause);
+}
+
+int sccp_connection_free(struct sccp_connection *connection)
+{
+	if (connection->connection_state > SCCP_CONNECTION_STATE_NONE
+	    && connection->connection_state < SCCP_CONNECTION_STATE_RELEASE_COMPLETE) {
+		LOGP(DSCCP, LOGL_ERROR, "The connection needs to be released before it is freed");
+		return -1;
+	}
+
+	talloc_free(connection);
+	return 0;
+}
+
+int sccp_connection_force_free(struct sccp_connection *con)
+{
+	if (con->connection_state > SCCP_CONNECTION_STATE_NONE &&
+	    con->connection_state < SCCP_CONNECTION_STATE_RELEASE_COMPLETE)
+		llist_del(&con->list);
+
+	con->connection_state = SCCP_CONNECTION_STATE_REFUSED;
+	sccp_connection_free(con);
+        return 0;
+}
+
+struct sccp_connection *sccp_connection_socket(void)
+{
+	return talloc_zero(tall_sccp_ctx, struct sccp_connection);
+}
+
+int sccp_connection_connect(struct sccp_connection *conn,
+			    const struct sockaddr_sccp *local,
+			    struct msgb *data)
+{
+	return _sccp_send_connection_request(conn, local, data);
+}
+
+int sccp_connection_set_incoming(const struct sockaddr_sccp *sock,
+				 int (*accept_cb)(struct sccp_connection *, void *), void *context)
+{
+	struct sccp_data_callback *cb;
+
+	if (!sock)
+		return -2;
+
+	cb = _find_ssn(sock->sccp_ssn);
+	if (!cb)
+		return -1;
+
+	cb->accept_cb = accept_cb;
+	cb->accept_context = context;
+	return 0;
+}
+
+int sccp_write(struct msgb *data, const struct sockaddr_sccp *in,
+	       const struct sockaddr_sccp *out, int class)
+{
+	return _sccp_send_data(class, in, out, data);
+}
+
+int sccp_set_read(const struct sockaddr_sccp *sock,
+		  int (*read_cb)(struct msgb *, unsigned int, void *), void *context)
+{
+	struct sccp_data_callback *cb;
+
+	if (!sock)
+		return -2;
+
+	cb  = _find_ssn(sock->sccp_ssn);
+	if (!cb)
+		return -1;
+
+	cb->read_cb = read_cb;
+	cb->read_context = context;
+	return 0;
+}
+
+static_assert(sizeof(struct sccp_source_reference) <= sizeof(u_int32_t), enough_space);
+
+u_int32_t sccp_src_ref_to_int(struct sccp_source_reference *ref)
+{
+	u_int32_t src_ref = 0;
+	memcpy(&src_ref, ref, sizeof(*ref));
+	return src_ref;
+}
+
+struct sccp_source_reference sccp_src_ref_from_int(u_int32_t int_ref)
+{
+	struct sccp_source_reference ref;
+	memcpy(&ref, &int_ref, sizeof(ref));
+	return ref;
+}
+
+int sccp_determine_msg_type(struct msgb *msg)
+{
+	if (msgb_l2len(msg) < 1)
+		return -1;
+
+	return msg->l2h[0];
+}
+
+int sccp_parse_header(struct msgb *msg, struct sccp_parse_result *result)
+{
+	int type;
+
+	if (msgb_l2len(msg) < 1)
+		return -1;
+
+	type = msg->l2h[0];
+	switch(type) {
+	case SCCP_MSG_TYPE_CR:
+		return _sccp_parse_connection_request(msg, result);
+		break;
+	case SCCP_MSG_TYPE_RLSD:
+		return _sccp_parse_connection_released(msg, result);
+		break;
+	case SCCP_MSG_TYPE_CREF:
+		return _sccp_parse_connection_refused(msg, result);
+		break;
+	case SCCP_MSG_TYPE_CC:
+		return _sccp_parse_connection_confirm(msg, result);
+		break;
+	case SCCP_MSG_TYPE_RLC:
+		return _sccp_parse_connection_release_complete(msg, result);
+		break;
+	case SCCP_MSG_TYPE_DT1:
+		return _sccp_parse_connection_dt1(msg, result);
+		break;
+	case SCCP_MSG_TYPE_UDT:
+		return _sccp_parse_udt(msg, result);
+		break;
+	case SCCP_MSG_TYPE_IT:
+		return _sccp_parse_it(msg, result);
+		break;
+	case SCCP_MSG_TYPE_ERR:
+		return _sccp_parse_err(msg, result);
+		break;
+	};
+
+	LOGP(DSCCP, LOGL_ERROR, "Unimplemented MSG Type: 0x%x\n", type);
+	return -1;
+}
+
+static __attribute__((constructor)) void on_dso_load(void)
+{
+	tall_sccp_ctx = talloc_named_const(NULL, 1, "sccp");
+}
+
+static __attribute__((destructor)) void on_dso_unload(void)
+{
+	talloc_report_full(tall_sccp_ctx, stderr);
+}
diff --git a/openbsc/src/silent_call.c b/openbsc/src/silent_call.c
new file mode 100644
index 0000000..8bd5341
--- /dev/null
+++ b/openbsc/src/silent_call.c
@@ -0,0 +1,146 @@
+/* GSM silent call feature */
+
+/*
+ * (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocore/msgb.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+
+/* paging of the requested subscriber has completed */
+static int paging_cb_silent(unsigned int hooknum, unsigned int event,
+			    struct msgb *msg, void *_lchan, void *_data)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_lchan *lchan = _lchan;
+	struct scall_signal_data sigdata;
+	int rc;
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	DEBUGP(DSMS, "paging_cb_silent: ");
+
+	conn = &lchan->conn;
+
+	sigdata.lchan = lchan;
+	sigdata.data = _data;
+
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		DEBUGPC(DSMS, "success, using Timeslot %u on ARFCN %u\n",
+			lchan->ts->nr, lchan->ts->trx->arfcn);
+		conn->silent_call = 1;
+		/* increment lchan reference count */
+		dispatch_signal(SS_SCALL, S_SCALL_SUCCESS, &sigdata);
+		use_subscr_con(conn);
+		break;
+	case GSM_PAGING_EXPIRED:
+		DEBUGP(DSMS, "expired\n");
+		dispatch_signal(SS_SCALL, S_SCALL_EXPIRED, &sigdata);
+		break;
+	default:
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+/* receive a layer 3 message from a silent call */
+int silent_call_rx(struct msgb *msg)
+{
+	/* FIXME: do something like sending it through a UDP port */
+	return 0;
+}
+
+struct msg_match {
+	u_int8_t pdisc;
+	u_int8_t msg_type;
+};
+
+/* list of messages that are handled inside OpenBSC, even in a silent call */
+static const struct msg_match silent_call_accept[] = {
+	{ GSM48_PDISC_MM, GSM48_MT_MM_LOC_UPD_REQUEST },
+	{ GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_REQ },
+};
+
+/* decide if we need to reroute a message as part of a silent call */
+int silent_call_reroute(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t pdisc = gh->proto_discr & 0x0f;
+	int i;
+
+	/* if we're not part of a silent call, never reroute */
+	if (!msg->lchan->conn.silent_call)
+		return 0;
+
+	/* check if we are a special message that is handled in openbsc */
+	for (i = 0; i < ARRAY_SIZE(silent_call_accept); i++) {
+		if (silent_call_accept[i].pdisc == pdisc &&
+		    silent_call_accept[i].msg_type == gh->msg_type)
+			return 0;
+	}
+
+	/* otherwise, reroute */
+	return 1;
+}
+
+
+/* initiate a silent call with a given subscriber */
+int gsm_silent_call_start(struct gsm_subscriber *subscr, void *data, int type)
+{
+	int rc;
+
+	rc = paging_request(subscr->net, subscr, type,
+			    paging_cb_silent, data);
+	return rc;
+}
+
+/* end a silent call with a given subscriber */
+int gsm_silent_call_stop(struct gsm_subscriber *subscr)
+{
+	struct gsm_lchan *lchan;
+	struct gsm_subscriber_connection *conn;
+
+	lchan = lchan_for_subscr(subscr);
+	if (!lchan)
+		return -EINVAL;
+
+	/* did we actually establish a silent call for this guy? */
+	conn = &lchan->conn;
+	if (!conn->silent_call)
+		return -EINVAL;
+
+	put_subscr_con(conn);
+
+	return 0;
+}
diff --git a/openbsc/src/socket.c b/openbsc/src/socket.c
new file mode 100644
index 0000000..3ed4d42
--- /dev/null
+++ b/openbsc/src/socket.c
@@ -0,0 +1,94 @@
+/* OpenBSC sokcet code, taken from Abis input driver for ip.access */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/talloc.h>
+
+int make_sock(struct bsc_fd *bfd, int proto, u_int16_t port,
+	      int (*cb)(struct bsc_fd *fd, unsigned int what))
+{
+	struct sockaddr_in addr;
+	int ret, on = 1;
+	int type = SOCK_STREAM;
+
+	if (proto == IPPROTO_UDP)
+		type = SOCK_DGRAM;
+
+	bfd->fd = socket(AF_INET, type, proto);
+	bfd->cb = cb;
+	bfd->when = BSC_FD_READ;
+	//bfd->data = line;
+
+	if (bfd->fd < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not create TCP socket.\n");
+		return -EIO;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	addr.sin_addr.s_addr = INADDR_ANY;
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not bind l2 socket %s\n",
+			strerror(errno));
+		close(bfd->fd);
+		return -EIO;
+	}
+
+	if (proto != IPPROTO_UDP) {
+		ret = listen(bfd->fd, 1);
+		if (ret < 0) {
+			perror("listen");
+			return ret;
+		}
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		perror("register_listen_fd");
+		close(bfd->fd);
+		return ret;
+	}
+	return 0;
+}
diff --git a/openbsc/src/subchan_demux.c b/openbsc/src/subchan_demux.c
new file mode 100644
index 0000000..0d6c1fe
--- /dev/null
+++ b/openbsc/src/subchan_demux.c
@@ -0,0 +1,322 @@
+/* A E1 sub-channel (de)multiplexer with TRAU frame sync */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/subchan_demux.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+
+void *tall_tqe_ctx;
+
+static inline void append_bit(struct demux_subch *sch, u_int8_t bit)
+{
+	sch->out_bitbuf[sch->out_idx++] = bit;
+}
+
+#define SYNC_HDR_BITS	16
+static const u_int8_t nullbytes[SYNC_HDR_BITS];
+
+/* check if we have just completed the 16 bit zero sync header,
+ * in accordance with GSM TS 08.60 Chapter 4.8.1 */
+static int sync_hdr_complete(struct demux_subch *sch, u_int8_t bit)
+{
+	if (bit == 0)
+		sch->consecutive_zeros++;
+	else
+		sch->consecutive_zeros = 0;
+
+	if (sch->consecutive_zeros >= SYNC_HDR_BITS) {
+		sch->consecutive_zeros = 0;
+		return 1;
+	}
+
+	return 0;
+}
+
+/* resynchronize to current location */
+static void resync_to_here(struct demux_subch *sch)
+{
+	memset(sch->out_bitbuf, 0, SYNC_HDR_BITS);
+
+	/* set index in a way that we can continue receiving bits after
+	 * the end of the SYNC header */
+	sch->out_idx = SYNC_HDR_BITS;
+	sch->in_sync = 1;
+}
+
+int subch_demux_init(struct subch_demux *dmx)
+{
+	int i;
+
+	dmx->chan_activ = 0;
+	for (i = 0; i < NR_SUBCH; i++) {
+		struct demux_subch *sch = &dmx->subch[i];
+		sch->out_idx = 0;
+		memset(sch->out_bitbuf, 0xff, sizeof(sch->out_bitbuf));
+	}
+	return 0;
+}
+
+/* input some arbitrary (modulo 4) number of bytes of a 64k E1 channel,
+ * split it into the 16k subchannels */
+int subch_demux_in(struct subch_demux *dmx, u_int8_t *data, int len)
+{
+	int i, c;
+
+	/* we avoid partially filled bytes in outbuf */
+	if (len % 4)
+		return -EINVAL;
+
+	for (i = 0; i < len; i++) {
+		u_int8_t inbyte = data[i];
+
+		for (c = 0; c < NR_SUBCH; c++) {
+			struct demux_subch *sch = &dmx->subch[c];
+			u_int8_t inbits;
+			u_int8_t bit;
+
+			/* ignore inactive subchannels */
+			if (!(dmx->chan_activ & (1 << c)))
+				continue;
+
+			inbits = inbyte >> (c << 1);
+
+			/* two bits for each subchannel */
+			if (inbits & 0x01)
+				bit = 1;
+			else
+				bit = 0;
+			append_bit(sch, bit);
+
+			if (sync_hdr_complete(sch, bit))
+				resync_to_here(sch);
+
+			if (inbits & 0x02)
+				bit = 1;
+			else
+				bit = 0;
+			append_bit(sch, bit);
+
+			if (sync_hdr_complete(sch, bit))
+				resync_to_here(sch);
+
+			/* FIXME: verify the first bit in octet 2, 4, 6, ...
+			 * according to TS 08.60 4.8.1 */
+
+			/* once we have reached TRAU_FRAME_BITS, call
+			 * the TRAU frame handler callback function */
+			if (sch->out_idx >= TRAU_FRAME_BITS) {
+				if (sch->in_sync) {
+					dmx->out_cb(dmx, c, sch->out_bitbuf,
+					    sch->out_idx, dmx->data);
+					sch->in_sync = 0;
+				}
+				sch->out_idx = 0;
+			}
+		}
+	}
+	return i;
+}
+
+int subch_demux_activate(struct subch_demux *dmx, int subch)
+{
+	if (subch >= NR_SUBCH)
+		return -EINVAL;
+
+	dmx->chan_activ |= (1 << subch);
+	return 0;
+}
+
+int subch_demux_deactivate(struct subch_demux *dmx, int subch)
+{
+	if (subch >= NR_SUBCH)
+		return -EINVAL;
+
+	dmx->chan_activ &= ~(1 << subch);
+	return 0;
+}
+
+/* MULTIPLEXER */
+
+static int alloc_add_idle_frame(struct subch_mux *mx, int sch_nr)
+{
+	/* allocate and initialize with idle pattern */
+	return subchan_mux_enqueue(mx, sch_nr, trau_idle_frame(),
+				   TRAU_FRAME_BITS);
+}
+
+/* return the requested number of bits from the specified subchannel */
+static int get_subch_bits(struct subch_mux *mx, int subch,
+			  u_int8_t *bits, int num_requested)
+{
+	struct mux_subch *sch = &mx->subch[subch];
+	int num_bits = 0;
+
+	while (num_bits < num_requested) {
+		struct subch_txq_entry *txe;
+		int num_bits_left;
+		int num_bits_thistime;
+
+		/* make sure we have a valid entry at top of tx queue.
+		 * if not, add an idle frame */
+		if (llist_empty(&sch->tx_queue))
+			alloc_add_idle_frame(mx, subch);
+	
+		if (llist_empty(&sch->tx_queue))
+			return -EIO;
+
+		txe = llist_entry(sch->tx_queue.next, struct subch_txq_entry, list);
+		num_bits_left = txe->bit_len - txe->next_bit;
+
+		if (num_bits_left < num_requested)
+			num_bits_thistime = num_bits_left;
+		else
+			num_bits_thistime = num_requested;
+
+		/* pull the bits from the txe */
+		memcpy(bits + num_bits, txe->bits + txe->next_bit, num_bits_thistime);
+		txe->next_bit += num_bits_thistime;
+
+		/* free the tx_queue entry if it is fully consumed */
+		if (txe->next_bit >= txe->bit_len) {
+			llist_del(&txe->list);
+			talloc_free(txe);
+		}
+
+		/* increment global number of bits dequeued */
+		num_bits += num_bits_thistime;
+	}
+
+	return num_requested;
+}
+
+/* compact an array of 8 single-bit bytes into one byte of 8 bits */
+static u_int8_t compact_bits(const u_int8_t *bits)
+{
+	u_int8_t ret = 0;
+	int i;
+
+	for (i = 0; i < 8; i++)
+		ret |= (bits[i] ? 1 : 0) << i;
+
+	return ret;
+}
+
+/* obtain a single output byte from the subchannel muxer */
+static int mux_output_byte(struct subch_mux *mx, u_int8_t *byte)
+{
+	u_int8_t bits[8];
+	int rc;
+
+	/* combine two bits of every subchan */
+	rc = get_subch_bits(mx, 0, &bits[0], 2);
+	rc = get_subch_bits(mx, 1, &bits[2], 2);
+	rc = get_subch_bits(mx, 2, &bits[4], 2);
+	rc = get_subch_bits(mx, 3, &bits[6], 2);
+
+	*byte = compact_bits(bits);
+
+	return rc;
+}
+
+/* Request the output of some muxed bytes from the subchan muxer */
+int subchan_mux_out(struct subch_mux *mx, u_int8_t *data, int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		int rc;
+		rc = mux_output_byte(mx, &data[i]);
+		if (rc < 0)
+			break;
+	}
+	return i;
+}
+
+static int llist_len(struct llist_head *head)
+{
+	struct llist_head *entry;
+	int i = 0;
+
+	llist_for_each(entry, head)
+		i++;
+
+	return i;
+}
+
+/* evict the 'num_evict' number of oldest entries in the queue */
+static void tx_queue_evict(struct mux_subch *sch, int num_evict)
+{
+	struct subch_txq_entry *tqe;
+	int i;
+
+	for (i = 0; i < num_evict; i++) {
+		if (llist_empty(&sch->tx_queue))
+			return;
+
+		tqe = llist_entry(sch->tx_queue.next, struct subch_txq_entry, list);
+		llist_del(&tqe->list);
+		talloc_free(tqe);
+	}
+}
+
+/* enqueue some data into the tx_queue of a given subchannel */
+int subchan_mux_enqueue(struct subch_mux *mx, int s_nr, const u_int8_t *data,
+			int len)
+{
+	struct mux_subch *sch = &mx->subch[s_nr];
+	int list_len = llist_len(&sch->tx_queue);
+	struct subch_txq_entry *tqe = talloc_zero_size(tall_tqe_ctx,
+							sizeof(*tqe) + len);
+	if (!tqe)
+		return -ENOMEM;
+
+	tqe->bit_len = len;
+	memcpy(tqe->bits, data, len);
+
+	if (list_len > 2)
+		tx_queue_evict(sch, list_len-2);
+
+	llist_add_tail(&tqe->list, &sch->tx_queue);
+
+	return 0;
+}
+
+/* initialize one subchannel muxer instance */
+int subchan_mux_init(struct subch_mux *mx)
+{
+	int i;
+
+	memset(mx, 0, sizeof(*mx));
+	for (i = 0; i < NR_SUBCH; i++) {
+		struct mux_subch *sch = &mx->subch[i];
+		INIT_LLIST_HEAD(&sch->tx_queue);
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/system_information.c b/openbsc/src/system_information.c
new file mode 100644
index 0000000..3bd833a
--- /dev/null
+++ b/openbsc/src/system_information.c
@@ -0,0 +1,494 @@
+/* GSM 04.08 System Information (SI) encoding and decoding
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/rest_octets.h>
+#include <osmocore/bitvec.h>
+#include <openbsc/debug.h>
+
+#define GSM48_CELL_CHAN_DESC_SIZE	16
+#define GSM_MACBLOCK_LEN 		23
+#define GSM_MACBLOCK_PADDING		0x2b
+
+/* verify the sizes of the system information type structs */
+
+/* rest octets are not part of the struct */
+static_assert(sizeof(struct gsm48_system_information_type_header) == 3, _si_header_size);
+static_assert(sizeof(struct gsm48_rach_control) == 3, _si_rach_control);
+static_assert(sizeof(struct gsm48_system_information_type_1) == 22, _si1_size);
+static_assert(sizeof(struct gsm48_system_information_type_2) == 23, _si2_size);
+static_assert(sizeof(struct gsm48_system_information_type_3) == 19, _si3_size);
+static_assert(sizeof(struct gsm48_system_information_type_4) == 13, _si4_size);
+
+/* bs11 forgot the l2 len, 0-6 rest octets */
+static_assert(sizeof(struct gsm48_system_information_type_5) == 18, _si5_size);
+static_assert(sizeof(struct gsm48_system_information_type_6) == 11, _si6_size);
+
+static_assert(sizeof(struct gsm48_system_information_type_13) == 3, _si13_size);
+
+/* Frequency Lists as per TS 04.08 10.5.2.13 */
+
+/* 10.5.2.13.2: Bit map 0 format */
+static int freq_list_bm0_set_arfcn(u_int8_t *chan_list, unsigned int arfcn)
+{
+	unsigned int byte, bit;
+
+	if (arfcn > 124 || arfcn < 1) {
+		LOGP(DRR, LOGL_ERROR, "Bitmap 0 only supports ARFCN 1...124\n");
+		return -EINVAL;
+	}
+
+	/* the bitmask is from 1..124, not from 0..123 */
+	arfcn--;
+
+	byte = arfcn / 8;
+	bit = arfcn % 8;
+
+	chan_list[GSM48_CELL_CHAN_DESC_SIZE-1-byte] |= (1 << bit);
+
+	return 0;
+}
+
+/* 10.5.2.13.7: Variable bit map format */
+static int freq_list_bmrel_set_arfcn(u_int8_t *chan_list, unsigned int arfcn)
+{
+	unsigned int byte, bit;
+	unsigned int min_arfcn;
+	unsigned int bitno;
+
+	min_arfcn = (chan_list[0] & 1) << 9;
+	min_arfcn |= chan_list[1] << 1;
+	min_arfcn |= (chan_list[2] >> 7) & 1;
+
+	/* The lower end of our bitmaks is always implicitly included */
+	if (arfcn == min_arfcn)
+		return 0;
+
+	if (arfcn < min_arfcn) {
+		LOGP(DRR, LOGL_ERROR, "arfcn(%u) < min(%u)\n", arfcn, min_arfcn);
+		return -EINVAL;
+	}
+	if (arfcn > min_arfcn + 111) {
+		LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn);
+		return -EINVAL;
+	}
+
+	bitno = (arfcn - min_arfcn);
+	byte = bitno / 8;
+	bit = bitno % 8;
+
+	chan_list[2 + byte] |= 1 << (7 - bit);
+
+	return 0;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int bitvec2freq_list(u_int8_t *chan_list, struct bitvec *bv,
+			    const struct gsm_bts *bts)
+{
+	int i, rc, min = 1024, max = -1;
+
+	memset(chan_list, 0, 16);
+
+	/* GSM900-only handsets only support 'bit map 0 format' */
+	if (bts->band == GSM_BAND_900) {
+		chan_list[0] = 0;
+
+		for (i = 0; i < bv->data_len*8; i++) {
+			if (bitvec_get_bit_pos(bv, i)) {
+				rc = freq_list_bm0_set_arfcn(chan_list, i);
+				if (rc < 0)
+					return rc;
+			}
+		}
+		return 0;
+	}
+
+	/* We currently only support the 'Variable bitmap format' */
+	chan_list[0] = 0x8e;
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i)) {
+			if (i < min)
+				min = i;
+			if (i > max)
+				max = i;
+		}
+	}
+
+	if (max == -1) {
+		/* Empty set, use 'bit map 0 format' */
+		chan_list[0] = 0;
+		return 0;
+	}
+
+	if ((max - min) > 111) {
+		LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, "
+			"distance > 111\n", min, max);
+		return -EINVAL;
+	}
+
+	chan_list[0] |= (min >> 9) & 1;
+	chan_list[1] = (min >> 1);
+	chan_list[2] = (min & 1) << 7;
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i)) {
+			rc = freq_list_bmrel_set_arfcn(chan_list, i);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	return 0;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int generate_cell_chan_list(u_int8_t *chan_list, struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	struct bitvec *bv = &bts->si_common.cell_alloc;
+
+	/* first we generate a bitvec of all TRX ARFCN's in our BTS */
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		bitvec_set_bit_pos(bv, trx->arfcn, 1);
+
+	/* then we generate a GSM 04.08 frequency list from the bitvec */
+	return bitvec2freq_list(chan_list, bv, bts);
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int generate_bcch_chan_list(u_int8_t *chan_list, struct gsm_bts *bts)
+{
+	struct gsm_bts *cur_bts;
+	struct bitvec *bv = &bts->si_common.neigh_list;
+
+	/* first we generate a bitvec of the BCCH ARFCN's in our BSC */
+	llist_for_each_entry(cur_bts, &bts->network->bts_list, list) {
+		if (cur_bts == bts)
+			continue;
+		bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1);
+	}
+
+	/* then we generate a GSM 04.08 frequency list from the bitvec */
+	return bitvec2freq_list(chan_list, bv, bts);
+}
+
+static int generate_si1(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_1 *si1 =
+		(struct gsm48_system_information_type_1 *) output;
+
+	memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si1->header.l2_plen = (21 << 2) | 1;
+	si1->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si1->header.skip_indicator = 0;
+	si1->header.system_information = GSM48_MT_RR_SYSINFO_1;
+
+	rc = generate_cell_chan_list(si1->cell_channel_description, bts);
+	if (rc < 0)
+		return rc;
+
+	si1->rach_control = bts->si_common.rach_control;
+
+	/* SI1 Rest Octets (10.5.2.32), contains NCH position */
+	rc = rest_octets_si1(si1->rest_octets, NULL);
+	return sizeof(*si1) + rc;
+}
+
+static int generate_si2(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_2 *si2 =
+		(struct gsm48_system_information_type_2 *) output;
+
+	memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si2->header.l2_plen = (22 << 2) | 1;
+	si2->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si2->header.skip_indicator = 0;
+	si2->header.system_information = GSM48_MT_RR_SYSINFO_2;
+
+	rc = generate_bcch_chan_list(si2->bcch_frequency_list, bts);
+	if (rc < 0)
+		return rc;
+
+	si2->ncc_permitted = bts->si_common.ncc_permitted;
+	si2->rach_control = bts->si_common.rach_control;
+
+	return sizeof(*si2);
+}
+
+static struct gsm48_si_ro_info si_info = {
+	.selection_params = {
+		.present = 0,
+	},
+	.power_offset = {
+		.present = 0,
+	},
+	.si2ter_indicator = 0,
+	.early_cm_ctrl = 1,
+	.scheduling = {
+		.present = 0,
+	},
+	.gprs_ind = {
+		.si13_position = 0,
+		.ra_colour = 0,
+		.present = 1,
+	},
+	.lsa_params = {
+		.present = 0,
+	},
+	.cell_id = 0,	/* FIXME: doesn't the bts have this? */
+	.break_ind = 0,
+};
+
+static int generate_si3(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_3 *si3 =
+		(struct gsm48_system_information_type_3 *) output;
+
+	memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si3->header.l2_plen = (18 << 2) | 1;
+	si3->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si3->header.skip_indicator = 0;
+	si3->header.system_information = GSM48_MT_RR_SYSINFO_3;
+
+	si3->cell_identity = htons(bts->cell_identity);
+	gsm48_generate_lai(&si3->lai, bts->network->country_code,
+			   bts->network->network_code,
+			   bts->location_area_code);
+	si3->control_channel_desc = bts->si_common.chan_desc;
+	si3->cell_options = bts->si_common.cell_options;
+	si3->cell_sel_par = bts->si_common.cell_sel_par;
+	si3->rach_control = bts->si_common.rach_control;
+
+	/* SI3 Rest Octets (10.5.2.34), containing
+		CBQ, CELL_RESELECT_OFFSET, TEMPORARY_OFFSET, PENALTY_TIME
+		Power Offset, 2ter Indicator, Early Classmark Sending,
+		Scheduling if and WHERE, GPRS Indicator, SI13 position */
+	rc = rest_octets_si3(si3->rest_octets, &si_info);
+
+	return sizeof(*si3) + rc;
+}
+
+static int generate_si4(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_4 *si4 =
+		(struct gsm48_system_information_type_4 *) output;
+
+	/* length of all IEs present except SI4 rest octets and l2_plen */
+	int l2_plen = sizeof(*si4) - 1;
+
+	memset(si4, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si4->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si4->header.skip_indicator = 0;
+	si4->header.system_information = GSM48_MT_RR_SYSINFO_4;
+
+	gsm48_generate_lai(&si4->lai, bts->network->country_code,
+			   bts->network->network_code,
+			   bts->location_area_code);
+	si4->cell_sel_par = bts->si_common.cell_sel_par;
+	si4->rach_control = bts->si_common.rach_control;
+
+	/* Optional: CBCH Channel Description + CBCH Mobile Allocation */
+
+	si4->header.l2_plen = (l2_plen << 2) | 1;
+
+	/* SI4 Rest Octets (10.5.2.35), containing
+		Optional Power offset, GPRS Indicator,
+		Cell Identity, LSA ID, Selection Parameter */
+	rc = rest_octets_si4(si4->data, &si_info);
+
+	return sizeof(*si4) + rc;
+}
+
+static int generate_si5(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_5 *si5;
+	int rc, l2_plen = 18;
+
+	memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	/* ip.access nanoBTS needs l2_plen!! */
+	if (is_ipaccess_bts(bts)) {
+		*output++ = (l2_plen << 2) | 1;
+		l2_plen++;
+	}
+
+	si5 = (struct gsm48_system_information_type_5 *) output;
+
+	/* l2 pseudo length, not part of msg: 18 */
+	si5->rr_protocol_discriminator = GSM48_PDISC_RR;
+	si5->skip_indicator = 0;
+	si5->system_information = GSM48_MT_RR_SYSINFO_5;
+	rc = generate_bcch_chan_list(si5->bcch_frequency_list, bts);
+	if (rc < 0)
+		return rc;
+
+	/* 04.08 9.1.37: L2 Pseudo Length of 18 */
+	return l2_plen;
+}
+
+static int generate_si6(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_6 *si6;
+	int l2_plen = 11;
+
+	memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	/* ip.access nanoBTS needs l2_plen!! */
+	if (is_ipaccess_bts(bts)) {
+		*output++ = (l2_plen << 2) | 1;
+		l2_plen++;
+	}
+
+	si6 = (struct gsm48_system_information_type_6 *) output;
+
+	/* l2 pseudo length, not part of msg: 11 */
+	si6->rr_protocol_discriminator = GSM48_PDISC_RR;
+	si6->skip_indicator = 0;
+	si6->system_information = GSM48_MT_RR_SYSINFO_6;
+	si6->cell_identity = htons(bts->cell_identity);
+	gsm48_generate_lai(&si6->lai, bts->network->country_code,
+			   bts->network->network_code,
+			   bts->location_area_code);
+	si6->cell_options = bts->si_common.cell_options;
+	si6->ncc_permitted = bts->si_common.ncc_permitted;
+
+	/* SI6 Rest Octets: 10.5.2.35a: PCH / NCH info, VBS/VGCS options */
+
+	return l2_plen;
+}
+
+static struct gsm48_si13_info si13_default = {
+	.cell_opts = {
+		.nmo 		= GPRS_NMO_III,
+		.t3168		= 1500,
+		.t3192		= 500,
+		.drx_timer_max	= 3,
+		.bs_cv_max	= 15,
+		.ext_info_present = 0,
+		.ext_info = {
+			/* The values below are just guesses ! */
+			.egprs_supported = 0,
+			.use_egprs_p_ch_req = 1,
+			.bep_period = 4,
+			.pfc_supported = 0,
+			.dtm_supported = 0,
+			.bss_paging_coordination = 0,
+		},
+	},
+	.pwr_ctrl_pars = {
+		.alpha		= 10,	/* a = 1.0 */
+		.t_avg_w	= 25,
+		.t_avg_t	= 25,
+		.pc_meas_chan	= 0, 	/* downling measured on CCCH */
+		.n_avg_i	= 15,
+	},
+	.bcch_change_mark	= 1,
+	.si_change_field	= 0,
+	.pbcch_present		= 0,
+	{
+		.no_pbcch = {
+			.rac		= 0,	/* needs to be patched */
+			.spgc_ccch_sup 	= 0,
+			.net_ctrl_ord	= 0,
+			.prio_acc_thr	= 6,
+		},
+	},
+};
+
+static int generate_si13(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_13 *si13 =
+		(struct gsm48_system_information_type_13 *) output;
+	int ret;
+
+	memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si13->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si13->header.skip_indicator = 0;
+	si13->header.system_information = GSM48_MT_RR_SYSINFO_13;
+
+	si13_default.no_pbcch.rac = bts->gprs.rac;
+
+	ret = rest_octets_si13(si13->rest_octets, &si13_default);
+	if (ret < 0)
+		return ret;
+
+	si13->header.l2_plen = ret & 0xff;
+
+	return sizeof (*si13) + ret;
+}
+
+int gsm_generate_si(u_int8_t *output, struct gsm_bts *bts, int type)
+{
+	switch (bts->gprs.mode) {
+	case BTS_GPRS_EGPRS:
+		si13_default.cell_opts.ext_info_present = 1;
+		si13_default.cell_opts.ext_info.egprs_supported = 1;
+		/* fallthrough */
+	case BTS_GPRS_GPRS:
+		si_info.gprs_ind.present = 1;
+		break;
+	case BTS_GPRS_NONE:
+		si_info.gprs_ind.present = 0;
+		break;
+	}
+
+	switch (type) {
+	case RSL_SYSTEM_INFO_1:
+		return generate_si1(output, bts);
+	case RSL_SYSTEM_INFO_2:
+		return generate_si2(output, bts);
+	case RSL_SYSTEM_INFO_3:
+		return generate_si3(output, bts);
+	case RSL_SYSTEM_INFO_4:
+		return generate_si4(output, bts);
+	case RSL_SYSTEM_INFO_5:
+		return generate_si5(output, bts);
+	case RSL_SYSTEM_INFO_6:
+		return generate_si6(output, bts);
+	case RSL_SYSTEM_INFO_13:
+		return generate_si13(output, bts);
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/talloc_ctx.c b/openbsc/src/talloc_ctx.c
new file mode 100644
index 0000000..4b373b4
--- /dev/null
+++ b/openbsc/src/talloc_ctx.c
@@ -0,0 +1,36 @@
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+
+extern void *tall_msgb_ctx;
+extern void *tall_fle_ctx;
+extern void *tall_locop_ctx;
+extern void *tall_gsms_ctx;
+extern void *tall_subscr_ctx;
+extern void *tall_sub_req_ctx;
+extern void *tall_call_ctx;
+extern void *tall_paging_ctx;
+extern void *tall_sigh_ctx;
+extern void *tall_tqe_ctx;
+extern void *tall_trans_ctx;
+extern void *tall_map_ctx;
+extern void *tall_upq_ctx;
+extern void *tall_ctr_ctx;
+
+void talloc_ctx_init(void)
+{
+	tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb");
+	tall_fle_ctx = talloc_named_const(tall_bsc_ctx, 0,
+					  "bs11_file_list_entry");
+	tall_locop_ctx = talloc_named_const(tall_bsc_ctx, 0, "loc_updating_oper");
+	tall_gsms_ctx = talloc_named_const(tall_bsc_ctx, 0, "sms");
+	tall_subscr_ctx = talloc_named_const(tall_bsc_ctx, 0, "subscriber");
+	tall_sub_req_ctx = talloc_named_const(tall_bsc_ctx, 0, "subscr_request");
+	tall_call_ctx = talloc_named_const(tall_bsc_ctx, 0, "gsm_call");
+	tall_paging_ctx = talloc_named_const(tall_bsc_ctx, 0, "paging_request");
+	tall_sigh_ctx = talloc_named_const(tall_bsc_ctx, 0, "signal_handler");
+	tall_tqe_ctx = talloc_named_const(tall_bsc_ctx, 0, "subch_txq_entry");
+	tall_trans_ctx = talloc_named_const(tall_bsc_ctx, 0, "transaction");
+	tall_map_ctx = talloc_named_const(tall_bsc_ctx, 0, "trau_map_entry");
+	tall_upq_ctx = talloc_named_const(tall_bsc_ctx, 0, "trau_upq_entry");
+	tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter");
+}
diff --git a/openbsc/src/telnet_interface.c b/openbsc/src/telnet_interface.c
new file mode 100644
index 0000000..c7de026
--- /dev/null
+++ b/openbsc/src/telnet_interface.c
@@ -0,0 +1,209 @@
+/* minimalistic telnet/network interface it might turn into a wire interface */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openbsc/telnet_interface.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_11.h>
+#include <osmocore/msgb.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+
+#include <vty/buffer.h>
+
+#define WRITE_CONNECTION(fd, msg...) \
+	int ret; \
+	char buf[4096]; \
+	snprintf(buf, sizeof(buf), msg); \
+	ret = write(fd, buf, strlen(buf));
+
+
+/* per connection data */
+LLIST_HEAD(active_connections);
+
+static void *tall_telnet_ctx;
+
+/* per network data */
+static int telnet_new_connection(struct bsc_fd *fd, unsigned int what);
+
+static struct bsc_fd server_socket = {
+	.when	    = BSC_FD_READ,
+	.cb	    = telnet_new_connection,
+	.priv_nr    = 0,
+};
+
+void telnet_init(struct gsm_network *network, int port) {
+	struct sockaddr_in sock_addr;
+	int fd, on = 1;
+
+	tall_telnet_ctx = talloc_named_const(tall_bsc_ctx, 1,
+					     "telnet_connection");
+
+	bsc_vty_init(network);
+
+	fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+	if (fd < 0) {
+		LOGP(DNM, LOGL_ERROR, "Telnet interface socket creation failed\n");
+		return;
+	}
+
+	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	memset(&sock_addr, 0, sizeof(sock_addr));
+	sock_addr.sin_family = AF_INET;
+	sock_addr.sin_port = htons(port);
+	sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+	if (bind(fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) < 0) {
+		LOGP(DNM, LOGL_ERROR, "Telnet interface failed to bind\n");
+		return;
+	}
+
+	if (listen(fd, 0) < 0) {
+		LOGP(DNM, LOGL_ERROR, "Telnet interface failed to listen\n");
+		return;
+	}
+
+	server_socket.data = network;
+	server_socket.fd = fd;
+	bsc_register_fd(&server_socket);
+}
+
+extern const char *openbsc_copyright;
+extern const char *openbsc_version;
+
+static void print_welcome(int fd) {
+	int ret;
+	static char *msg =
+		"Welcome to the OpenBSC Control interface\n";
+
+	ret = write(fd, msg, strlen(msg));
+	ret = write(fd, openbsc_copyright, strlen(openbsc_copyright));
+}
+
+int telnet_close_client(struct bsc_fd *fd) {
+	struct telnet_connection *conn = (struct telnet_connection*)fd->data;
+
+	close(fd->fd);
+	bsc_unregister_fd(fd);
+
+	if (conn->dbg) {
+		log_del_target(conn->dbg);
+		talloc_free(conn->dbg);
+	}
+
+	llist_del(&conn->entry);
+	talloc_free(conn);
+	return 0;
+}
+
+static int client_data(struct bsc_fd *fd, unsigned int what)
+{
+	struct telnet_connection *conn = fd->data;
+	int rc = 0;
+
+	if (what & BSC_FD_READ) {
+		conn->fd.when &= ~BSC_FD_READ;
+		rc = vty_read(conn->vty);
+	}
+
+	/* vty might have been closed from vithin vty_read() */
+	if (!conn->vty)
+		return rc;
+
+	if (what & BSC_FD_WRITE) {
+		rc = buffer_flush_all(conn->vty->obuf, fd->fd);
+		if (rc == BUFFER_EMPTY)
+			conn->fd.when &= ~BSC_FD_WRITE;
+	}
+
+	return rc;
+}
+
+static int telnet_new_connection(struct bsc_fd *fd, unsigned int what) {
+	struct telnet_connection *connection;
+	struct sockaddr_in sockaddr;
+	socklen_t len = sizeof(sockaddr);
+	int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len);
+
+	if (new_connection < 0) {
+		LOGP(DNM, LOGL_ERROR, "telnet accept failed\n");
+		return -1;
+	}
+
+
+	connection = talloc_zero(tall_telnet_ctx, struct telnet_connection);
+	connection->network = (struct gsm_network*)fd->data;
+	connection->fd.data = connection;
+	connection->fd.fd = new_connection;
+	connection->fd.when = BSC_FD_READ;
+	connection->fd.cb = client_data;
+	bsc_register_fd(&connection->fd);
+	llist_add_tail(&connection->entry, &active_connections);
+
+	print_welcome(new_connection);
+
+	connection->vty = vty_create(new_connection, connection);
+	if (!connection->vty) {
+		LOGP(DNM, LOGL_ERROR, "couldn't create VTY\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* callback from VTY code */
+void vty_event(enum event event, int sock, struct vty *vty)
+{
+	struct telnet_connection *connection = vty->priv;
+	struct bsc_fd *bfd = &connection->fd;
+
+	if (vty->type != VTY_TERM)
+		return;
+
+	switch (event) {
+	case VTY_READ:
+		bfd->when |= BSC_FD_READ;
+		break;
+	case VTY_WRITE:
+		bfd->when |= BSC_FD_WRITE;
+		break;
+	case VTY_CLOSED:
+		/* vty layer is about to free() vty */
+		connection->vty = NULL;
+		telnet_close_client(bfd);
+		break;
+	default:
+		break;
+	}
+}
+
diff --git a/openbsc/src/token_auth.c b/openbsc/src/token_auth.c
new file mode 100644
index 0000000..7fefea5
--- /dev/null
+++ b/openbsc/src/token_auth.c
@@ -0,0 +1,156 @@
+/* SMS based token authentication for ad-hoc GSM networks */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+
+#define TOKEN_SMS_TEXT "HAR 2009 GSM.  Register at http://har2009.gnumonks.org/ " \
+			"Your IMSI is %s, auth token is %08X, phone no is %s."
+
+extern struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver,
+				     const char *text);
+
+static char *build_sms_string(struct gsm_subscriber *subscr, u_int32_t token)
+{
+	char *sms_str;
+	unsigned int len;
+
+	len = strlen(subscr->imsi) + 8 + strlen(TOKEN_SMS_TEXT);
+	sms_str = talloc_size(tall_bsc_ctx, len);
+	if (!sms_str)
+		return NULL;
+
+	snprintf(sms_str, len, TOKEN_SMS_TEXT, subscr->imsi, token,
+		 subscr->extension);
+	sms_str[len-1] = '\0';
+
+	return sms_str;
+}
+
+static int token_subscr_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr = signal_data;
+	struct gsm_sms *sms;
+	int rc = 0;
+
+	if (signal != S_SUBSCR_ATTACHED)
+		return 0;
+
+	if (subscr->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+		return 0;
+
+	if (subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT) {
+		u_int32_t token;
+		char *sms_str;
+
+		/* we've seen this subscriber for the first time. */
+		rc = db_subscriber_alloc_token(subscr, &token);
+		if (rc != 0) {
+			rc = -EIO;
+			goto unauth;
+		}
+
+		sms_str = build_sms_string(subscr, token);
+		if (!sms_str) {
+			rc = -ENOMEM;
+			goto unauth;
+		}
+
+		sms = sms_from_text(subscr, sms_str);
+		talloc_free(sms_str);
+		if (!sms) {
+			rc = -ENOMEM;
+			goto unauth;
+		}
+
+		rc = gsm411_send_sms_subscr(subscr, sms);
+
+		/* FIXME: else, delete the subscirber from database */
+unauth:
+
+		/* make sure we don't allow him in again unless he clicks the web UI */
+		subscr->authorized = 0;
+		db_sync_subscriber(subscr);
+		if (rc) {
+			struct gsm_lchan *lchan = lchan_for_subscr(subscr);
+			if (lchan) {
+				u_int8_t auth_rand[16];
+				/* kick the subscriber off the network */
+				gsm48_tx_mm_auth_req(lchan, auth_rand, 0);
+				gsm48_tx_mm_auth_rej(lchan);
+				/* FIXME: close the channel early ?*/
+				//gsm48_send_rr_Release(lchan);
+			}
+		}
+	}
+
+	return rc;
+}
+
+static int token_sms_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct gsm_sms *sms = signal_data;
+	struct gsm_lchan *lchan;
+	u_int8_t auth_rand[16];
+
+
+	if (signal != S_SMS_DELIVERED)
+		return 0;
+
+
+	/* these are not the droids we've been looking for */
+	if (!sms->receiver ||
+	    !(sms->receiver->flags & GSM_SUBSCRIBER_FIRST_CONTACT))
+		return 0;
+
+
+	if (sms->receiver->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+		return 0;
+
+
+	lchan = lchan_for_subscr(sms->receiver);
+	if (lchan) {
+		/* kick the subscriber off the network */
+		gsm48_tx_mm_auth_req(lchan, auth_rand, 0);
+		gsm48_tx_mm_auth_rej(lchan);
+		/* FIXME: close the channel early ?*/
+		//gsm48_send_rr_Release(lchan);
+	}
+
+	return 0;
+}
+
+//static __attribute__((constructor)) void on_dso_load_token(void)
+void on_dso_load_token(void)
+{
+	register_signal_handler(SS_SUBSCR, token_subscr_cb, NULL);
+	register_signal_handler(SS_SMS, token_sms_cb, NULL);
+}
diff --git a/openbsc/src/transaction.c b/openbsc/src/transaction.c
new file mode 100644
index 0000000..5e0d507
--- /dev/null
+++ b/openbsc/src/transaction.c
@@ -0,0 +1,172 @@
+/* GSM 04.07 Transaction handling */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/transaction.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mncc.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/mncc.h>
+#include <openbsc/paging.h>
+
+void *tall_trans_ctx;
+
+void _gsm48_cc_trans_free(struct gsm_trans *trans);
+
+struct gsm_trans *trans_find_by_id(struct gsm_subscriber *subscr,
+				   u_int8_t proto, u_int8_t trans_id)
+{
+	struct gsm_trans *trans;
+	struct gsm_network *net = subscr->net;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->subscr == subscr &&
+		    trans->protocol == proto &&
+		    trans->transaction_id == trans_id)
+			return trans;
+	}
+	return NULL;
+}
+
+struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
+					u_int32_t callref)
+{
+	struct gsm_trans *trans;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->callref == callref)
+			return trans;
+	}
+	return NULL;
+}
+
+struct gsm_trans *trans_alloc(struct gsm_subscriber *subscr,
+			      u_int8_t protocol, u_int8_t trans_id,
+			      u_int32_t callref)
+{
+	struct gsm_trans *trans;
+
+	DEBUGP(DCC, "subscr=%p, subscr->net=%p\n", subscr, subscr->net);
+
+	trans = talloc_zero(tall_trans_ctx, struct gsm_trans);
+	if (!trans)
+		return NULL;
+
+	trans->subscr = subscr;
+	subscr_get(trans->subscr);
+
+	trans->protocol = protocol;
+	trans->transaction_id = trans_id;
+	trans->callref = callref;
+
+	llist_add_tail(&trans->entry, &subscr->net->trans_list);
+
+	return trans;
+}
+
+void trans_free(struct gsm_trans *trans)
+{
+	switch (trans->protocol) {
+	case GSM48_PDISC_CC:
+		_gsm48_cc_trans_free(trans);
+		break;
+	case GSM48_PDISC_SMS:
+		_gsm411_sms_trans_free(trans);
+		break;
+	}
+
+	if (trans->conn)
+		put_subscr_con(trans->conn);
+
+	if (!trans->conn && trans->subscr && trans->subscr->net) {
+		/* Stop paging on all bts' */
+		paging_request_stop(NULL, trans->subscr, NULL);
+	}
+
+	if (trans->subscr)
+		subscr_put(trans->subscr);
+
+	llist_del(&trans->entry);
+
+	talloc_free(trans);
+}
+
+/* allocate an unused transaction ID for the given subscriber
+ * in the given protocol using the ti_flag specified */
+int trans_assign_trans_id(struct gsm_subscriber *subscr,
+			  u_int8_t protocol, u_int8_t ti_flag)
+{
+	struct gsm_network *net = subscr->net;
+	struct gsm_trans *trans;
+	unsigned int used_tid_bitmask = 0;
+	int i, j, h;
+
+	if (ti_flag)
+		ti_flag = 0x8;
+
+	/* generate bitmask of already-used TIDs for this (subscr,proto) */
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->subscr != subscr ||
+		    trans->protocol != protocol ||
+		    trans->transaction_id == 0xff)
+			continue;
+		used_tid_bitmask |= (1 << trans->transaction_id);
+	}
+
+	/* find a new one, trying to go in a 'circular' pattern */
+	for (h = 6; h > 0; h--)
+		if (used_tid_bitmask & (1 << (h | ti_flag)))
+			break;
+	for (i = 0; i < 7; i++) {
+		j = ((h + i) % 7) | ti_flag;
+		if ((used_tid_bitmask & (1 << j)) == 0)
+			return j;
+	}
+
+	return -1;
+}
+
+/* update all transactions to use a different LCHAN, e.g.
+ * after handover has succeeded */
+int trans_lchan_change(struct gsm_subscriber_connection *conn_old,
+		       struct gsm_subscriber_connection *conn_new)
+{
+	struct gsm_network *net = conn_old->lchan->ts->trx->bts->network;
+	struct gsm_trans *trans;
+	int num = 0;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->conn == conn_old) {
+
+			/* drop old channel use count */
+			put_subscr_con(conn_old);
+			/* assign new channel */
+			trans->conn = conn_new;
+			/* bump new channel use count */
+			use_subscr_con(conn_new);
+			num++;
+		}
+	}
+
+	return num;
+}
diff --git a/openbsc/src/trau_frame.c b/openbsc/src/trau_frame.c
new file mode 100644
index 0000000..2bc61a5
--- /dev/null
+++ b/openbsc/src/trau_frame.c
@@ -0,0 +1,261 @@
+/* TRAU frame handling according to GSM TS 08.60 */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/trau_frame.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/debug.h>
+
+static u_int32_t get_bits(const u_int8_t *bitbuf, int offset, int num)
+{
+	int i;
+	u_int32_t ret = 0;
+
+	for (i = offset; i < offset + num; i++) {
+		ret = ret << 1;
+		if (bitbuf[i])
+			ret |= 1;
+	}
+	return ret;
+}
+
+/* Decode according to 3.1.1 */
+static void decode_fr(struct decoded_trau_frame *fr, const u_int8_t *trau_bits)
+{
+	int i;
+	int d_idx = 0;
+
+	/* C1 .. C15 */
+	memcpy(fr->c_bits+0, trau_bits+17, 15);
+	/* C16 .. C21 */
+	memcpy(fr->c_bits+15, trau_bits+310, 6);
+	/* T1 .. T4 */
+	memcpy(fr->t_bits+0, trau_bits+316, 4);
+	/* D1 .. D255 */
+	for (i = 32; i < 304; i+= 16) {
+		memcpy(fr->d_bits + d_idx, trau_bits+i+1, 15);
+		d_idx += 15;
+	}
+	/* D256 .. D260 */
+	memcpy(fr->d_bits + d_idx, trau_bits + 305, 5);
+}
+
+/* Decode according to 3.1.2 */
+static void decode_amr(struct decoded_trau_frame *fr, const u_int8_t *trau_bits)
+{
+	int i;
+	int d_idx = 0;
+
+	/* C1 .. C15 */
+	memcpy(fr->c_bits+0, trau_bits+17, 15);
+	/* C16 .. C25 */
+	memcpy(fr->c_bits+15, trau_bits+33, 10);
+	/* T1 .. T4 */
+	memcpy(fr->t_bits+0, trau_bits+316, 4);
+	/* D1 .. D5 */
+	memcpy(fr->d_bits, trau_bits+43, 5);
+	/* D6 .. D245 */
+	for (i = 48; i < 304; i += 16) {
+		memcpy(fr->d_bits + d_idx, trau_bits+i+1, 15);
+		d_idx += 15;
+	}
+	/* D246 .. D256 */
+	memcpy(fr->d_bits + d_idx, trau_bits + 305, 11);
+}
+
+int decode_trau_frame(struct decoded_trau_frame *fr, const u_int8_t *trau_bits)
+{
+	u_int8_t cbits5 = get_bits(trau_bits, 17, 5);
+
+	switch (cbits5) {
+	case TRAU_FT_FR_UP:
+	case TRAU_FT_FR_DOWN:
+	case TRAU_FT_IDLE_UP:
+	case TRAU_FT_IDLE_DOWN:
+	case TRAU_FT_EFR:
+		decode_fr(fr, trau_bits);
+		break;
+	case TRAU_FT_AMR:
+		decode_amr(fr, trau_bits);
+		break;
+	case TRAU_FT_OM_UP:
+	case TRAU_FT_OM_DOWN:
+	case TRAU_FT_DATA_UP:
+	case TRAU_FT_DATA_DOWN:
+	case TRAU_FT_D145_SYNC:
+	case TRAU_FT_EDATA:
+		LOGP(DMUX, LOGL_NOTICE, "can't decode unimplemented TRAU "
+			"Frame Type 0x%02x\n", cbits5);
+		return -1;
+		break;
+	default:
+		LOGP(DMUX, LOGL_NOTICE, "can't decode unknown TRAU "
+			"Frame Type 0x%02x\n", cbits5);
+		return -1;
+		break;
+	}
+
+	return 0;
+}
+
+const u_int8_t ft_fr_down_bits[] = { 1, 1, 1, 0, 0 };
+const u_int8_t ft_idle_down_bits[] = { 0, 1, 1, 1, 0 };
+
+/* modify an uplink TRAU frame so we can send it downlink */
+int trau_frame_up2down(struct decoded_trau_frame *fr)
+{
+	u_int8_t cbits5 = get_bits(fr->c_bits, 0, 5);
+
+	switch (cbits5) {
+	case TRAU_FT_FR_UP:
+		memcpy(fr->c_bits, ft_fr_down_bits, 5);
+		/* clear time alignment */
+		memset(fr->c_bits+5, 0, 6);
+		/* FIXME: SP / BFI in case of DTx */
+		/* C12 .. C21 are spare and coded as '1' */
+		memset(fr->c_bits+11, 0x01, 10);
+		break;
+	case TRAU_FT_EFR:
+		/* clear time alignment */
+		memset(fr->c_bits+5, 0, 6);
+		/* FIXME: set UFE appropriately */
+		/* FIXME: SP / BFI in case of DTx */
+		break;
+	case TRAU_FT_IDLE_UP:
+		memcpy(fr->c_bits, ft_idle_down_bits, 5);
+		/* clear time alignment */
+		memset(fr->c_bits+5, 0, 6);
+		/* FIXME: SP / BFI in case of DTx */
+		/* C12 .. C21 are spare and coded as '1' */
+		memset(fr->c_bits+11, 0x01, 10);
+		break;
+	case TRAU_FT_FR_DOWN:
+	case TRAU_FT_IDLE_DOWN:
+	case TRAU_FT_OM_DOWN:
+	case TRAU_FT_DATA_DOWN:
+		/* we cannot convert a downlink to a downlink frame */
+		return -EINVAL;
+		break;
+	case TRAU_FT_AMR:
+	case TRAU_FT_OM_UP:
+	case TRAU_FT_DATA_UP:
+	case TRAU_FT_D145_SYNC:
+	case TRAU_FT_EDATA:
+		LOGP(DMUX, LOGL_NOTICE, "unimplemented TRAU Frame Type "
+			"0x%02x\n", cbits5);
+		return -1;
+		break;
+	default:
+		LOGP(DMUX, LOGL_NOTICE, "unknown TRAU Frame Type "
+			"0x%02x\n", cbits5);
+		return -1;
+		break;
+	}
+
+	return 0;
+
+}
+
+static void encode_fr(u_int8_t *trau_bits, const struct decoded_trau_frame *fr)
+{
+	int i;
+	int d_idx = 0;
+
+	trau_bits[16] = 1;
+	/* C1 .. C15 */
+	memcpy(trau_bits+17, fr->c_bits+0, 15);
+	/* D1 .. D255 */
+	for (i = 32; i < 304; i+= 16) {
+		trau_bits[i] = 1;
+		memcpy(trau_bits+i+1, fr->d_bits + d_idx, 15);
+		d_idx += 15;
+	}
+	/* D256 .. D260 */
+	trau_bits[304] = 1;
+	memcpy(trau_bits + 305, fr->d_bits + d_idx, 5);
+	/* C16 .. C21 */
+	memcpy(trau_bits+310, fr->c_bits+15, 6);
+
+	/* FIXME: handle timing adjustment */
+
+	/* T1 .. T4 */
+	memcpy(trau_bits+316, fr->t_bits+0, 4);
+}
+
+
+int encode_trau_frame(u_int8_t *trau_bits, const struct decoded_trau_frame *fr)
+{
+	u_int8_t cbits5 = get_bits(fr->c_bits, 0, 5);
+	
+	/* 16 bits of sync header */
+	memset(trau_bits, 0, 16);
+
+	switch (cbits5) {
+	case TRAU_FT_FR_UP:
+	case TRAU_FT_FR_DOWN:
+	case TRAU_FT_IDLE_UP:
+	case TRAU_FT_IDLE_DOWN:
+	case TRAU_FT_EFR:
+		encode_fr(trau_bits, fr);
+		break;
+	case TRAU_FT_AMR:
+	case TRAU_FT_OM_UP:
+	case TRAU_FT_OM_DOWN:
+	case TRAU_FT_DATA_UP:
+	case TRAU_FT_DATA_DOWN:
+	case TRAU_FT_D145_SYNC:
+	case TRAU_FT_EDATA:
+		LOGP(DMUX, LOGL_NOTICE, "unimplemented TRAU Frame Type "
+			"0x%02x\n", cbits5);
+		return -1;
+		break;
+	default:
+		LOGP(DMUX, LOGL_NOTICE, "unknown TRAU Frame Type "
+			"0x%02x\n", cbits5);
+		return -1;
+		break;
+	}
+
+	return 0;
+}
+
+static struct decoded_trau_frame fr_idle_frame = {
+	.c_bits = { 0, 1, 1, 1, 0 },	/* IDLE DOWNLINK 3.5.5 */
+	.t_bits = { 1, 1, 1, 1 },
+};
+static u_int8_t encoded_idle_frame[TRAU_FRAME_BITS];
+static int dbits_initted;
+
+u_int8_t *trau_idle_frame(void)
+{
+	/* only initialize during the first call */
+	if (!dbits_initted) {
+		/* set all D-bits to 1 */
+		memset(&fr_idle_frame.d_bits, 0x01, 260);
+		encode_fr(encoded_idle_frame, &fr_idle_frame);
+	}
+	return encoded_idle_frame;
+}
diff --git a/openbsc/src/trau_mux.c b/openbsc/src/trau_mux.c
new file mode 100644
index 0000000..f2fa5c0
--- /dev/null
+++ b/openbsc/src/trau_mux.c
@@ -0,0 +1,313 @@
+/* Simple TRAU frame reflector to route voice calls */
+
+/* (C) 2009 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+
+u_int8_t gsm_fr_map[] = {
+	6, 6, 5, 5, 4, 4, 3, 3,
+	7, 2, 2, 6, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 7, 2, 2, 6, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 7, 2, 2, 6, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 7, 2, 2, 6, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3
+};
+
+struct map_entry {
+	struct llist_head list;
+	struct gsm_e1_subslot src, dst;
+};
+
+struct upqueue_entry {
+	struct llist_head list;
+	struct gsm_network *net;
+	struct gsm_e1_subslot src;
+	u_int32_t callref;
+};
+
+static LLIST_HEAD(ss_map);
+static LLIST_HEAD(ss_upqueue);
+
+void *tall_map_ctx, *tall_upq_ctx;
+
+/* map one particular subslot to another subslot */
+int trau_mux_map(const struct gsm_e1_subslot *src,
+		 const struct gsm_e1_subslot *dst)
+{
+	struct map_entry *me;
+
+	me = talloc(tall_map_ctx, struct map_entry);
+	if (!me) {
+		LOGP(DMIB, LOGL_FATAL, "Out of memory\n");
+		return -ENOMEM;
+	}
+
+	DEBUGP(DCC, "Setting up TRAU mux map between (e1=%u,ts=%u,ss=%u) "
+		"and (e1=%u,ts=%u,ss=%u)\n",
+		src->e1_nr, src->e1_ts, src->e1_ts_ss,
+		dst->e1_nr, dst->e1_ts, dst->e1_ts_ss);
+
+	/* make sure to get rid of any stale old mappings */
+	trau_mux_unmap(src, 0);
+	trau_mux_unmap(dst, 0);
+
+	memcpy(&me->src, src, sizeof(me->src));
+	memcpy(&me->dst, dst, sizeof(me->dst));
+	llist_add(&me->list, &ss_map);
+
+	return 0;
+}
+
+int trau_mux_map_lchan(const struct gsm_lchan *src,	
+			const struct gsm_lchan *dst)
+{
+	struct gsm_e1_subslot *src_ss, *dst_ss;
+
+	src_ss = &src->ts->e1_link;
+	dst_ss = &dst->ts->e1_link;
+
+	return trau_mux_map(src_ss, dst_ss);
+}
+
+
+/* unmap one particular subslot from another subslot */
+int trau_mux_unmap(const struct gsm_e1_subslot *ss, u_int32_t callref)
+{
+	struct map_entry *me, *me2;
+	struct upqueue_entry *ue, *ue2;
+
+	if (ss)
+		llist_for_each_entry_safe(me, me2, &ss_map, list) {
+			if (!memcmp(&me->src, ss, sizeof(*ss)) ||
+			    !memcmp(&me->dst, ss, sizeof(*ss))) {
+				llist_del(&me->list);
+				return 0;
+			}
+		}
+	llist_for_each_entry_safe(ue, ue2, &ss_upqueue, list) {
+		if (ue->callref == callref) {
+			llist_del(&ue->list);
+			return 0;
+		}
+		if (ss && !memcmp(&ue->src, ss, sizeof(*ss))) {
+			llist_del(&ue->list);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+/* look-up an enty in the TRAU mux map */
+static struct gsm_e1_subslot *
+lookup_trau_mux_map(const struct gsm_e1_subslot *src)
+{
+	struct map_entry *me;
+
+	llist_for_each_entry(me, &ss_map, list) {
+		if (!memcmp(&me->src, src, sizeof(*src)))
+			return &me->dst;
+		if (!memcmp(&me->dst, src, sizeof(*src)))
+			return &me->src;
+	}
+	return NULL;
+}
+
+/* look-up an enty in the TRAU upqueue */
+struct upqueue_entry *
+lookup_trau_upqueue(const struct gsm_e1_subslot *src)
+{
+	struct upqueue_entry *ue;
+
+	llist_for_each_entry(ue, &ss_upqueue, list) {
+		if (!memcmp(&ue->src, src, sizeof(*src)))
+			return ue;
+	}
+	return NULL;
+}
+
+static const u_int8_t c_bits_check[] = { 0, 0, 0, 1, 0 };
+
+/* we get called by subchan_demux */
+int trau_mux_input(struct gsm_e1_subslot *src_e1_ss,
+		   const u_int8_t *trau_bits, int num_bits)
+{
+	struct decoded_trau_frame tf;
+	u_int8_t trau_bits_out[TRAU_FRAME_BITS];
+	struct gsm_e1_subslot *dst_e1_ss = lookup_trau_mux_map(src_e1_ss);
+	struct subch_mux *mx;
+	struct upqueue_entry *ue;
+	int rc;
+
+	/* decode TRAU, change it to downlink, re-encode */
+	rc = decode_trau_frame(&tf, trau_bits);
+	if (rc)
+		return rc;
+
+	if (!dst_e1_ss) {
+		struct msgb *msg;
+		struct gsm_data_frame *frame;
+		unsigned char *data;
+		int i, j, k, l, o;
+		/* frame shall be sent to upqueue */
+		if (!(ue = lookup_trau_upqueue(src_e1_ss)))
+			return -EINVAL;
+		if (!ue->callref)
+			return -EINVAL;
+		if (memcmp(tf.c_bits, c_bits_check, sizeof(c_bits_check)))
+			DEBUGPC(DMUX, "illegal trau (C1-C5) %s\n",
+				hexdump(tf.c_bits, sizeof(c_bits_check)));
+		msg = msgb_alloc(sizeof(struct gsm_data_frame) + 33,
+				 "GSM-DATA");
+		if (!msg)
+			return -ENOMEM;
+
+		frame = (struct gsm_data_frame *)msg->data;
+		memset(frame, 0, sizeof(struct gsm_data_frame));
+		data = frame->data;
+		data[0] = 0xd << 4;
+		/* reassemble d-bits */
+		i = 0; /* counts bits */
+		j = 4; /* counts output bits */
+		k = gsm_fr_map[0]-1; /* current number bit in element */
+		l = 0; /* counts element bits */
+		o = 0; /* offset input bits */
+		while (i < 260) {
+			data[j/8] |= (tf.d_bits[k+o] << (7-(j%8)));
+			if (--k < 0) {
+				o += gsm_fr_map[l];
+				k = gsm_fr_map[++l]-1;
+			}
+			i++;
+			j++;
+		}
+		frame->msg_type = GSM_TCHF_FRAME;
+		frame->callref = ue->callref;
+		msgb_enqueue(&ue->net->upqueue, msg);
+
+		return 0;
+	}
+
+	mx = e1inp_get_mux(dst_e1_ss->e1_nr, dst_e1_ss->e1_ts);
+	if (!mx)
+		return -EINVAL;
+
+	trau_frame_up2down(&tf);
+	encode_trau_frame(trau_bits_out, &tf);
+
+	/* and send it to the muxer */
+	return subchan_mux_enqueue(mx, dst_e1_ss->e1_ts_ss, trau_bits_out,
+				   TRAU_FRAME_BITS);
+}
+
+/* add receiver instance for lchan and callref */
+int trau_recv_lchan(struct gsm_lchan *lchan, u_int32_t callref)
+{
+	struct gsm_e1_subslot *src_ss;
+	struct upqueue_entry *ue;
+
+	ue = talloc(tall_upq_ctx, struct upqueue_entry);
+	if (!ue)
+		return -ENOMEM;
+
+	src_ss = &lchan->ts->e1_link;
+
+	DEBUGP(DCC, "Setting up TRAU receiver (e1=%u,ts=%u,ss=%u) "
+		"and (callref 0x%x)\n",
+		src_ss->e1_nr, src_ss->e1_ts, src_ss->e1_ts_ss,
+		callref);
+
+	/* make sure to get rid of any stale old mappings */
+	trau_mux_unmap(src_ss, callref);
+
+	memcpy(&ue->src, src_ss, sizeof(ue->src));
+	ue->net = lchan->ts->trx->bts->network;
+	ue->callref = callref;
+	llist_add(&ue->list, &ss_upqueue);
+
+	return 0;
+}
+
+int trau_send_frame(struct gsm_lchan *lchan, struct gsm_data_frame *frame)
+{
+	u_int8_t trau_bits_out[TRAU_FRAME_BITS];
+	struct gsm_e1_subslot *dst_e1_ss = &lchan->ts->e1_link;
+	struct subch_mux *mx;
+	int i, j, k, l, o;
+	unsigned char *data = frame->data;
+	struct decoded_trau_frame tf;
+
+	mx = e1inp_get_mux(dst_e1_ss->e1_nr, dst_e1_ss->e1_ts);
+	if (!mx)
+		return -EINVAL;
+
+	switch (frame->msg_type) {
+	case GSM_TCHF_FRAME:
+		/* set c-bits and t-bits */
+		tf.c_bits[0] = 1;
+		tf.c_bits[1] = 1;
+		tf.c_bits[2] = 1;
+		tf.c_bits[3] = 0;
+		tf.c_bits[4] = 0;
+		memset(&tf.c_bits[5], 0, 6);
+		memset(&tf.c_bits[11], 1, 10);
+		memset(&tf.t_bits[0], 1, 4);
+		/* reassemble d-bits */
+		i = 0; /* counts bits */
+		j = 4; /* counts input bits */
+		k = gsm_fr_map[0]-1; /* current number bit in element */
+		l = 0; /* counts element bits */
+		o = 0; /* offset output bits */
+		while (i < 260) {
+			tf.d_bits[k+o] = (data[j/8] >> (7-(j%8))) & 1;
+			if (--k < 0) {
+				o += gsm_fr_map[l];
+				k = gsm_fr_map[++l]-1;
+			}
+			i++;
+			j++;
+		}
+		break;
+	default:
+		DEBUGPC(DMUX, "unsupported message type %d\n",
+			frame->msg_type);
+		return -EINVAL;
+	}
+
+	encode_trau_frame(trau_bits_out, &tf);
+
+	/* and send it to the muxer */
+	return subchan_mux_enqueue(mx, dst_e1_ss->e1_ts_ss, trau_bits_out,
+				   TRAU_FRAME_BITS);
+}
diff --git a/openbsc/src/ussd.c b/openbsc/src/ussd.c
new file mode 100644
index 0000000..5476919
--- /dev/null
+++ b/openbsc/src/ussd.c
@@ -0,0 +1,71 @@
+/* Network-specific handling of mobile-originated USSDs. */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/* This module defines the network-specific handling of mobile-originated
+   USSD messages. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+/* Declarations of USSD strings to be recognised */
+const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
+
+/* Forward declarations of network-specific handler functions */
+static int send_own_number(const struct msgb *msg, const struct ussd_request *req);
+
+
+/* Entrypoint - handler function common to all mobile-originated USSDs */
+int handle_rcv_ussd(struct msgb *msg)
+{
+	struct ussd_request req;
+
+	gsm0480_decode_ussd_request(msg, &req);
+	if (req.text[0] == 0xFF)  /* Release-Complete */
+		return 0;
+
+	if (strstr(USSD_TEXT_OWN_NUMBER, req.text) != NULL) {
+		DEBUGP(DMM, "USSD: Own number requested\n");
+		return send_own_number(msg, &req);
+	} else {
+		DEBUGP(DMM, "Unhandled USSD %s\n", req.text);
+		return gsm0480_send_ussd_reject(msg, &req);
+	}
+}
+
+/* A network-specific handler function */
+static int send_own_number(const struct msgb *msg, const struct ussd_request *req)
+{
+	char *own_number = msg->lchan->conn.subscr->extension;
+	char response_string[GSM_EXTENSION_LENGTH + 20];
+
+	/* Need trailing CR as EOT character */
+	snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number);
+	return gsm0480_send_ussd_response(msg, response_string, req);
+}
diff --git a/openbsc/src/vty/buffer.c b/openbsc/src/vty/buffer.c
new file mode 100644
index 0000000..0bc1760
--- /dev/null
+++ b/openbsc/src/vty/buffer.c
@@ -0,0 +1,463 @@
+/*
+ * Buffering of output and input.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stddef.h>
+#include <sys/uio.h>
+
+#include <osmocore/talloc.h>
+#include <vty/buffer.h>
+#include <vty/vty.h>
+
+/* Buffer master. */
+struct buffer {
+	/* Data list. */
+	struct buffer_data *head;
+	struct buffer_data *tail;
+
+	/* Size of each buffer_data chunk. */
+	size_t size;
+};
+
+/* Data container. */
+struct buffer_data {
+	struct buffer_data *next;
+
+	/* Location to add new data. */
+	size_t cp;
+
+	/* Pointer to data not yet flushed. */
+	size_t sp;
+
+	/* Actual data stream (variable length). */
+	unsigned char data[0];	/* real dimension is buffer->size */
+};
+
+/* It should always be true that: 0 <= sp <= cp <= size */
+
+/* Default buffer size (used if none specified).  It is rounded up to the
+   next page boundery. */
+#define BUFFER_SIZE_DEFAULT		4096
+
+#define BUFFER_DATA_FREE(D) talloc_free((D))
+
+/* Make new buffer. */
+struct buffer *buffer_new(void *ctx, size_t size)
+{
+	struct buffer *b;
+
+	b = talloc_zero(ctx, struct buffer);
+
+	if (size)
+		b->size = size;
+	else {
+		static size_t default_size;
+		if (!default_size) {
+			long pgsz = sysconf(_SC_PAGESIZE);
+			default_size =
+			    ((((BUFFER_SIZE_DEFAULT - 1) / pgsz) + 1) * pgsz);
+		}
+		b->size = default_size;
+	}
+
+	return b;
+}
+
+/* Free buffer. */
+void buffer_free(struct buffer *b)
+{
+	buffer_reset(b);
+	talloc_free(b);
+}
+
+/* Make string clone. */
+char *buffer_getstr(struct buffer *b)
+{
+	size_t totlen = 0;
+	struct buffer_data *data;
+	char *s;
+	char *p;
+
+	for (data = b->head; data; data = data->next)
+		totlen += data->cp - data->sp;
+	if (!(s = _talloc_zero(tall_vty_ctx, (totlen + 1), "buffer_getstr")))
+		return NULL;
+	p = s;
+	for (data = b->head; data; data = data->next) {
+		memcpy(p, data->data + data->sp, data->cp - data->sp);
+		p += data->cp - data->sp;
+	}
+	*p = '\0';
+	return s;
+}
+
+/* Return 1 if buffer is empty. */
+int buffer_empty(struct buffer *b)
+{
+	return (b->head == NULL);
+}
+
+/* Clear and free all allocated data. */
+void buffer_reset(struct buffer *b)
+{
+	struct buffer_data *data;
+	struct buffer_data *next;
+
+	for (data = b->head; data; data = next) {
+		next = data->next;
+		BUFFER_DATA_FREE(data);
+	}
+	b->head = b->tail = NULL;
+}
+
+/* Add buffer_data to the end of buffer. */
+static struct buffer_data *buffer_add(struct buffer *b)
+{
+	struct buffer_data *d;
+
+	d = _talloc_zero(b,
+			 offsetof(struct buffer_data, data[b->size]),
+			 "buffer_add");
+	if (!d)
+		return NULL;
+	d->cp = d->sp = 0;
+	d->next = NULL;
+
+	if (b->tail)
+		b->tail->next = d;
+	else
+		b->head = d;
+	b->tail = d;
+
+	return d;
+}
+
+/* Write data to buffer. */
+void buffer_put(struct buffer *b, const void *p, size_t size)
+{
+	struct buffer_data *data = b->tail;
+	const char *ptr = p;
+
+	/* We use even last one byte of data buffer. */
+	while (size) {
+		size_t chunk;
+
+		/* If there is no data buffer add it. */
+		if (data == NULL || data->cp == b->size)
+			data = buffer_add(b);
+
+		chunk =
+		    ((size <=
+		      (b->size - data->cp)) ? size : (b->size - data->cp));
+		memcpy((data->data + data->cp), ptr, chunk);
+		size -= chunk;
+		ptr += chunk;
+		data->cp += chunk;
+	}
+}
+
+/* Insert character into the buffer. */
+void buffer_putc(struct buffer *b, u_char c)
+{
+	buffer_put(b, &c, 1);
+}
+
+/* Put string to the buffer. */
+void buffer_putstr(struct buffer *b, const char *c)
+{
+	buffer_put(b, c, strlen(c));
+}
+
+/* Keep flushing data to the fd until the buffer is empty or an error is
+   encountered or the operation would block. */
+buffer_status_t buffer_flush_all(struct buffer *b, int fd)
+{
+	buffer_status_t ret;
+	struct buffer_data *head;
+	size_t head_sp;
+
+	if (!b->head)
+		return BUFFER_EMPTY;
+	head_sp = (head = b->head)->sp;
+	/* Flush all data. */
+	while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING) {
+		if ((b->head == head) && (head_sp == head->sp)
+		    && (errno != EINTR))
+			/* No data was flushed, so kernel buffer must be full. */
+			return ret;
+		head_sp = (head = b->head)->sp;
+	}
+
+	return ret;
+}
+
+#if 0
+/* Flush enough data to fill a terminal window of the given scene (used only
+   by vty telnet interface). */
+buffer_status_t
+buffer_flush_window(struct buffer * b, int fd, int width, int height,
+		    int erase_flag, int no_more_flag)
+{
+	int nbytes;
+	int iov_alloc;
+	int iov_index;
+	struct iovec *iov;
+	struct iovec small_iov[3];
+	char more[] = " --More-- ";
+	char erase[] =
+	    { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+		' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+		0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08
+	};
+	struct buffer_data *data;
+	int column;
+
+	if (!b->head)
+		return BUFFER_EMPTY;
+
+	if (height < 1) {
+		zlog_warn
+		    ("%s called with non-positive window height %d, forcing to 1",
+		     __func__, height);
+		height = 1;
+	} else if (height >= 2)
+		height--;
+	if (width < 1) {
+		zlog_warn
+		    ("%s called with non-positive window width %d, forcing to 1",
+		     __func__, width);
+		width = 1;
+	}
+
+	/* For erase and more data add two to b's buffer_data count. */
+	if (b->head->next == NULL) {
+		iov_alloc = sizeof(small_iov) / sizeof(small_iov[0]);
+		iov = small_iov;
+	} else {
+		iov_alloc = ((height * (width + 2)) / b->size) + 10;
+		iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov));
+	}
+	iov_index = 0;
+
+	/* Previously print out is performed. */
+	if (erase_flag) {
+		iov[iov_index].iov_base = erase;
+		iov[iov_index].iov_len = sizeof erase;
+		iov_index++;
+	}
+
+	/* Output data. */
+	column = 1;		/* Column position of next character displayed. */
+	for (data = b->head; data && (height > 0); data = data->next) {
+		size_t cp;
+
+		cp = data->sp;
+		while ((cp < data->cp) && (height > 0)) {
+			/* Calculate lines remaining and column position after displaying
+			   this character. */
+			if (data->data[cp] == '\r')
+				column = 1;
+			else if ((data->data[cp] == '\n') || (column == width)) {
+				column = 1;
+				height--;
+			} else
+				column++;
+			cp++;
+		}
+		iov[iov_index].iov_base = (char *)(data->data + data->sp);
+		iov[iov_index++].iov_len = cp - data->sp;
+		data->sp = cp;
+
+		if (iov_index == iov_alloc)
+			/* This should not ordinarily happen. */
+		{
+			iov_alloc *= 2;
+			if (iov != small_iov) {
+				zlog_warn("%s: growing iov array to %d; "
+					  "width %d, height %d, size %lu",
+					  __func__, iov_alloc, width, height,
+					  (u_long) b->size);
+				iov =
+				    XREALLOC(MTYPE_TMP, iov,
+					     iov_alloc * sizeof(*iov));
+			} else {
+				/* This should absolutely never occur. */
+				zlog_err
+				    ("%s: corruption detected: iov_small overflowed; "
+				     "head %p, tail %p, head->next %p",
+				     __func__, b->head, b->tail, b->head->next);
+				iov =
+				    XMALLOC(MTYPE_TMP,
+					    iov_alloc * sizeof(*iov));
+				memcpy(iov, small_iov, sizeof(small_iov));
+			}
+		}
+	}
+
+	/* In case of `more' display need. */
+	if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) {
+		iov[iov_index].iov_base = more;
+		iov[iov_index].iov_len = sizeof more;
+		iov_index++;
+	}
+#ifdef IOV_MAX
+	/* IOV_MAX are normally defined in <sys/uio.h> , Posix.1g.
+	   example: Solaris2.6 are defined IOV_MAX size at 16.     */
+	{
+		struct iovec *c_iov = iov;
+		nbytes = 0;	/* Make sure it's initialized. */
+
+		while (iov_index > 0) {
+			int iov_size;
+
+			iov_size =
+			    ((iov_index > IOV_MAX) ? IOV_MAX : iov_index);
+			if ((nbytes = writev(fd, c_iov, iov_size)) < 0) {
+				zlog_warn("%s: writev to fd %d failed: %s",
+					  __func__, fd, safe_strerror(errno));
+				break;
+			}
+
+			/* move pointer io-vector */
+			c_iov += iov_size;
+			iov_index -= iov_size;
+		}
+	}
+#else				/* IOV_MAX */
+	if ((nbytes = writev(fd, iov, iov_index)) < 0)
+		zlog_warn("%s: writev to fd %d failed: %s",
+			  __func__, fd, safe_strerror(errno));
+#endif				/* IOV_MAX */
+
+	/* Free printed buffer data. */
+	while (b->head && (b->head->sp == b->head->cp)) {
+		struct buffer_data *del;
+		if (!(b->head = (del = b->head)->next))
+			b->tail = NULL;
+		BUFFER_DATA_FREE(del);
+	}
+
+	if (iov != small_iov)
+		XFREE(MTYPE_TMP, iov);
+
+	return (nbytes < 0) ? BUFFER_ERROR :
+	    (b->head ? BUFFER_PENDING : BUFFER_EMPTY);
+}
+#endif
+
+/* This function (unlike other buffer_flush* functions above) is designed
+to work with non-blocking sockets.  It does not attempt to write out
+all of the queued data, just a "big" chunk.  It returns 0 if it was
+able to empty out the buffers completely, 1 if more flushing is
+required later, or -1 on a fatal write error. */
+buffer_status_t buffer_flush_available(struct buffer * b, int fd)
+{
+
+/* These are just reasonable values to make sure a significant amount of
+data is written.  There's no need to go crazy and try to write it all
+in one shot. */
+#ifdef IOV_MAX
+#define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX)
+#else
+#define MAX_CHUNKS 16
+#endif
+#define MAX_FLUSH 131072
+
+	struct buffer_data *d;
+	size_t written;
+	struct iovec iov[MAX_CHUNKS];
+	size_t iovcnt = 0;
+	size_t nbyte = 0;
+
+	for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH);
+	     d = d->next, iovcnt++) {
+		iov[iovcnt].iov_base = d->data + d->sp;
+		nbyte += (iov[iovcnt].iov_len = d->cp - d->sp);
+	}
+
+	if (!nbyte)
+		/* No data to flush: should we issue a warning message? */
+		return BUFFER_EMPTY;
+
+	/* only place where written should be sign compared */
+	if ((ssize_t) (written = writev(fd, iov, iovcnt)) < 0) {
+		if (ERRNO_IO_RETRY(errno))
+			/* Calling code should try again later. */
+			return BUFFER_PENDING;
+		return BUFFER_ERROR;
+	}
+
+	/* Free printed buffer data. */
+	while (written > 0) {
+		struct buffer_data *d;
+		if (!(d = b->head))
+			break;
+		if (written < d->cp - d->sp) {
+			d->sp += written;
+			return BUFFER_PENDING;
+		}
+
+		written -= (d->cp - d->sp);
+		if (!(b->head = d->next))
+			b->tail = NULL;
+		BUFFER_DATA_FREE(d);
+	}
+
+	return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
+
+#undef MAX_CHUNKS
+#undef MAX_FLUSH
+}
+
+buffer_status_t
+buffer_write(struct buffer * b, int fd, const void *p, size_t size)
+{
+	ssize_t nbytes;
+
+#if 0
+	/* Should we attempt to drain any previously buffered data?  This could help reduce latency in pushing out the data if we are stuck in a long-running thread that is preventing the main select loop from calling the flush thread... */
+
+	if (b->head && (buffer_flush_available(b, fd) == BUFFER_ERROR))
+		return BUFFER_ERROR;
+#endif
+	if (b->head)
+		/* Buffer is not empty, so do not attempt to write the new data. */
+		nbytes = 0;
+	else if ((nbytes = write(fd, p, size)) < 0) {
+		if (ERRNO_IO_RETRY(errno))
+			nbytes = 0;
+		else
+			return BUFFER_ERROR;
+	}
+	/* Add any remaining data to the buffer. */
+	{
+		size_t written = nbytes;
+		if (written < size)
+			buffer_put(b, ((const char *)p) + written,
+				   size - written);
+	}
+	return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
+}
diff --git a/openbsc/src/vty/cardshell.h b/openbsc/src/vty/cardshell.h
new file mode 100644
index 0000000..85164d2
--- /dev/null
+++ b/openbsc/src/vty/cardshell.h
@@ -0,0 +1,6 @@
+#include "../../bscconfig.h"
+#define QUAGGA_PROGNAME	PACKAGE_NAME
+#define QUAGGA_VERSION	PACKAGE_VERSION
+#define QUAGGA_COPYRIGHT "Harald Welte <laforge@gnumonks.org>"
+#define CONFIGFILE_MASK 022
+#define SYSCONFDIR "/usr/local/etc"
diff --git a/openbsc/src/vty/command.c b/openbsc/src/vty/command.c
new file mode 100644
index 0000000..67e6804
--- /dev/null
+++ b/openbsc/src/vty/command.c
@@ -0,0 +1,3416 @@
+/*
+   $Id: command.c,v 1.47 2005/04/25 16:26:42 paul Exp $
+
+   Command interpreter routine for virtual terminal [aka TeletYpe]
+   Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+
+This file is part of GNU Zebra.
+
+GNU Zebra is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published
+by the Free Software Foundation; either version 2, or (at your
+option) any later version.
+
+GNU Zebra 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
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Zebra; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.  */
+
+#include "cardshell.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#define _XOPEN_SOURCE
+#include <unistd.h>
+#include <assert.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+//#include "memory.h"
+//#include "log.h"
+//#include <lib/version.h>
+//#include "thread.h"
+#include <vty/vector.h>
+#include <vty/vty.h>
+#include <vty/command.h>
+//#include "workqueue.h"
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <osmocore/talloc.h>
+
+void *tall_vty_cmd_ctx;
+
+/* Command vector which includes some level of command lists. Normally
+   each daemon maintains each own cmdvec. */
+vector cmdvec;
+
+/* Host information structure. */
+struct host host;
+
+/* Standard command node structures. */
+struct cmd_node auth_node = {
+	AUTH_NODE,
+	"Password: ",
+};
+
+struct cmd_node view_node = {
+	VIEW_NODE,
+	"%s> ",
+};
+
+struct cmd_node auth_enable_node = {
+	AUTH_ENABLE_NODE,
+	"Password: ",
+};
+
+struct cmd_node enable_node = {
+	ENABLE_NODE,
+	"%s# ",
+};
+
+struct cmd_node config_node = {
+	CONFIG_NODE,
+	"%s(config)# ",
+	1
+};
+
+/* Default motd string. */
+const char *default_motd = "\r\n\
+Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ").\r\n\
+" QUAGGA_COPYRIGHT "\r\n\
+\r\n";
+
+#if 0
+static struct facility_map {
+	int facility;
+	const char *name;
+	size_t match;
+} syslog_facilities[] = {
+	{
+	LOG_KERN, "kern", 1}, {
+	LOG_USER, "user", 2}, {
+	LOG_MAIL, "mail", 1}, {
+	LOG_DAEMON, "daemon", 1}, {
+	LOG_AUTH, "auth", 1}, {
+	LOG_SYSLOG, "syslog", 1}, {
+	LOG_LPR, "lpr", 2}, {
+	LOG_NEWS, "news", 1}, {
+	LOG_UUCP, "uucp", 2}, {
+	LOG_CRON, "cron", 1},
+#ifdef LOG_FTP
+	{
+	LOG_FTP, "ftp", 1},
+#endif
+	{
+	LOG_LOCAL0, "local0", 6}, {
+	LOG_LOCAL1, "local1", 6}, {
+	LOG_LOCAL2, "local2", 6}, {
+	LOG_LOCAL3, "local3", 6}, {
+	LOG_LOCAL4, "local4", 6}, {
+	LOG_LOCAL5, "local5", 6}, {
+	LOG_LOCAL6, "local6", 6}, {
+	LOG_LOCAL7, "local7", 6}, {
+0, NULL, 0},};
+
+static const char *facility_name(int facility)
+{
+	struct facility_map *fm;
+
+	for (fm = syslog_facilities; fm->name; fm++)
+		if (fm->facility == facility)
+			return fm->name;
+	return "";
+}
+
+static int facility_match(const char *str)
+{
+	struct facility_map *fm;
+
+	for (fm = syslog_facilities; fm->name; fm++)
+		if (!strncmp(str, fm->name, fm->match))
+			return fm->facility;
+	return -1;
+}
+
+static int level_match(const char *s)
+{
+	int level;
+
+	for (level = 0; zlog_priority[level] != NULL; level++)
+		if (!strncmp(s, zlog_priority[level], 2))
+			return level;
+	return ZLOG_DISABLED;
+}
+#endif
+
+/* This is called from main when a daemon is invoked with -v or --version. */
+void print_version(const char *progname)
+{
+	printf("%s version %s\n", progname, QUAGGA_VERSION);
+	printf("%s\n", QUAGGA_COPYRIGHT);
+}
+
+/* Utility function to concatenate argv argument into a single string
+   with inserting ' ' character between each argument.  */
+char *argv_concat(const char **argv, int argc, int shift)
+{
+	int i;
+	size_t len;
+	char *str;
+	char *p;
+
+	len = 0;
+	for (i = shift; i < argc; i++)
+		len += strlen(argv[i]) + 1;
+	if (!len)
+		return NULL;
+	p = str = _talloc_zero(tall_vty_cmd_ctx, len, "arvg_concat");
+	for (i = shift; i < argc; i++) {
+		size_t arglen;
+		memcpy(p, argv[i], (arglen = strlen(argv[i])));
+		p += arglen;
+		*p++ = ' ';
+	}
+	*(p - 1) = '\0';
+	return str;
+}
+
+/* Install top node of command vector. */
+void install_node(struct cmd_node *node, int (*func) (struct vty *))
+{
+	vector_set_index(cmdvec, node->node, node);
+	node->func = func;
+	node->cmd_vector = vector_init(VECTOR_MIN_SIZE);
+}
+
+/* Compare two command's string.  Used in sort_node (). */
+static int cmp_node(const void *p, const void *q)
+{
+	struct cmd_element *a = *(struct cmd_element **)p;
+	struct cmd_element *b = *(struct cmd_element **)q;
+
+	return strcmp(a->string, b->string);
+}
+
+static int cmp_desc(const void *p, const void *q)
+{
+	struct desc *a = *(struct desc **)p;
+	struct desc *b = *(struct desc **)q;
+
+	return strcmp(a->cmd, b->cmd);
+}
+
+/* Sort each node's command element according to command string. */
+void sort_node()
+{
+	unsigned int i, j;
+	struct cmd_node *cnode;
+	vector descvec;
+	struct cmd_element *cmd_element;
+
+	for (i = 0; i < vector_active(cmdvec); i++)
+		if ((cnode = vector_slot(cmdvec, i)) != NULL) {
+			vector cmd_vector = cnode->cmd_vector;
+			qsort(cmd_vector->index, vector_active(cmd_vector),
+			      sizeof(void *), cmp_node);
+
+			for (j = 0; j < vector_active(cmd_vector); j++)
+				if ((cmd_element =
+				     vector_slot(cmd_vector, j)) != NULL
+				    && vector_active(cmd_element->strvec)) {
+					descvec =
+					    vector_slot(cmd_element->strvec,
+							vector_active
+							(cmd_element->strvec) -
+							1);
+					qsort(descvec->index,
+					      vector_active(descvec),
+					      sizeof(void *), cmp_desc);
+				}
+		}
+}
+
+/* Breaking up string into each command piece. I assume given
+   character is separated by a space character. Return value is a
+   vector which includes char ** data element. */
+vector cmd_make_strvec(const char *string)
+{
+	const char *cp, *start;
+	char *token;
+	int strlen;
+	vector strvec;
+
+	if (string == NULL)
+		return NULL;
+
+	cp = string;
+
+	/* Skip white spaces. */
+	while (isspace((int)*cp) && *cp != '\0')
+		cp++;
+
+	/* Return if there is only white spaces */
+	if (*cp == '\0')
+		return NULL;
+
+	if (*cp == '!' || *cp == '#')
+		return NULL;
+
+	/* Prepare return vector. */
+	strvec = vector_init(VECTOR_MIN_SIZE);
+
+	/* Copy each command piece and set into vector. */
+	while (1) {
+		start = cp;
+		while (!(isspace((int)*cp) || *cp == '\r' || *cp == '\n') &&
+		       *cp != '\0')
+			cp++;
+		strlen = cp - start;
+		token = _talloc_zero(tall_vty_cmd_ctx, strlen + 1, "make_strvec");
+		memcpy(token, start, strlen);
+		*(token + strlen) = '\0';
+		vector_set(strvec, token);
+
+		while ((isspace((int)*cp) || *cp == '\n' || *cp == '\r') &&
+		       *cp != '\0')
+			cp++;
+
+		if (*cp == '\0')
+			return strvec;
+	}
+}
+
+/* Free allocated string vector. */
+void cmd_free_strvec(vector v)
+{
+	unsigned int i;
+	char *cp;
+
+	if (!v)
+		return;
+
+	for (i = 0; i < vector_active(v); i++)
+		if ((cp = vector_slot(v, i)) != NULL)
+			talloc_free(cp);
+
+	vector_free(v);
+}
+
+/* Fetch next description.  Used in cmd_make_descvec(). */
+static char *cmd_desc_str(const char **string)
+{
+	const char *cp, *start;
+	char *token;
+	int strlen;
+
+	cp = *string;
+
+	if (cp == NULL)
+		return NULL;
+
+	/* Skip white spaces. */
+	while (isspace((int)*cp) && *cp != '\0')
+		cp++;
+
+	/* Return if there is only white spaces */
+	if (*cp == '\0')
+		return NULL;
+
+	start = cp;
+
+	while (!(*cp == '\r' || *cp == '\n') && *cp != '\0')
+		cp++;
+
+	strlen = cp - start;
+	token = _talloc_zero(tall_vty_cmd_ctx, strlen + 1, "cmd_desc_str");
+	memcpy(token, start, strlen);
+	*(token + strlen) = '\0';
+
+	*string = cp;
+
+	return token;
+}
+
+/* New string vector. */
+static vector cmd_make_descvec(const char *string, const char *descstr)
+{
+	int multiple = 0;
+	const char *sp;
+	char *token;
+	int len;
+	const char *cp;
+	const char *dp;
+	vector allvec;
+	vector strvec = NULL;
+	struct desc *desc;
+
+	cp = string;
+	dp = descstr;
+
+	if (cp == NULL)
+		return NULL;
+
+	allvec = vector_init(VECTOR_MIN_SIZE);
+
+	while (1) {
+		while (isspace((int)*cp) && *cp != '\0')
+			cp++;
+
+		if (*cp == '(') {
+			multiple = 1;
+			cp++;
+		}
+		if (*cp == ')') {
+			multiple = 0;
+			cp++;
+		}
+		if (*cp == '|') {
+			if (!multiple) {
+				fprintf(stderr, "Command parse error!: %s\n",
+					string);
+				exit(1);
+			}
+			cp++;
+		}
+
+		while (isspace((int)*cp) && *cp != '\0')
+			cp++;
+
+		if (*cp == '(') {
+			multiple = 1;
+			cp++;
+		}
+
+		if (*cp == '\0')
+			return allvec;
+
+		sp = cp;
+
+		while (!
+		       (isspace((int)*cp) || *cp == '\r' || *cp == '\n'
+			|| *cp == ')' || *cp == '|') && *cp != '\0')
+			cp++;
+
+		len = cp - sp;
+
+		token = _talloc_zero(tall_vty_cmd_ctx, len + 1, "cmd_make_descvec");
+		memcpy(token, sp, len);
+		*(token + len) = '\0';
+
+		desc = talloc_zero(tall_vty_cmd_ctx, struct desc);
+		desc->cmd = token;
+		desc->str = cmd_desc_str(&dp);
+
+		if (multiple) {
+			if (multiple == 1) {
+				strvec = vector_init(VECTOR_MIN_SIZE);
+				vector_set(allvec, strvec);
+			}
+			multiple++;
+		} else {
+			strvec = vector_init(VECTOR_MIN_SIZE);
+			vector_set(allvec, strvec);
+		}
+		vector_set(strvec, desc);
+	}
+}
+
+/* Count mandantory string vector size.  This is to determine inputed
+   command has enough command length. */
+static int cmd_cmdsize(vector strvec)
+{
+	unsigned int i;
+	int size = 0;
+	vector descvec;
+	struct desc *desc;
+
+	for (i = 0; i < vector_active(strvec); i++)
+		if ((descvec = vector_slot(strvec, i)) != NULL) {
+			if ((vector_active(descvec)) == 1
+			    && (desc = vector_slot(descvec, 0)) != NULL) {
+				if (desc->cmd == NULL || CMD_OPTION(desc->cmd))
+					return size;
+				else
+					size++;
+			} else
+				size++;
+		}
+	return size;
+}
+
+/* Return prompt character of specified node. */
+const char *cmd_prompt(enum node_type node)
+{
+	struct cmd_node *cnode;
+
+	cnode = vector_slot(cmdvec, node);
+	return cnode->prompt;
+}
+
+/* Install a command into a node. */
+void install_element(enum node_type ntype, struct cmd_element *cmd)
+{
+	struct cmd_node *cnode;
+
+	cnode = vector_slot(cmdvec, ntype);
+
+	if (cnode == NULL) {
+		fprintf(stderr,
+			"Command node %d doesn't exist, please check it\n",
+			ntype);
+		exit(1);
+	}
+
+	vector_set(cnode->cmd_vector, cmd);
+
+	cmd->strvec = cmd_make_descvec(cmd->string, cmd->doc);
+	cmd->cmdsize = cmd_cmdsize(cmd->strvec);
+}
+
+#ifdef VTY_CRYPT_PW
+static unsigned char itoa64[] =
+    "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static void to64(char *s, long v, int n)
+{
+	while (--n >= 0) {
+		*s++ = itoa64[v & 0x3f];
+		v >>= 6;
+	}
+}
+
+static char *zencrypt(const char *passwd)
+{
+	char salt[6];
+	struct timeval tv;
+	char *crypt(const char *, const char *);
+
+	gettimeofday(&tv, 0);
+
+	to64(&salt[0], random(), 3);
+	to64(&salt[3], tv.tv_usec, 3);
+	salt[5] = '\0';
+
+	return crypt(passwd, salt);
+}
+#endif
+
+/* This function write configuration of this host. */
+static int config_write_host(struct vty *vty)
+{
+	if (host.name)
+		vty_out(vty, "hostname %s%s", host.name, VTY_NEWLINE);
+
+	if (host.encrypt) {
+		if (host.password_encrypt)
+			vty_out(vty, "password 8 %s%s", host.password_encrypt,
+				VTY_NEWLINE);
+		if (host.enable_encrypt)
+			vty_out(vty, "enable password 8 %s%s",
+				host.enable_encrypt, VTY_NEWLINE);
+	} else {
+		if (host.password)
+			vty_out(vty, "password %s%s", host.password,
+				VTY_NEWLINE);
+		if (host.enable)
+			vty_out(vty, "enable password %s%s", host.enable,
+				VTY_NEWLINE);
+	}
+
+#if 0
+	if (zlog_default->default_lvl != LOG_DEBUG) {
+		vty_out(vty, "! N.B. The 'log trap' command is deprecated.%s",
+			VTY_NEWLINE);
+		vty_out(vty, "log trap %s%s",
+			zlog_priority[zlog_default->default_lvl], VTY_NEWLINE);
+	}
+
+	if (host.logfile
+	    && (zlog_default->maxlvl[ZLOG_DEST_FILE] != ZLOG_DISABLED)) {
+		vty_out(vty, "log file %s", host.logfile);
+		if (zlog_default->maxlvl[ZLOG_DEST_FILE] !=
+		    zlog_default->default_lvl)
+			vty_out(vty, " %s",
+				zlog_priority[zlog_default->
+					      maxlvl[ZLOG_DEST_FILE]]);
+		vty_out(vty, "%s", VTY_NEWLINE);
+	}
+
+	if (zlog_default->maxlvl[ZLOG_DEST_STDOUT] != ZLOG_DISABLED) {
+		vty_out(vty, "log stdout");
+		if (zlog_default->maxlvl[ZLOG_DEST_STDOUT] !=
+		    zlog_default->default_lvl)
+			vty_out(vty, " %s",
+				zlog_priority[zlog_default->
+					      maxlvl[ZLOG_DEST_STDOUT]]);
+		vty_out(vty, "%s", VTY_NEWLINE);
+	}
+
+	if (zlog_default->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED)
+		vty_out(vty, "no log monitor%s", VTY_NEWLINE);
+	else if (zlog_default->maxlvl[ZLOG_DEST_MONITOR] !=
+		 zlog_default->default_lvl)
+		vty_out(vty, "log monitor %s%s",
+			zlog_priority[zlog_default->maxlvl[ZLOG_DEST_MONITOR]],
+			VTY_NEWLINE);
+
+	if (zlog_default->maxlvl[ZLOG_DEST_SYSLOG] != ZLOG_DISABLED) {
+		vty_out(vty, "log syslog");
+		if (zlog_default->maxlvl[ZLOG_DEST_SYSLOG] !=
+		    zlog_default->default_lvl)
+			vty_out(vty, " %s",
+				zlog_priority[zlog_default->
+					      maxlvl[ZLOG_DEST_SYSLOG]]);
+		vty_out(vty, "%s", VTY_NEWLINE);
+	}
+
+	if (zlog_default->facility != LOG_DAEMON)
+		vty_out(vty, "log facility %s%s",
+			facility_name(zlog_default->facility), VTY_NEWLINE);
+
+	if (zlog_default->record_priority == 1)
+		vty_out(vty, "log record-priority%s", VTY_NEWLINE);
+#endif
+	if (host.advanced)
+		vty_out(vty, "service advanced-vty%s", VTY_NEWLINE);
+
+	if (host.encrypt)
+		vty_out(vty, "service password-encryption%s", VTY_NEWLINE);
+
+	if (host.lines >= 0)
+		vty_out(vty, "service terminal-length %d%s", host.lines,
+			VTY_NEWLINE);
+
+	if (host.motdfile)
+		vty_out(vty, "banner motd file %s%s", host.motdfile,
+			VTY_NEWLINE);
+	else if (!host.motd)
+		vty_out(vty, "no banner motd%s", VTY_NEWLINE);
+
+	return 1;
+}
+
+/* Utility function for getting command vector. */
+static vector cmd_node_vector(vector v, enum node_type ntype)
+{
+	struct cmd_node *cnode = vector_slot(v, ntype);
+	return cnode->cmd_vector;
+}
+
+#if 0
+/* Filter command vector by symbol.  This function is not actually used;
+ * should it be deleted? */
+static int cmd_filter_by_symbol(char *command, char *symbol)
+{
+	int i, lim;
+
+	if (strcmp(symbol, "IPV4_ADDRESS") == 0) {
+		i = 0;
+		lim = strlen(command);
+		while (i < lim) {
+			if (!
+			    (isdigit((int)command[i]) || command[i] == '.'
+			     || command[i] == '/'))
+				return 1;
+			i++;
+		}
+		return 0;
+	}
+	if (strcmp(symbol, "STRING") == 0) {
+		i = 0;
+		lim = strlen(command);
+		while (i < lim) {
+			if (!
+			    (isalpha((int)command[i]) || command[i] == '_'
+			     || command[i] == '-'))
+				return 1;
+			i++;
+		}
+		return 0;
+	}
+	if (strcmp(symbol, "IFNAME") == 0) {
+		i = 0;
+		lim = strlen(command);
+		while (i < lim) {
+			if (!isalnum((int)command[i]))
+				return 1;
+			i++;
+		}
+		return 0;
+	}
+	return 0;
+}
+#endif
+
+/* Completion match types. */
+enum match_type {
+	no_match,
+	extend_match,
+	ipv4_prefix_match,
+	ipv4_match,
+	ipv6_prefix_match,
+	ipv6_match,
+	range_match,
+	vararg_match,
+	partly_match,
+	exact_match
+};
+
+static enum match_type cmd_ipv4_match(const char *str)
+{
+	const char *sp;
+	int dots = 0, nums = 0;
+	char buf[4];
+
+	if (str == NULL)
+		return partly_match;
+
+	for (;;) {
+		memset(buf, 0, sizeof(buf));
+		sp = str;
+		while (*str != '\0') {
+			if (*str == '.') {
+				if (dots >= 3)
+					return no_match;
+
+				if (*(str + 1) == '.')
+					return no_match;
+
+				if (*(str + 1) == '\0')
+					return partly_match;
+
+				dots++;
+				break;
+			}
+			if (!isdigit((int)*str))
+				return no_match;
+
+			str++;
+		}
+
+		if (str - sp > 3)
+			return no_match;
+
+		strncpy(buf, sp, str - sp);
+		if (atoi(buf) > 255)
+			return no_match;
+
+		nums++;
+
+		if (*str == '\0')
+			break;
+
+		str++;
+	}
+
+	if (nums < 4)
+		return partly_match;
+
+	return exact_match;
+}
+
+static enum match_type cmd_ipv4_prefix_match(const char *str)
+{
+	const char *sp;
+	int dots = 0;
+	char buf[4];
+
+	if (str == NULL)
+		return partly_match;
+
+	for (;;) {
+		memset(buf, 0, sizeof(buf));
+		sp = str;
+		while (*str != '\0' && *str != '/') {
+			if (*str == '.') {
+				if (dots == 3)
+					return no_match;
+
+				if (*(str + 1) == '.' || *(str + 1) == '/')
+					return no_match;
+
+				if (*(str + 1) == '\0')
+					return partly_match;
+
+				dots++;
+				break;
+			}
+
+			if (!isdigit((int)*str))
+				return no_match;
+
+			str++;
+		}
+
+		if (str - sp > 3)
+			return no_match;
+
+		strncpy(buf, sp, str - sp);
+		if (atoi(buf) > 255)
+			return no_match;
+
+		if (dots == 3) {
+			if (*str == '/') {
+				if (*(str + 1) == '\0')
+					return partly_match;
+
+				str++;
+				break;
+			} else if (*str == '\0')
+				return partly_match;
+		}
+
+		if (*str == '\0')
+			return partly_match;
+
+		str++;
+	}
+
+	sp = str;
+	while (*str != '\0') {
+		if (!isdigit((int)*str))
+			return no_match;
+
+		str++;
+	}
+
+	if (atoi(sp) > 32)
+		return no_match;
+
+	return exact_match;
+}
+
+#define IPV6_ADDR_STR		"0123456789abcdefABCDEF:.%"
+#define IPV6_PREFIX_STR		"0123456789abcdefABCDEF:.%/"
+#define STATE_START		1
+#define STATE_COLON		2
+#define STATE_DOUBLE		3
+#define STATE_ADDR		4
+#define STATE_DOT               5
+#define STATE_SLASH		6
+#define STATE_MASK		7
+
+#ifdef HAVE_IPV6
+
+static enum match_type cmd_ipv6_match(const char *str)
+{
+	int state = STATE_START;
+	int colons = 0, nums = 0, double_colon = 0;
+	const char *sp = NULL;
+	struct sockaddr_in6 sin6_dummy;
+	int ret;
+
+	if (str == NULL)
+		return partly_match;
+
+	if (strspn(str, IPV6_ADDR_STR) != strlen(str))
+		return no_match;
+
+	/* use inet_pton that has a better support,
+	 * for example inet_pton can support the automatic addresses:
+	 *  ::1.2.3.4
+	 */
+	ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
+
+	if (ret == 1)
+		return exact_match;
+
+	while (*str != '\0') {
+		switch (state) {
+		case STATE_START:
+			if (*str == ':') {
+				if (*(str + 1) != ':' && *(str + 1) != '\0')
+					return no_match;
+				colons--;
+				state = STATE_COLON;
+			} else {
+				sp = str;
+				state = STATE_ADDR;
+			}
+
+			continue;
+		case STATE_COLON:
+			colons++;
+			if (*(str + 1) == ':')
+				state = STATE_DOUBLE;
+			else {
+				sp = str + 1;
+				state = STATE_ADDR;
+			}
+			break;
+		case STATE_DOUBLE:
+			if (double_colon)
+				return no_match;
+
+			if (*(str + 1) == ':')
+				return no_match;
+			else {
+				if (*(str + 1) != '\0')
+					colons++;
+				sp = str + 1;
+				state = STATE_ADDR;
+			}
+
+			double_colon++;
+			nums++;
+			break;
+		case STATE_ADDR:
+			if (*(str + 1) == ':' || *(str + 1) == '\0') {
+				if (str - sp > 3)
+					return no_match;
+
+				nums++;
+				state = STATE_COLON;
+			}
+			if (*(str + 1) == '.')
+				state = STATE_DOT;
+			break;
+		case STATE_DOT:
+			state = STATE_ADDR;
+			break;
+		default:
+			break;
+		}
+
+		if (nums > 8)
+			return no_match;
+
+		if (colons > 7)
+			return no_match;
+
+		str++;
+	}
+
+#if 0
+	if (nums < 11)
+		return partly_match;
+#endif				/* 0 */
+
+	return exact_match;
+}
+
+static enum match_type cmd_ipv6_prefix_match(const char *str)
+{
+	int state = STATE_START;
+	int colons = 0, nums = 0, double_colon = 0;
+	int mask;
+	const char *sp = NULL;
+	char *endptr = NULL;
+
+	if (str == NULL)
+		return partly_match;
+
+	if (strspn(str, IPV6_PREFIX_STR) != strlen(str))
+		return no_match;
+
+	while (*str != '\0' && state != STATE_MASK) {
+		switch (state) {
+		case STATE_START:
+			if (*str == ':') {
+				if (*(str + 1) != ':' && *(str + 1) != '\0')
+					return no_match;
+				colons--;
+				state = STATE_COLON;
+			} else {
+				sp = str;
+				state = STATE_ADDR;
+			}
+
+			continue;
+		case STATE_COLON:
+			colons++;
+			if (*(str + 1) == '/')
+				return no_match;
+			else if (*(str + 1) == ':')
+				state = STATE_DOUBLE;
+			else {
+				sp = str + 1;
+				state = STATE_ADDR;
+			}
+			break;
+		case STATE_DOUBLE:
+			if (double_colon)
+				return no_match;
+
+			if (*(str + 1) == ':')
+				return no_match;
+			else {
+				if (*(str + 1) != '\0' && *(str + 1) != '/')
+					colons++;
+				sp = str + 1;
+
+				if (*(str + 1) == '/')
+					state = STATE_SLASH;
+				else
+					state = STATE_ADDR;
+			}
+
+			double_colon++;
+			nums += 1;
+			break;
+		case STATE_ADDR:
+			if (*(str + 1) == ':' || *(str + 1) == '.'
+			    || *(str + 1) == '\0' || *(str + 1) == '/') {
+				if (str - sp > 3)
+					return no_match;
+
+				for (; sp <= str; sp++)
+					if (*sp == '/')
+						return no_match;
+
+				nums++;
+
+				if (*(str + 1) == ':')
+					state = STATE_COLON;
+				else if (*(str + 1) == '.')
+					state = STATE_DOT;
+				else if (*(str + 1) == '/')
+					state = STATE_SLASH;
+			}
+			break;
+		case STATE_DOT:
+			state = STATE_ADDR;
+			break;
+		case STATE_SLASH:
+			if (*(str + 1) == '\0')
+				return partly_match;
+
+			state = STATE_MASK;
+			break;
+		default:
+			break;
+		}
+
+		if (nums > 11)
+			return no_match;
+
+		if (colons > 7)
+			return no_match;
+
+		str++;
+	}
+
+	if (state < STATE_MASK)
+		return partly_match;
+
+	mask = strtol(str, &endptr, 10);
+	if (*endptr != '\0')
+		return no_match;
+
+	if (mask < 0 || mask > 128)
+		return no_match;
+
+/* I don't know why mask < 13 makes command match partly.
+   Forgive me to make this comments. I Want to set static default route
+   because of lack of function to originate default in ospf6d; sorry
+       yasu
+  if (mask < 13)
+    return partly_match;
+*/
+
+	return exact_match;
+}
+
+#endif				/* HAVE_IPV6  */
+
+#define DECIMAL_STRLEN_MAX 10
+
+static int cmd_range_match(const char *range, const char *str)
+{
+	char *p;
+	char buf[DECIMAL_STRLEN_MAX + 1];
+	char *endptr = NULL;
+	unsigned long min, max, val;
+
+	if (str == NULL)
+		return 1;
+
+	val = strtoul(str, &endptr, 10);
+	if (*endptr != '\0')
+		return 0;
+
+	range++;
+	p = strchr(range, '-');
+	if (p == NULL)
+		return 0;
+	if (p - range > DECIMAL_STRLEN_MAX)
+		return 0;
+	strncpy(buf, range, p - range);
+	buf[p - range] = '\0';
+	min = strtoul(buf, &endptr, 10);
+	if (*endptr != '\0')
+		return 0;
+
+	range = p + 1;
+	p = strchr(range, '>');
+	if (p == NULL)
+		return 0;
+	if (p - range > DECIMAL_STRLEN_MAX)
+		return 0;
+	strncpy(buf, range, p - range);
+	buf[p - range] = '\0';
+	max = strtoul(buf, &endptr, 10);
+	if (*endptr != '\0')
+		return 0;
+
+	if (val < min || val > max)
+		return 0;
+
+	return 1;
+}
+
+/* Make completion match and return match type flag. */
+static enum match_type
+cmd_filter_by_completion(char *command, vector v, unsigned int index)
+{
+	unsigned int i;
+	const char *str;
+	struct cmd_element *cmd_element;
+	enum match_type match_type;
+	vector descvec;
+	struct desc *desc;
+
+	match_type = no_match;
+
+	/* If command and cmd_element string does not match set NULL to vector */
+	for (i = 0; i < vector_active(v); i++)
+		if ((cmd_element = vector_slot(v, i)) != NULL) {
+			if (index >= vector_active(cmd_element->strvec))
+				vector_slot(v, i) = NULL;
+			else {
+				unsigned int j;
+				int matched = 0;
+
+				descvec =
+				    vector_slot(cmd_element->strvec, index);
+
+				for (j = 0; j < vector_active(descvec); j++)
+					if ((desc = vector_slot(descvec, j))) {
+						str = desc->cmd;
+
+						if (CMD_VARARG(str)) {
+							if (match_type <
+							    vararg_match)
+								match_type =
+								    vararg_match;
+							matched++;
+						} else if (CMD_RANGE(str)) {
+							if (cmd_range_match
+							    (str, command)) {
+								if (match_type <
+								    range_match)
+									match_type
+									    =
+									    range_match;
+
+								matched++;
+							}
+						}
+#ifdef HAVE_IPV6
+						else if (CMD_IPV6(str)) {
+							if (cmd_ipv6_match
+							    (command)) {
+								if (match_type <
+								    ipv6_match)
+									match_type
+									    =
+									    ipv6_match;
+
+								matched++;
+							}
+						} else if (CMD_IPV6_PREFIX(str)) {
+							if (cmd_ipv6_prefix_match(command)) {
+								if (match_type <
+								    ipv6_prefix_match)
+									match_type
+									    =
+									    ipv6_prefix_match;
+
+								matched++;
+							}
+						}
+#endif				/* HAVE_IPV6  */
+						else if (CMD_IPV4(str)) {
+							if (cmd_ipv4_match
+							    (command)) {
+								if (match_type <
+								    ipv4_match)
+									match_type
+									    =
+									    ipv4_match;
+
+								matched++;
+							}
+						} else if (CMD_IPV4_PREFIX(str)) {
+							if (cmd_ipv4_prefix_match(command)) {
+								if (match_type <
+								    ipv4_prefix_match)
+									match_type
+									    =
+									    ipv4_prefix_match;
+								matched++;
+							}
+						} else
+							/* Check is this point's argument optional ? */
+						if (CMD_OPTION(str)
+							    ||
+							    CMD_VARIABLE(str)) {
+							if (match_type <
+							    extend_match)
+								match_type =
+								    extend_match;
+							matched++;
+						} else
+						    if (strncmp
+							(command, str,
+							 strlen(command)) ==
+							0) {
+							if (strcmp(command, str)
+							    == 0)
+								match_type =
+								    exact_match;
+							else {
+								if (match_type <
+								    partly_match)
+									match_type
+									    =
+									    partly_match;
+							}
+							matched++;
+						}
+					}
+				if (!matched)
+					vector_slot(v, i) = NULL;
+			}
+		}
+	return match_type;
+}
+
+/* Filter vector by command character with index. */
+static enum match_type
+cmd_filter_by_string(char *command, vector v, unsigned int index)
+{
+	unsigned int i;
+	const char *str;
+	struct cmd_element *cmd_element;
+	enum match_type match_type;
+	vector descvec;
+	struct desc *desc;
+
+	match_type = no_match;
+
+	/* If command and cmd_element string does not match set NULL to vector */
+	for (i = 0; i < vector_active(v); i++)
+		if ((cmd_element = vector_slot(v, i)) != NULL) {
+			/* If given index is bigger than max string vector of command,
+			   set NULL */
+			if (index >= vector_active(cmd_element->strvec))
+				vector_slot(v, i) = NULL;
+			else {
+				unsigned int j;
+				int matched = 0;
+
+				descvec =
+				    vector_slot(cmd_element->strvec, index);
+
+				for (j = 0; j < vector_active(descvec); j++)
+					if ((desc = vector_slot(descvec, j))) {
+						str = desc->cmd;
+
+						if (CMD_VARARG(str)) {
+							if (match_type <
+							    vararg_match)
+								match_type =
+								    vararg_match;
+							matched++;
+						} else if (CMD_RANGE(str)) {
+							if (cmd_range_match
+							    (str, command)) {
+								if (match_type <
+								    range_match)
+									match_type
+									    =
+									    range_match;
+								matched++;
+							}
+						}
+#ifdef HAVE_IPV6
+						else if (CMD_IPV6(str)) {
+							if (cmd_ipv6_match
+							    (command) ==
+							    exact_match) {
+								if (match_type <
+								    ipv6_match)
+									match_type
+									    =
+									    ipv6_match;
+								matched++;
+							}
+						} else if (CMD_IPV6_PREFIX(str)) {
+							if (cmd_ipv6_prefix_match(command) == exact_match) {
+								if (match_type <
+								    ipv6_prefix_match)
+									match_type
+									    =
+									    ipv6_prefix_match;
+								matched++;
+							}
+						}
+#endif				/* HAVE_IPV6  */
+						else if (CMD_IPV4(str)) {
+							if (cmd_ipv4_match
+							    (command) ==
+							    exact_match) {
+								if (match_type <
+								    ipv4_match)
+									match_type
+									    =
+									    ipv4_match;
+								matched++;
+							}
+						} else if (CMD_IPV4_PREFIX(str)) {
+							if (cmd_ipv4_prefix_match(command) == exact_match) {
+								if (match_type <
+								    ipv4_prefix_match)
+									match_type
+									    =
+									    ipv4_prefix_match;
+								matched++;
+							}
+						} else if (CMD_OPTION(str)
+							   || CMD_VARIABLE(str))
+						{
+							if (match_type <
+							    extend_match)
+								match_type =
+								    extend_match;
+							matched++;
+						} else {
+							if (strcmp(command, str)
+							    == 0) {
+								match_type =
+								    exact_match;
+								matched++;
+							}
+						}
+					}
+				if (!matched)
+					vector_slot(v, i) = NULL;
+			}
+		}
+	return match_type;
+}
+
+/* Check ambiguous match */
+static int
+is_cmd_ambiguous(char *command, vector v, int index, enum match_type type)
+{
+	unsigned int i;
+	unsigned int j;
+	const char *str = NULL;
+	struct cmd_element *cmd_element;
+	const char *matched = NULL;
+	vector descvec;
+	struct desc *desc;
+
+	for (i = 0; i < vector_active(v); i++)
+		if ((cmd_element = vector_slot(v, i)) != NULL) {
+			int match = 0;
+
+			descvec = vector_slot(cmd_element->strvec, index);
+
+			for (j = 0; j < vector_active(descvec); j++)
+				if ((desc = vector_slot(descvec, j))) {
+					enum match_type ret;
+
+					str = desc->cmd;
+
+					switch (type) {
+					case exact_match:
+						if (!
+						    (CMD_OPTION(str)
+						     || CMD_VARIABLE(str))
+&& strcmp(command, str) == 0)
+							match++;
+						break;
+					case partly_match:
+						if (!
+						    (CMD_OPTION(str)
+						     || CMD_VARIABLE(str))
+&& strncmp(command, str, strlen(command)) == 0) {
+							if (matched
+							    && strcmp(matched,
+								      str) != 0)
+								return 1;	/* There is ambiguous match. */
+							else
+								matched = str;
+							match++;
+						}
+						break;
+					case range_match:
+						if (cmd_range_match
+						    (str, command)) {
+							if (matched
+							    && strcmp(matched,
+								      str) != 0)
+								return 1;
+							else
+								matched = str;
+							match++;
+						}
+						break;
+#ifdef HAVE_IPV6
+					case ipv6_match:
+						if (CMD_IPV6(str))
+							match++;
+						break;
+					case ipv6_prefix_match:
+						if ((ret =
+						     cmd_ipv6_prefix_match
+						     (command)) != no_match) {
+							if (ret == partly_match)
+								return 2;	/* There is incomplete match. */
+
+							match++;
+						}
+						break;
+#endif				/* HAVE_IPV6 */
+					case ipv4_match:
+						if (CMD_IPV4(str))
+							match++;
+						break;
+					case ipv4_prefix_match:
+						if ((ret =
+						     cmd_ipv4_prefix_match
+						     (command)) != no_match) {
+							if (ret == partly_match)
+								return 2;	/* There is incomplete match. */
+
+							match++;
+						}
+						break;
+					case extend_match:
+						if (CMD_OPTION(str)
+						    || CMD_VARIABLE(str))
+							match++;
+						break;
+					case no_match:
+					default:
+						break;
+					}
+				}
+			if (!match)
+				vector_slot(v, i) = NULL;
+		}
+	return 0;
+}
+
+/* If src matches dst return dst string, otherwise return NULL */
+static const char *cmd_entry_function(const char *src, const char *dst)
+{
+	/* Skip variable arguments. */
+	if (CMD_OPTION(dst) || CMD_VARIABLE(dst) || CMD_VARARG(dst) ||
+	    CMD_IPV4(dst) || CMD_IPV4_PREFIX(dst) || CMD_RANGE(dst))
+		return NULL;
+
+	/* In case of 'command \t', given src is NULL string. */
+	if (src == NULL)
+		return dst;
+
+	/* Matched with input string. */
+	if (strncmp(src, dst, strlen(src)) == 0)
+		return dst;
+
+	return NULL;
+}
+
+/* If src matches dst return dst string, otherwise return NULL */
+/* This version will return the dst string always if it is
+   CMD_VARIABLE for '?' key processing */
+static const char *cmd_entry_function_desc(const char *src, const char *dst)
+{
+	if (CMD_VARARG(dst))
+		return dst;
+
+	if (CMD_RANGE(dst)) {
+		if (cmd_range_match(dst, src))
+			return dst;
+		else
+			return NULL;
+	}
+#ifdef HAVE_IPV6
+	if (CMD_IPV6(dst)) {
+		if (cmd_ipv6_match(src))
+			return dst;
+		else
+			return NULL;
+	}
+
+	if (CMD_IPV6_PREFIX(dst)) {
+		if (cmd_ipv6_prefix_match(src))
+			return dst;
+		else
+			return NULL;
+	}
+#endif				/* HAVE_IPV6 */
+
+	if (CMD_IPV4(dst)) {
+		if (cmd_ipv4_match(src))
+			return dst;
+		else
+			return NULL;
+	}
+
+	if (CMD_IPV4_PREFIX(dst)) {
+		if (cmd_ipv4_prefix_match(src))
+			return dst;
+		else
+			return NULL;
+	}
+
+	/* Optional or variable commands always match on '?' */
+	if (CMD_OPTION(dst) || CMD_VARIABLE(dst))
+		return dst;
+
+	/* In case of 'command \t', given src is NULL string. */
+	if (src == NULL)
+		return dst;
+
+	if (strncmp(src, dst, strlen(src)) == 0)
+		return dst;
+	else
+		return NULL;
+}
+
+/* Check same string element existence.  If it isn't there return
+    1. */
+static int cmd_unique_string(vector v, const char *str)
+{
+	unsigned int i;
+	char *match;
+
+	for (i = 0; i < vector_active(v); i++)
+		if ((match = vector_slot(v, i)) != NULL)
+			if (strcmp(match, str) == 0)
+				return 0;
+	return 1;
+}
+
+/* Compare string to description vector.  If there is same string
+   return 1 else return 0. */
+static int desc_unique_string(vector v, const char *str)
+{
+	unsigned int i;
+	struct desc *desc;
+
+	for (i = 0; i < vector_active(v); i++)
+		if ((desc = vector_slot(v, i)) != NULL)
+			if (strcmp(desc->cmd, str) == 0)
+				return 1;
+	return 0;
+}
+
+static int cmd_try_do_shortcut(enum node_type node, char *first_word)
+{
+	if (first_word != NULL &&
+	    node != AUTH_NODE &&
+	    node != VIEW_NODE &&
+	    node != AUTH_ENABLE_NODE &&
+	    node != ENABLE_NODE && 0 == strcmp("do", first_word))
+		return 1;
+	return 0;
+}
+
+/* '?' describe command support. */
+static vector
+cmd_describe_command_real(vector vline, struct vty *vty, int *status)
+{
+	unsigned int i;
+	vector cmd_vector;
+#define INIT_MATCHVEC_SIZE 10
+	vector matchvec;
+	struct cmd_element *cmd_element;
+	unsigned int index;
+	int ret;
+	enum match_type match;
+	char *command;
+	static struct desc desc_cr = { "<cr>", "" };
+
+	/* Set index. */
+	if (vector_active(vline) == 0) {
+		*status = CMD_ERR_NO_MATCH;
+		return NULL;
+	} else
+		index = vector_active(vline) - 1;
+
+	/* Make copy vector of current node's command vector. */
+	cmd_vector = vector_copy(cmd_node_vector(cmdvec, vty->node));
+
+	/* Prepare match vector */
+	matchvec = vector_init(INIT_MATCHVEC_SIZE);
+
+	/* Filter commands. */
+	/* Only words precedes current word will be checked in this loop. */
+	for (i = 0; i < index; i++)
+		if ((command = vector_slot(vline, i))) {
+			match =
+			    cmd_filter_by_completion(command, cmd_vector, i);
+
+			if (match == vararg_match) {
+				struct cmd_element *cmd_element;
+				vector descvec;
+				unsigned int j, k;
+
+				for (j = 0; j < vector_active(cmd_vector); j++)
+					if ((cmd_element =
+					     vector_slot(cmd_vector, j)) != NULL
+					    &&
+					    (vector_active
+					     (cmd_element->strvec))) {
+						descvec =
+						    vector_slot(cmd_element->
+								strvec,
+								vector_active
+								(cmd_element->
+								 strvec) - 1);
+						for (k = 0;
+						     k < vector_active(descvec);
+						     k++) {
+							struct desc *desc =
+							    vector_slot(descvec,
+									k);
+							vector_set(matchvec,
+								   desc);
+						}
+					}
+
+				vector_set(matchvec, &desc_cr);
+				vector_free(cmd_vector);
+
+				return matchvec;
+			}
+
+			if ((ret =
+			     is_cmd_ambiguous(command, cmd_vector, i,
+					      match)) == 1) {
+				vector_free(cmd_vector);
+				*status = CMD_ERR_AMBIGUOUS;
+				return NULL;
+			} else if (ret == 2) {
+				vector_free(cmd_vector);
+				*status = CMD_ERR_NO_MATCH;
+				return NULL;
+			}
+		}
+
+	/* Prepare match vector */
+	/*  matchvec = vector_init (INIT_MATCHVEC_SIZE); */
+
+	/* Make sure that cmd_vector is filtered based on current word */
+	command = vector_slot(vline, index);
+	if (command)
+		match = cmd_filter_by_completion(command, cmd_vector, index);
+
+	/* Make description vector. */
+	for (i = 0; i < vector_active(cmd_vector); i++)
+		if ((cmd_element = vector_slot(cmd_vector, i)) != NULL) {
+			const char *string = NULL;
+			vector strvec = cmd_element->strvec;
+
+			/* if command is NULL, index may be equal to vector_active */
+			if (command && index >= vector_active(strvec))
+				vector_slot(cmd_vector, i) = NULL;
+			else {
+				/* Check if command is completed. */
+				if (command == NULL
+				    && index == vector_active(strvec)) {
+					string = "<cr>";
+					if (!desc_unique_string
+					    (matchvec, string))
+						vector_set(matchvec, &desc_cr);
+				} else {
+					unsigned int j;
+					vector descvec =
+					    vector_slot(strvec, index);
+					struct desc *desc;
+
+					for (j = 0; j < vector_active(descvec);
+					     j++)
+						if ((desc =
+						     vector_slot(descvec, j))) {
+							string =
+							    cmd_entry_function_desc
+							    (command,
+							     desc->cmd);
+							if (string) {
+								/* Uniqueness check */
+								if (!desc_unique_string(matchvec, string))
+									vector_set
+									    (matchvec,
+									     desc);
+							}
+						}
+				}
+			}
+		}
+	vector_free(cmd_vector);
+
+	if (vector_slot(matchvec, 0) == NULL) {
+		vector_free(matchvec);
+		*status = CMD_ERR_NO_MATCH;
+	} else
+		*status = CMD_SUCCESS;
+
+	return matchvec;
+}
+
+vector cmd_describe_command(vector vline, struct vty * vty, int *status)
+{
+	vector ret;
+
+	if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
+		enum node_type onode;
+		vector shifted_vline;
+		unsigned int index;
+
+		onode = vty->node;
+		vty->node = ENABLE_NODE;
+		/* We can try it on enable node, cos' the vty is authenticated */
+
+		shifted_vline = vector_init(vector_count(vline));
+		/* use memcpy? */
+		for (index = 1; index < vector_active(vline); index++) {
+			vector_set_index(shifted_vline, index - 1,
+					 vector_lookup(vline, index));
+		}
+
+		ret = cmd_describe_command_real(shifted_vline, vty, status);
+
+		vector_free(shifted_vline);
+		vty->node = onode;
+		return ret;
+	}
+
+	return cmd_describe_command_real(vline, vty, status);
+}
+
+/* Check LCD of matched command. */
+static int cmd_lcd(char **matched)
+{
+	int i;
+	int j;
+	int lcd = -1;
+	char *s1, *s2;
+	char c1, c2;
+
+	if (matched[0] == NULL || matched[1] == NULL)
+		return 0;
+
+	for (i = 1; matched[i] != NULL; i++) {
+		s1 = matched[i - 1];
+		s2 = matched[i];
+
+		for (j = 0; (c1 = s1[j]) && (c2 = s2[j]); j++)
+			if (c1 != c2)
+				break;
+
+		if (lcd < 0)
+			lcd = j;
+		else {
+			if (lcd > j)
+				lcd = j;
+		}
+	}
+	return lcd;
+}
+
+/* Command line completion support. */
+static char **cmd_complete_command_real(vector vline, struct vty *vty,
+					int *status)
+{
+	unsigned int i;
+	vector cmd_vector = vector_copy(cmd_node_vector(cmdvec, vty->node));
+#define INIT_MATCHVEC_SIZE 10
+	vector matchvec;
+	struct cmd_element *cmd_element;
+	unsigned int index;
+	char **match_str;
+	struct desc *desc;
+	vector descvec;
+	char *command;
+	int lcd;
+
+	if (vector_active(vline) == 0) {
+		*status = CMD_ERR_NO_MATCH;
+		return NULL;
+	} else
+		index = vector_active(vline) - 1;
+
+	/* First, filter by preceeding command string */
+	for (i = 0; i < index; i++)
+		if ((command = vector_slot(vline, i))) {
+			enum match_type match;
+			int ret;
+
+			/* First try completion match, if there is exactly match return 1 */
+			match =
+			    cmd_filter_by_completion(command, cmd_vector, i);
+
+			/* If there is exact match then filter ambiguous match else check
+			   ambiguousness. */
+			if ((ret =
+			     is_cmd_ambiguous(command, cmd_vector, i,
+					      match)) == 1) {
+				vector_free(cmd_vector);
+				*status = CMD_ERR_AMBIGUOUS;
+				return NULL;
+			}
+			/*
+			   else if (ret == 2)
+			   {
+			   vector_free (cmd_vector);
+			   *status = CMD_ERR_NO_MATCH;
+			   return NULL;
+			   }
+			 */
+		}
+
+	/* Prepare match vector. */
+	matchvec = vector_init(INIT_MATCHVEC_SIZE);
+
+	/* Now we got into completion */
+	for (i = 0; i < vector_active(cmd_vector); i++)
+		if ((cmd_element = vector_slot(cmd_vector, i))) {
+			const char *string;
+			vector strvec = cmd_element->strvec;
+
+			/* Check field length */
+			if (index >= vector_active(strvec))
+				vector_slot(cmd_vector, i) = NULL;
+			else {
+				unsigned int j;
+
+				descvec = vector_slot(strvec, index);
+				for (j = 0; j < vector_active(descvec); j++)
+					if ((desc = vector_slot(descvec, j))) {
+						if ((string = cmd_entry_function(vector_slot(vline, index), desc->cmd)))
+							if (cmd_unique_string (matchvec, string))
+								vector_set (matchvec, talloc_strdup(tall_vty_cmd_ctx, string));
+					}
+			}
+		}
+
+	/* We don't need cmd_vector any more. */
+	vector_free(cmd_vector);
+
+	/* No matched command */
+	if (vector_slot(matchvec, 0) == NULL) {
+		vector_free(matchvec);
+
+		/* In case of 'command \t' pattern.  Do you need '?' command at
+		   the end of the line. */
+		if (vector_slot(vline, index) == '\0')
+			*status = CMD_ERR_NOTHING_TODO;
+		else
+			*status = CMD_ERR_NO_MATCH;
+		return NULL;
+	}
+
+	/* Only one matched */
+	if (vector_slot(matchvec, 1) == NULL) {
+		match_str = (char **)matchvec->index;
+		vector_only_wrapper_free(matchvec);
+		*status = CMD_COMPLETE_FULL_MATCH;
+		return match_str;
+	}
+	/* Make it sure last element is NULL. */
+	vector_set(matchvec, NULL);
+
+	/* Check LCD of matched strings. */
+	if (vector_slot(vline, index) != NULL) {
+		lcd = cmd_lcd((char **)matchvec->index);
+
+		if (lcd) {
+			int len = strlen(vector_slot(vline, index));
+
+			if (len < lcd) {
+				char *lcdstr;
+
+				lcdstr = _talloc_zero(tall_vty_cmd_ctx, lcd + 1,
+						      "complete-lcdstr");
+				memcpy(lcdstr, matchvec->index[0], lcd);
+				lcdstr[lcd] = '\0';
+
+				/* match_str = (char **) &lcdstr; */
+
+				/* Free matchvec. */
+				for (i = 0; i < vector_active(matchvec); i++) {
+					if (vector_slot(matchvec, i))
+						talloc_free(vector_slot(matchvec, i));
+				}
+				vector_free(matchvec);
+
+				/* Make new matchvec. */
+				matchvec = vector_init(INIT_MATCHVEC_SIZE);
+				vector_set(matchvec, lcdstr);
+				match_str = (char **)matchvec->index;
+				vector_only_wrapper_free(matchvec);
+
+				*status = CMD_COMPLETE_MATCH;
+				return match_str;
+			}
+		}
+	}
+
+	match_str = (char **)matchvec->index;
+	vector_only_wrapper_free(matchvec);
+	*status = CMD_COMPLETE_LIST_MATCH;
+	return match_str;
+}
+
+char **cmd_complete_command(vector vline, struct vty *vty, int *status)
+{
+	char **ret;
+
+	if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
+		enum node_type onode;
+		vector shifted_vline;
+		unsigned int index;
+
+		onode = vty->node;
+		vty->node = ENABLE_NODE;
+		/* We can try it on enable node, cos' the vty is authenticated */
+
+		shifted_vline = vector_init(vector_count(vline));
+		/* use memcpy? */
+		for (index = 1; index < vector_active(vline); index++) {
+			vector_set_index(shifted_vline, index - 1,
+					 vector_lookup(vline, index));
+		}
+
+		ret = cmd_complete_command_real(shifted_vline, vty, status);
+
+		vector_free(shifted_vline);
+		vty->node = onode;
+		return ret;
+	}
+
+	return cmd_complete_command_real(vline, vty, status);
+}
+
+/* return parent node */
+/* MUST eventually converge on CONFIG_NODE */
+enum node_type vty_go_parent(struct vty *vty)
+{
+	assert(vty->node > CONFIG_NODE);
+
+	switch (vty->node) {
+	case GSMNET_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+		break;
+	case BTS_NODE:
+		vty->node = GSMNET_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts *bts = vty->index;
+			vty->index = bts->network;
+		}
+		break;
+	case TRX_NODE:
+		vty->node = BTS_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx *trx = vty->index;
+			vty->index = trx->bts;
+		}
+		break;
+	case TS_NODE:
+		vty->node = TRX_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx_ts *ts = vty->index;
+			vty->index = ts->trx;
+		}
+		break;
+	case SUBSCR_NODE:
+		vty->node = VIEW_NODE;
+		subscr_put(vty->index);
+		vty->index = NULL;
+		break;
+	default:
+		vty->node = CONFIG_NODE;
+	}
+
+	return vty->node;
+}
+
+/* Execute command by argument vline vector. */
+static int
+cmd_execute_command_real(vector vline, struct vty *vty,
+			 struct cmd_element **cmd)
+{
+	unsigned int i;
+	unsigned int index;
+	vector cmd_vector;
+	struct cmd_element *cmd_element;
+	struct cmd_element *matched_element;
+	unsigned int matched_count, incomplete_count;
+	int argc;
+	const char *argv[CMD_ARGC_MAX];
+	enum match_type match = 0;
+	int varflag;
+	char *command;
+
+	/* Make copy of command elements. */
+	cmd_vector = vector_copy(cmd_node_vector(cmdvec, vty->node));
+
+	for (index = 0; index < vector_active(vline); index++)
+		if ((command = vector_slot(vline, index))) {
+			int ret;
+
+			match =
+			    cmd_filter_by_completion(command, cmd_vector,
+						     index);
+
+			if (match == vararg_match)
+				break;
+
+			ret =
+			    is_cmd_ambiguous(command, cmd_vector, index, match);
+
+			if (ret == 1) {
+				vector_free(cmd_vector);
+				return CMD_ERR_AMBIGUOUS;
+			} else if (ret == 2) {
+				vector_free(cmd_vector);
+				return CMD_ERR_NO_MATCH;
+			}
+		}
+
+	/* Check matched count. */
+	matched_element = NULL;
+	matched_count = 0;
+	incomplete_count = 0;
+
+	for (i = 0; i < vector_active(cmd_vector); i++)
+		if ((cmd_element = vector_slot(cmd_vector, i))) {
+			if (match == vararg_match
+			    || index >= cmd_element->cmdsize) {
+				matched_element = cmd_element;
+#if 0
+				printf("DEBUG: %s\n", cmd_element->string);
+#endif
+				matched_count++;
+			} else {
+				incomplete_count++;
+			}
+		}
+
+	/* Finish of using cmd_vector. */
+	vector_free(cmd_vector);
+
+	/* To execute command, matched_count must be 1. */
+	if (matched_count == 0) {
+		if (incomplete_count)
+			return CMD_ERR_INCOMPLETE;
+		else
+			return CMD_ERR_NO_MATCH;
+	}
+
+	if (matched_count > 1)
+		return CMD_ERR_AMBIGUOUS;
+
+	/* Argument treatment */
+	varflag = 0;
+	argc = 0;
+
+	for (i = 0; i < vector_active(vline); i++) {
+		if (varflag)
+			argv[argc++] = vector_slot(vline, i);
+		else {
+			vector descvec =
+			    vector_slot(matched_element->strvec, i);
+
+			if (vector_active(descvec) == 1) {
+				struct desc *desc = vector_slot(descvec, 0);
+
+				if (CMD_VARARG(desc->cmd))
+					varflag = 1;
+
+				if (varflag || CMD_VARIABLE(desc->cmd)
+				    || CMD_OPTION(desc->cmd))
+					argv[argc++] = vector_slot(vline, i);
+			} else
+				argv[argc++] = vector_slot(vline, i);
+		}
+
+		if (argc >= CMD_ARGC_MAX)
+			return CMD_ERR_EXEED_ARGC_MAX;
+	}
+
+	/* For vtysh execution. */
+	if (cmd)
+		*cmd = matched_element;
+
+	if (matched_element->daemon)
+		return CMD_SUCCESS_DAEMON;
+
+	/* Execute matched command. */
+	return (*matched_element->func) (matched_element, vty, argc, argv);
+}
+
+int
+cmd_execute_command(vector vline, struct vty *vty, struct cmd_element **cmd,
+		    int vtysh)
+{
+	int ret, saved_ret, tried = 0;
+	enum node_type onode;
+	void *oindex;
+
+	onode = vty->node;
+	oindex = vty->index;
+
+	if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
+		vector shifted_vline;
+		unsigned int index;
+
+		vty->node = ENABLE_NODE;
+		/* We can try it on enable node, cos' the vty is authenticated */
+
+		shifted_vline = vector_init(vector_count(vline));
+		/* use memcpy? */
+		for (index = 1; index < vector_active(vline); index++) {
+			vector_set_index(shifted_vline, index - 1,
+					 vector_lookup(vline, index));
+		}
+
+		ret = cmd_execute_command_real(shifted_vline, vty, cmd);
+
+		vector_free(shifted_vline);
+		vty->node = onode;
+		return ret;
+	}
+
+	saved_ret = ret = cmd_execute_command_real(vline, vty, cmd);
+
+	if (vtysh)
+		return saved_ret;
+
+	/* This assumes all nodes above CONFIG_NODE are childs of CONFIG_NODE */
+	while (ret != CMD_SUCCESS && ret != CMD_WARNING
+	       && vty->node > CONFIG_NODE) {
+		vty_go_parent(vty);
+		ret = cmd_execute_command_real(vline, vty, cmd);
+		tried = 1;
+		if (ret == CMD_SUCCESS || ret == CMD_WARNING) {
+			/* succesfull command, leave the node as is */
+			return ret;
+		}
+	}
+	/* no command succeeded, reset the vty to the original node and
+	   return the error for this node */
+	if (tried) {
+		vty->node = onode;
+		vty->index = oindex;
+	}
+	return saved_ret;
+}
+
+/* Execute command by argument readline. */
+int
+cmd_execute_command_strict(vector vline, struct vty *vty,
+			   struct cmd_element **cmd)
+{
+	unsigned int i;
+	unsigned int index;
+	vector cmd_vector;
+	struct cmd_element *cmd_element;
+	struct cmd_element *matched_element;
+	unsigned int matched_count, incomplete_count;
+	int argc;
+	const char *argv[CMD_ARGC_MAX];
+	int varflag;
+	enum match_type match = 0;
+	char *command;
+
+	/* Make copy of command element */
+	cmd_vector = vector_copy(cmd_node_vector(cmdvec, vty->node));
+
+	for (index = 0; index < vector_active(vline); index++)
+		if ((command = vector_slot(vline, index))) {
+			int ret;
+
+			match = cmd_filter_by_string(vector_slot(vline, index),
+						     cmd_vector, index);
+
+			/* If command meets '.VARARG' then finish matching. */
+			if (match == vararg_match)
+				break;
+
+			ret =
+			    is_cmd_ambiguous(command, cmd_vector, index, match);
+			if (ret == 1) {
+				vector_free(cmd_vector);
+				return CMD_ERR_AMBIGUOUS;
+			}
+			if (ret == 2) {
+				vector_free(cmd_vector);
+				return CMD_ERR_NO_MATCH;
+			}
+		}
+
+	/* Check matched count. */
+	matched_element = NULL;
+	matched_count = 0;
+	incomplete_count = 0;
+	for (i = 0; i < vector_active(cmd_vector); i++)
+		if (vector_slot(cmd_vector, i) != NULL) {
+			cmd_element = vector_slot(cmd_vector, i);
+
+			if (match == vararg_match
+			    || index >= cmd_element->cmdsize) {
+				matched_element = cmd_element;
+				matched_count++;
+			} else
+				incomplete_count++;
+		}
+
+	/* Finish of using cmd_vector. */
+	vector_free(cmd_vector);
+
+	/* To execute command, matched_count must be 1. */
+	if (matched_count == 0) {
+		if (incomplete_count)
+			return CMD_ERR_INCOMPLETE;
+		else
+			return CMD_ERR_NO_MATCH;
+	}
+
+	if (matched_count > 1)
+		return CMD_ERR_AMBIGUOUS;
+
+	/* Argument treatment */
+	varflag = 0;
+	argc = 0;
+
+	for (i = 0; i < vector_active(vline); i++) {
+		if (varflag)
+			argv[argc++] = vector_slot(vline, i);
+		else {
+			vector descvec =
+			    vector_slot(matched_element->strvec, i);
+
+			if (vector_active(descvec) == 1) {
+				struct desc *desc = vector_slot(descvec, 0);
+
+				if (CMD_VARARG(desc->cmd))
+					varflag = 1;
+
+				if (varflag || CMD_VARIABLE(desc->cmd)
+				    || CMD_OPTION(desc->cmd))
+					argv[argc++] = vector_slot(vline, i);
+			} else
+				argv[argc++] = vector_slot(vline, i);
+		}
+
+		if (argc >= CMD_ARGC_MAX)
+			return CMD_ERR_EXEED_ARGC_MAX;
+	}
+
+	/* For vtysh execution. */
+	if (cmd)
+		*cmd = matched_element;
+
+	if (matched_element->daemon)
+		return CMD_SUCCESS_DAEMON;
+
+	/* Now execute matched command */
+	return (*matched_element->func) (matched_element, vty, argc, argv);
+}
+
+/* Configration make from file. */
+int config_from_file(struct vty *vty, FILE * fp)
+{
+	int ret;
+	vector vline;
+
+	while (fgets(vty->buf, VTY_BUFSIZ, fp)) {
+		vline = cmd_make_strvec(vty->buf);
+
+		/* In case of comment line */
+		if (vline == NULL)
+			continue;
+		/* Execute configuration command : this is strict match */
+		ret = cmd_execute_command_strict(vline, vty, NULL);
+
+		/* Try again with setting node to CONFIG_NODE */
+		while (ret != CMD_SUCCESS && ret != CMD_WARNING
+		       && ret != CMD_ERR_NOTHING_TODO
+		       && vty->node != CONFIG_NODE) {
+			vty_go_parent(vty);
+			ret = cmd_execute_command_strict(vline, vty, NULL);
+		}
+
+		cmd_free_strvec(vline);
+
+		if (ret != CMD_SUCCESS && ret != CMD_WARNING
+		    && ret != CMD_ERR_NOTHING_TODO)
+			return ret;
+	}
+	return CMD_SUCCESS;
+}
+
+/* Configration from terminal */
+DEFUN(config_terminal,
+      config_terminal_cmd,
+      "configure terminal",
+      "Configuration from vty interface\n" "Configuration terminal\n")
+{
+	if (vty_config_lock(vty))
+		vty->node = CONFIG_NODE;
+	else {
+		vty_out(vty, "VTY configuration is locked by other VTY%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	return CMD_SUCCESS;
+}
+
+/* Enable command */
+DEFUN(enable, config_enable_cmd, "enable", "Turn on privileged mode command\n")
+{
+	/* If enable password is NULL, change to ENABLE_NODE */
+	if ((host.enable == NULL && host.enable_encrypt == NULL) ||
+	    vty->type == VTY_SHELL_SERV)
+		vty->node = ENABLE_NODE;
+	else
+		vty->node = AUTH_ENABLE_NODE;
+
+	return CMD_SUCCESS;
+}
+
+/* Disable command */
+DEFUN(disable,
+      config_disable_cmd, "disable", "Turn off privileged mode command\n")
+{
+	if (vty->node == ENABLE_NODE)
+		vty->node = VIEW_NODE;
+	return CMD_SUCCESS;
+}
+
+/* Down vty node level. */
+gDEFUN(config_exit,
+      config_exit_cmd, "exit", "Exit current mode and down to previous mode\n")
+{
+	switch (vty->node) {
+	case GSMNET_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+		break;
+	case BTS_NODE:
+		vty->node = GSMNET_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts *bts = vty->index;
+			vty->index = bts->network;
+		}
+		break;
+	case TRX_NODE:
+		vty->node = BTS_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx *trx = vty->index;
+			vty->index = trx->bts;
+		}
+		break;
+	case TS_NODE:
+		vty->node = TRX_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx_ts *ts = vty->index;
+			vty->index = ts->trx;
+		}
+		break;
+	case SUBSCR_NODE:
+		vty->node = VIEW_NODE;
+		subscr_put(vty->index);
+		vty->index = NULL;
+		break;
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		if (0)		//vty_shell (vty))
+			exit(0);
+		else
+			vty->status = VTY_CLOSE;
+		break;
+	case CONFIG_NODE:
+		vty->node = ENABLE_NODE;
+		vty_config_unlock(vty);
+		break;
+	case VTY_NODE:
+		vty->node = CONFIG_NODE;
+		break;
+	case MGCP_NODE:
+	case GBPROXY_NODE:
+	case SGSN_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+	default:
+		break;
+	}
+	return CMD_SUCCESS;
+}
+
+/* quit is alias of exit. */
+gALIAS(config_exit,
+      config_quit_cmd, "quit", "Exit current mode and down to previous mode\n")
+
+/* End of configuration. */
+    gDEFUN(config_end,
+      config_end_cmd, "end", "End current mode and change to enable mode.")
+{
+	switch (vty->node) {
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		/* Nothing to do. */
+		break;
+	case CONFIG_NODE:
+	case VTY_NODE:
+		vty_config_unlock(vty);
+		vty->node = ENABLE_NODE;
+		break;
+	default:
+		break;
+	}
+	return CMD_SUCCESS;
+}
+
+/* Show version. */
+DEFUN(show_version,
+      show_version_cmd, "show version", SHOW_STR "Displays program version\n")
+{
+	vty_out(vty, "%s %s (%s).%s", QUAGGA_PROGNAME, QUAGGA_VERSION,
+		host.name ? host.name : "", VTY_NEWLINE);
+	vty_out(vty, "%s%s", QUAGGA_COPYRIGHT, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+/* Help display function for all node. */
+gDEFUN(config_help,
+      config_help_cmd, "help", "Description of the interactive help system\n")
+{
+	vty_out(vty,
+		"This VTY provides advanced help features.  When you need help,%s\
+anytime at the command line please press '?'.%s\
+%s\
+If nothing matches, the help list will be empty and you must backup%s\
+ until entering a '?' shows the available options.%s\
+Two styles of help are provided:%s\
+1. Full help is available when you are ready to enter a%s\
+command argument (e.g. 'show ?') and describes each possible%s\
+argument.%s\
+2. Partial help is provided when an abbreviated argument is entered%s\
+   and you want to know what arguments match the input%s\
+   (e.g. 'show me?'.)%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
+		VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+/* Help display function for all node. */
+gDEFUN(config_list, config_list_cmd, "list", "Print command list\n")
+{
+	unsigned int i;
+	struct cmd_node *cnode = vector_slot(cmdvec, vty->node);
+	struct cmd_element *cmd;
+
+	for (i = 0; i < vector_active(cnode->cmd_vector); i++)
+		if ((cmd = vector_slot(cnode->cmd_vector, i)) != NULL
+		    && !(cmd->attr == CMD_ATTR_DEPRECATED
+			 || cmd->attr == CMD_ATTR_HIDDEN))
+			vty_out(vty, "  %s%s", cmd->string, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+/* Write current configuration into file. */
+DEFUN(config_write_file,
+      config_write_file_cmd,
+      "write file",
+      "Write running configuration to memory, network, or terminal\n"
+      "Write to configuration file\n")
+{
+	unsigned int i;
+	int fd;
+	struct cmd_node *node;
+	char *config_file;
+	char *config_file_tmp = NULL;
+	char *config_file_sav = NULL;
+	struct vty *file_vty;
+
+	/* Check and see if we are operating under vtysh configuration */
+	if (host.config == NULL) {
+		vty_out(vty, "Can't save to configuration file, using vtysh.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Get filename. */
+	config_file = host.config;
+
+	config_file_sav =
+	    _talloc_zero(tall_vty_cmd_ctx,
+			 strlen(config_file) + strlen(CONF_BACKUP_EXT) + 1,
+			 "config_file_sav");
+	strcpy(config_file_sav, config_file);
+	strcat(config_file_sav, CONF_BACKUP_EXT);
+
+	config_file_tmp = _talloc_zero(tall_vty_cmd_ctx, strlen(config_file) + 8,
+					"config_file_tmp");
+	sprintf(config_file_tmp, "%s.XXXXXX", config_file);
+
+	/* Open file to configuration write. */
+	fd = mkstemp(config_file_tmp);
+	if (fd < 0) {
+		vty_out(vty, "Can't open configuration file %s.%s",
+			config_file_tmp, VTY_NEWLINE);
+		talloc_free(config_file_tmp);
+		talloc_free(config_file_sav);
+		return CMD_WARNING;
+	}
+
+	/* Make vty for configuration file. */
+	file_vty = vty_new();
+	file_vty->fd = fd;
+	file_vty->type = VTY_FILE;
+
+	/* Config file header print. */
+	vty_out(file_vty, "!\n! OpenBSC configuration saved from vty\n!   ");
+	//vty_time_print (file_vty, 1);
+	vty_out(file_vty, "!\n");
+
+	for (i = 0; i < vector_active(cmdvec); i++)
+		if ((node = vector_slot(cmdvec, i)) && node->func) {
+			if ((*node->func) (file_vty))
+				vty_out(file_vty, "!\n");
+		}
+	vty_close(file_vty);
+
+	if (unlink(config_file_sav) != 0)
+		if (errno != ENOENT) {
+			vty_out(vty,
+				"Can't unlink backup configuration file %s.%s",
+				config_file_sav, VTY_NEWLINE);
+			talloc_free(config_file_sav);
+			talloc_free(config_file_tmp);
+			unlink(config_file_tmp);
+			return CMD_WARNING;
+		}
+	if (link(config_file, config_file_sav) != 0) {
+		vty_out(vty, "Can't backup old configuration file %s.%s",
+			config_file_sav, VTY_NEWLINE);
+		talloc_free(config_file_sav);
+		talloc_free(config_file_tmp);
+		unlink(config_file_tmp);
+		return CMD_WARNING;
+	}
+	sync();
+	if (unlink(config_file) != 0) {
+		vty_out(vty, "Can't unlink configuration file %s.%s",
+			config_file, VTY_NEWLINE);
+		talloc_free(config_file_sav);
+		talloc_free(config_file_tmp);
+		unlink(config_file_tmp);
+		return CMD_WARNING;
+	}
+	if (link(config_file_tmp, config_file) != 0) {
+		vty_out(vty, "Can't save configuration file %s.%s", config_file,
+			VTY_NEWLINE);
+		talloc_free(config_file_sav);
+		talloc_free(config_file_tmp);
+		unlink(config_file_tmp);
+		return CMD_WARNING;
+	}
+	unlink(config_file_tmp);
+	sync();
+
+	talloc_free(config_file_sav);
+	talloc_free(config_file_tmp);
+
+	if (chmod(config_file, 0666 & ~CONFIGFILE_MASK) != 0) {
+		vty_out(vty, "Can't chmod configuration file %s: %s (%d).%s",
+			config_file, strerror(errno), errno, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty_out(vty, "Configuration saved to %s%s", config_file, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+ALIAS(config_write_file,
+      config_write_cmd,
+      "write", "Write running configuration to memory, network, or terminal\n")
+
+    ALIAS(config_write_file,
+      config_write_memory_cmd,
+      "write memory",
+      "Write running configuration to memory, network, or terminal\n"
+      "Write configuration to the file (same as write file)\n")
+
+    ALIAS(config_write_file,
+      copy_runningconfig_startupconfig_cmd,
+      "copy running-config startup-config",
+      "Copy configuration\n"
+      "Copy running config to... \n"
+      "Copy running config to startup config (same as write file)\n")
+
+/* Write current configuration into the terminal. */
+    DEFUN(config_write_terminal,
+      config_write_terminal_cmd,
+      "write terminal",
+      "Write running configuration to memory, network, or terminal\n"
+      "Write to terminal\n")
+{
+	unsigned int i;
+	struct cmd_node *node;
+
+	if (vty->type == VTY_SHELL_SERV) {
+		for (i = 0; i < vector_active(cmdvec); i++)
+			if ((node = vector_slot(cmdvec, i)) && node->func
+			    && node->vtysh) {
+				if ((*node->func) (vty))
+					vty_out(vty, "!%s", VTY_NEWLINE);
+			}
+	} else {
+		vty_out(vty, "%sCurrent configuration:%s", VTY_NEWLINE,
+			VTY_NEWLINE);
+		vty_out(vty, "!%s", VTY_NEWLINE);
+
+		for (i = 0; i < vector_active(cmdvec); i++)
+			if ((node = vector_slot(cmdvec, i)) && node->func) {
+				if ((*node->func) (vty))
+					vty_out(vty, "!%s", VTY_NEWLINE);
+			}
+		vty_out(vty, "end%s", VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
+
+/* Write current configuration into the terminal. */
+ALIAS(config_write_terminal,
+      show_running_config_cmd,
+      "show running-config", SHOW_STR "running configuration\n")
+
+/* Write startup configuration into the terminal. */
+    DEFUN(show_startup_config,
+      show_startup_config_cmd,
+      "show startup-config", SHOW_STR "Contentes of startup configuration\n")
+{
+	char buf[BUFSIZ];
+	FILE *confp;
+
+	confp = fopen(host.config, "r");
+	if (confp == NULL) {
+		vty_out(vty, "Can't open configuration file [%s]%s",
+			host.config, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	while (fgets(buf, BUFSIZ, confp)) {
+		char *cp = buf;
+
+		while (*cp != '\r' && *cp != '\n' && *cp != '\0')
+			cp++;
+		*cp = '\0';
+
+		vty_out(vty, "%s%s", buf, VTY_NEWLINE);
+	}
+
+	fclose(confp);
+
+	return CMD_SUCCESS;
+}
+
+/* Hostname configuration */
+DEFUN(config_hostname,
+      hostname_cmd,
+      "hostname WORD",
+      "Set system's network name\n" "This system's network name\n")
+{
+	if (!isalpha((int)*argv[0])) {
+		vty_out(vty, "Please specify string starting with alphabet%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (host.name)
+		talloc_free(host.name);
+
+	host.name = talloc_strdup(tall_vty_cmd_ctx, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_no_hostname,
+      no_hostname_cmd,
+      "no hostname [HOSTNAME]",
+      NO_STR "Reset system's network name\n" "Host name of this router\n")
+{
+	if (host.name)
+		talloc_free(host.name);
+	host.name = NULL;
+	return CMD_SUCCESS;
+}
+
+/* VTY interface password set. */
+DEFUN(config_password, password_cmd,
+      "password (8|) WORD",
+      "Assign the terminal connection password\n"
+      "Specifies a HIDDEN password will follow\n"
+      "dummy string \n" "The HIDDEN line password string\n")
+{
+	/* Argument check. */
+	if (argc == 0) {
+		vty_out(vty, "Please specify password.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (argc == 2) {
+		if (*argv[0] == '8') {
+			if (host.password)
+				talloc_free(host.password);
+			host.password = NULL;
+			if (host.password_encrypt)
+				talloc_free(host.password_encrypt);
+			host.password_encrypt = talloc_strdup(tall_vty_cmd_ctx, argv[1]);
+			return CMD_SUCCESS;
+		} else {
+			vty_out(vty, "Unknown encryption type.%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	if (!isalnum((int)*argv[0])) {
+		vty_out(vty,
+			"Please specify string starting with alphanumeric%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (host.password)
+		talloc_free(host.password);
+	host.password = NULL;
+
+#ifdef VTY_CRYPT_PW
+	if (host.encrypt) {
+		if (host.password_encrypt)
+			talloc_free(host.password_encrypt);
+		host.password_encrypt = talloc_strdup(tall_vty_cmd_ctx, zencrypt(argv[0]));
+	} else
+#endif
+		host.password = talloc_strdup(tall_vty_cmd_ctx, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+ALIAS(config_password, password_text_cmd,
+      "password LINE",
+      "Assign the terminal connection password\n"
+      "The UNENCRYPTED (cleartext) line password\n")
+
+/* VTY enable password set. */
+    DEFUN(config_enable_password, enable_password_cmd,
+      "enable password (8|) WORD",
+      "Modify enable password parameters\n"
+      "Assign the privileged level password\n"
+      "Specifies a HIDDEN password will follow\n"
+      "dummy string \n" "The HIDDEN 'enable' password string\n")
+{
+	/* Argument check. */
+	if (argc == 0) {
+		vty_out(vty, "Please specify password.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Crypt type is specified. */
+	if (argc == 2) {
+		if (*argv[0] == '8') {
+			if (host.enable)
+				talloc_free(host.enable);
+			host.enable = NULL;
+
+			if (host.enable_encrypt)
+				talloc_free(host.enable_encrypt);
+			host.enable_encrypt = talloc_strdup(tall_vty_cmd_ctx, argv[1]);
+
+			return CMD_SUCCESS;
+		} else {
+			vty_out(vty, "Unknown encryption type.%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	if (!isalnum((int)*argv[0])) {
+		vty_out(vty,
+			"Please specify string starting with alphanumeric%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (host.enable)
+		talloc_free(host.enable);
+	host.enable = NULL;
+
+	/* Plain password input. */
+#ifdef VTY_CRYPT_PW
+	if (host.encrypt) {
+		if (host.enable_encrypt)
+			talloc_free(host.enable_encrypt);
+		host.enable_encrypt = talloc_strdup(tall_vty_cmd_ctx, zencrypt(argv[0]));
+	} else
+#endif
+		host.enable = talloc_strdup(tall_vty_cmd_ctx, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+ALIAS(config_enable_password,
+      enable_password_text_cmd,
+      "enable password LINE",
+      "Modify enable password parameters\n"
+      "Assign the privileged level password\n"
+      "The UNENCRYPTED (cleartext) 'enable' password\n")
+
+/* VTY enable password delete. */
+    DEFUN(no_config_enable_password, no_enable_password_cmd,
+      "no enable password",
+      NO_STR
+      "Modify enable password parameters\n"
+      "Assign the privileged level password\n")
+{
+	if (host.enable)
+		talloc_free(host.enable);
+	host.enable = NULL;
+
+	if (host.enable_encrypt)
+		talloc_free(host.enable_encrypt);
+	host.enable_encrypt = NULL;
+
+	return CMD_SUCCESS;
+}
+
+#ifdef VTY_CRYPT_PW
+DEFUN(service_password_encrypt,
+      service_password_encrypt_cmd,
+      "service password-encryption",
+      "Set up miscellaneous service\n" "Enable encrypted passwords\n")
+{
+	if (host.encrypt)
+		return CMD_SUCCESS;
+
+	host.encrypt = 1;
+
+	if (host.password) {
+		if (host.password_encrypt)
+			talloc_free(host.password_encrypt);
+		host.password_encrypt = talloc_strdup(tall_vty_cmd_ctx, zencrypt(host.password));
+	}
+	if (host.enable) {
+		if (host.enable_encrypt)
+			talloc_free(host.enable_encrypt);
+		host.enable_encrypt = talloc_strdup(tall_vty_cmd_ctx, zencrypt(host.enable));
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_service_password_encrypt,
+      no_service_password_encrypt_cmd,
+      "no service password-encryption",
+      NO_STR "Set up miscellaneous service\n" "Enable encrypted passwords\n")
+{
+	if (!host.encrypt)
+		return CMD_SUCCESS;
+
+	host.encrypt = 0;
+
+	if (host.password_encrypt)
+		talloc_free(host.password_encrypt);
+	host.password_encrypt = NULL;
+
+	if (host.enable_encrypt)
+		talloc_free(host.enable_encrypt);
+	host.enable_encrypt = NULL;
+
+	return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(config_terminal_length, config_terminal_length_cmd,
+      "terminal length <0-512>",
+      "Set terminal line parameters\n"
+      "Set number of lines on a screen\n"
+      "Number of lines on screen (0 for no pausing)\n")
+{
+	int lines;
+	char *endptr = NULL;
+
+	lines = strtol(argv[0], &endptr, 10);
+	if (lines < 0 || lines > 512 || *endptr != '\0') {
+		vty_out(vty, "length is malformed%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	vty->lines = lines;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_terminal_no_length, config_terminal_no_length_cmd,
+      "terminal no length",
+      "Set terminal line parameters\n"
+      NO_STR "Set number of lines on a screen\n")
+{
+	vty->lines = -1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(service_terminal_length, service_terminal_length_cmd,
+      "service terminal-length <0-512>",
+      "Set up miscellaneous service\n"
+      "System wide terminal length configuration\n"
+      "Number of lines of VTY (0 means no line control)\n")
+{
+	int lines;
+	char *endptr = NULL;
+
+	lines = strtol(argv[0], &endptr, 10);
+	if (lines < 0 || lines > 512 || *endptr != '\0') {
+		vty_out(vty, "length is malformed%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	host.lines = lines;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_service_terminal_length, no_service_terminal_length_cmd,
+      "no service terminal-length [<0-512>]",
+      NO_STR
+      "Set up miscellaneous service\n"
+      "System wide terminal length configuration\n"
+      "Number of lines of VTY (0 means no line control)\n")
+{
+	host.lines = -1;
+	return CMD_SUCCESS;
+}
+
+DEFUN_HIDDEN(do_echo,
+	     echo_cmd,
+	     "echo .MESSAGE",
+	     "Echo a message back to the vty\n" "The message to echo\n")
+{
+	char *message;
+
+	vty_out(vty, "%s%s",
+		((message =
+		  argv_concat(argv, argc, 0)) ? message : ""), VTY_NEWLINE);
+	if (message)
+		talloc_free(message);
+	return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(config_logmsg,
+      config_logmsg_cmd,
+      "logmsg " LOG_LEVELS " .MESSAGE",
+      "Send a message to enabled logging destinations\n"
+      LOG_LEVEL_DESC "The message to send\n")
+{
+	int level;
+	char *message;
+
+	if ((level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+
+	zlog(NULL, level,
+	     ((message = argv_concat(argv, argc, 1)) ? message : ""));
+	if (message)
+		talloc_free(message);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_logging,
+      show_logging_cmd,
+      "show logging", SHOW_STR "Show current logging configuration\n")
+{
+	struct zlog *zl = zlog_default;
+
+	vty_out(vty, "Syslog logging: ");
+	if (zl->maxlvl[ZLOG_DEST_SYSLOG] == ZLOG_DISABLED)
+		vty_out(vty, "disabled");
+	else
+		vty_out(vty, "level %s, facility %s, ident %s",
+			zlog_priority[zl->maxlvl[ZLOG_DEST_SYSLOG]],
+			facility_name(zl->facility), zl->ident);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	vty_out(vty, "Stdout logging: ");
+	if (zl->maxlvl[ZLOG_DEST_STDOUT] == ZLOG_DISABLED)
+		vty_out(vty, "disabled");
+	else
+		vty_out(vty, "level %s",
+			zlog_priority[zl->maxlvl[ZLOG_DEST_STDOUT]]);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	vty_out(vty, "Monitor logging: ");
+	if (zl->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED)
+		vty_out(vty, "disabled");
+	else
+		vty_out(vty, "level %s",
+			zlog_priority[zl->maxlvl[ZLOG_DEST_MONITOR]]);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	vty_out(vty, "File logging: ");
+	if ((zl->maxlvl[ZLOG_DEST_FILE] == ZLOG_DISABLED) || !zl->fp)
+		vty_out(vty, "disabled");
+	else
+		vty_out(vty, "level %s, filename %s",
+			zlog_priority[zl->maxlvl[ZLOG_DEST_FILE]],
+			zl->filename);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	vty_out(vty, "Protocol name: %s%s",
+		zlog_proto_names[zl->protocol], VTY_NEWLINE);
+	vty_out(vty, "Record priority: %s%s",
+		(zl->record_priority ? "enabled" : "disabled"), VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_stdout,
+      config_log_stdout_cmd,
+      "log stdout", "Logging control\n" "Set stdout logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_STDOUT, zlog_default->default_lvl);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_stdout_level,
+      config_log_stdout_level_cmd,
+      "log stdout " LOG_LEVELS,
+      "Logging control\n" "Set stdout logging level\n" LOG_LEVEL_DESC)
+{
+	int level;
+
+	if ((level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+	zlog_set_level(NULL, ZLOG_DEST_STDOUT, level);
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_stdout,
+      no_config_log_stdout_cmd,
+      "no log stdout [LEVEL]",
+      NO_STR "Logging control\n" "Cancel logging to stdout\n" "Logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_STDOUT, ZLOG_DISABLED);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_monitor,
+      config_log_monitor_cmd,
+      "log monitor",
+      "Logging control\n" "Set terminal line (monitor) logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_MONITOR, zlog_default->default_lvl);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_monitor_level,
+      config_log_monitor_level_cmd,
+      "log monitor " LOG_LEVELS,
+      "Logging control\n"
+      "Set terminal line (monitor) logging level\n" LOG_LEVEL_DESC)
+{
+	int level;
+
+	if ((level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+	zlog_set_level(NULL, ZLOG_DEST_MONITOR, level);
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_monitor,
+      no_config_log_monitor_cmd,
+      "no log monitor [LEVEL]",
+      NO_STR
+      "Logging control\n"
+      "Disable terminal line (monitor) logging\n" "Logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_MONITOR, ZLOG_DISABLED);
+	return CMD_SUCCESS;
+}
+
+static int set_log_file(struct vty *vty, const char *fname, int loglevel)
+{
+	int ret;
+	char *p = NULL;
+	const char *fullpath;
+
+	/* Path detection. */
+	if (!IS_DIRECTORY_SEP(*fname)) {
+		char cwd[MAXPATHLEN + 1];
+		cwd[MAXPATHLEN] = '\0';
+
+		if (getcwd(cwd, MAXPATHLEN) == NULL) {
+			zlog_err("config_log_file: Unable to alloc mem!");
+			return CMD_WARNING;
+		}
+
+		if ((p = _talloc_zero(tall_vcmd_ctx,
+				      strlen(cwd) + strlen(fname) + 2),
+				      "set_log_file")
+		    == NULL) {
+			zlog_err("config_log_file: Unable to alloc mem!");
+			return CMD_WARNING;
+		}
+		sprintf(p, "%s/%s", cwd, fname);
+		fullpath = p;
+	} else
+		fullpath = fname;
+
+	ret = zlog_set_file(NULL, fullpath, loglevel);
+
+	if (p)
+		talloc_free(p);
+
+	if (!ret) {
+		vty_out(vty, "can't open logfile %s\n", fname);
+		return CMD_WARNING;
+	}
+
+	if (host.logfile)
+		talloc_free(host.logfile);
+
+	host.logfile = talloc_strdup(tall_vty_cmd_ctx, fname);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_file,
+      config_log_file_cmd,
+      "log file FILENAME",
+      "Logging control\n" "Logging to file\n" "Logging filename\n")
+{
+	return set_log_file(vty, argv[0], zlog_default->default_lvl);
+}
+
+DEFUN(config_log_file_level,
+      config_log_file_level_cmd,
+      "log file FILENAME " LOG_LEVELS,
+      "Logging control\n"
+      "Logging to file\n" "Logging filename\n" LOG_LEVEL_DESC)
+{
+	int level;
+
+	if ((level = level_match(argv[1])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+	return set_log_file(vty, argv[0], level);
+}
+
+DEFUN(no_config_log_file,
+      no_config_log_file_cmd,
+      "no log file [FILENAME]",
+      NO_STR
+      "Logging control\n" "Cancel logging to file\n" "Logging file name\n")
+{
+	zlog_reset_file(NULL);
+
+	if (host.logfile)
+		talloc_free(host.logfile);
+
+	host.logfile = NULL;
+
+	return CMD_SUCCESS;
+}
+
+ALIAS(no_config_log_file,
+      no_config_log_file_level_cmd,
+      "no log file FILENAME LEVEL",
+      NO_STR
+      "Logging control\n"
+      "Cancel logging to file\n" "Logging file name\n" "Logging level\n")
+
+    DEFUN(config_log_syslog,
+      config_log_syslog_cmd,
+      "log syslog", "Logging control\n" "Set syslog logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_SYSLOG, zlog_default->default_lvl);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_syslog_level,
+      config_log_syslog_level_cmd,
+      "log syslog " LOG_LEVELS,
+      "Logging control\n" "Set syslog logging level\n" LOG_LEVEL_DESC)
+{
+	int level;
+
+	if ((level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+	zlog_set_level(NULL, ZLOG_DEST_SYSLOG, level);
+	return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(config_log_syslog_facility,
+		 config_log_syslog_facility_cmd,
+		 "log syslog facility " LOG_FACILITIES,
+		 "Logging control\n"
+		 "Logging goes to syslog\n"
+		 "(Deprecated) Facility parameter for syslog messages\n"
+		 LOG_FACILITY_DESC)
+{
+	int facility;
+
+	if ((facility = facility_match(argv[0])) < 0)
+		return CMD_ERR_NO_MATCH;
+
+	zlog_set_level(NULL, ZLOG_DEST_SYSLOG, zlog_default->default_lvl);
+	zlog_default->facility = facility;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_syslog,
+      no_config_log_syslog_cmd,
+      "no log syslog [LEVEL]",
+      NO_STR "Logging control\n" "Cancel logging to syslog\n" "Logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_SYSLOG, ZLOG_DISABLED);
+	return CMD_SUCCESS;
+}
+
+ALIAS(no_config_log_syslog,
+      no_config_log_syslog_facility_cmd,
+      "no log syslog facility " LOG_FACILITIES,
+      NO_STR
+      "Logging control\n"
+      "Logging goes to syslog\n"
+      "Facility parameter for syslog messages\n" LOG_FACILITY_DESC)
+
+    DEFUN(config_log_facility,
+      config_log_facility_cmd,
+      "log facility " LOG_FACILITIES,
+      "Logging control\n"
+      "Facility parameter for syslog messages\n" LOG_FACILITY_DESC)
+{
+	int facility;
+
+	if ((facility = facility_match(argv[0])) < 0)
+		return CMD_ERR_NO_MATCH;
+	zlog_default->facility = facility;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_facility,
+      no_config_log_facility_cmd,
+      "no log facility [FACILITY]",
+      NO_STR
+      "Logging control\n"
+      "Reset syslog facility to default (daemon)\n" "Syslog facility\n")
+{
+	zlog_default->facility = LOG_DAEMON;
+	return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(config_log_trap,
+		 config_log_trap_cmd,
+		 "log trap " LOG_LEVELS,
+		 "Logging control\n"
+		 "(Deprecated) Set logging level and default for all destinations\n"
+		 LOG_LEVEL_DESC)
+{
+	int new_level;
+	int i;
+
+	if ((new_level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+
+	zlog_default->default_lvl = new_level;
+	for (i = 0; i < ZLOG_NUM_DESTS; i++)
+		if (zlog_default->maxlvl[i] != ZLOG_DISABLED)
+			zlog_default->maxlvl[i] = new_level;
+	return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(no_config_log_trap,
+		 no_config_log_trap_cmd,
+		 "no log trap [LEVEL]",
+		 NO_STR
+		 "Logging control\n"
+		 "Permit all logging information\n" "Logging level\n")
+{
+	zlog_default->default_lvl = LOG_DEBUG;
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_record_priority,
+      config_log_record_priority_cmd,
+      "log record-priority",
+      "Logging control\n"
+      "Log the priority of the message within the message\n")
+{
+	zlog_default->record_priority = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_record_priority,
+      no_config_log_record_priority_cmd,
+      "no log record-priority",
+      NO_STR
+      "Logging control\n"
+      "Do not log the priority of the message within the message\n")
+{
+	zlog_default->record_priority = 0;
+	return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(banner_motd_file,
+      banner_motd_file_cmd,
+      "banner motd file [FILE]",
+      "Set banner\n" "Banner for motd\n" "Banner from a file\n" "Filename\n")
+{
+	if (host.motdfile)
+		talloc_free(host.motdfile);
+	host.motdfile = talloc_strdup(tall_vty_cmd_ctx, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(banner_motd_default,
+      banner_motd_default_cmd,
+      "banner motd default",
+      "Set banner string\n" "Strings for motd\n" "Default string\n")
+{
+	host.motd = default_motd;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_banner_motd,
+      no_banner_motd_cmd,
+      "no banner motd", NO_STR "Set banner string\n" "Strings for motd\n")
+{
+	host.motd = NULL;
+	if (host.motdfile)
+		talloc_free(host.motdfile);
+	host.motdfile = NULL;
+	return CMD_SUCCESS;
+}
+
+/* Set config filename.  Called from vty.c */
+void host_config_set(const char *filename)
+{
+	host.config = talloc_strdup(tall_vty_cmd_ctx, filename);
+}
+
+void install_default(enum node_type node)
+{
+	install_element(node, &config_exit_cmd);
+	install_element(node, &config_quit_cmd);
+	install_element(node, &config_end_cmd);
+	install_element(node, &config_help_cmd);
+	install_element(node, &config_list_cmd);
+
+	install_element(node, &config_write_terminal_cmd);
+	install_element(node, &config_write_file_cmd);
+	install_element(node, &config_write_memory_cmd);
+	install_element(node, &config_write_cmd);
+	install_element(node, &show_running_config_cmd);
+}
+
+/* Initialize command interface. Install basic nodes and commands. */
+void cmd_init(int terminal)
+{
+	/* Allocate initial top vector of commands. */
+	cmdvec = vector_init(VECTOR_MIN_SIZE);
+
+	/* Default host value settings. */
+	host.name = NULL;
+	host.password = NULL;
+	host.enable = NULL;
+	host.logfile = NULL;
+	host.config = NULL;
+	host.lines = -1;
+	host.motd = default_motd;
+	host.motdfile = NULL;
+
+	/* Install top nodes. */
+	install_node(&view_node, NULL);
+	install_node(&enable_node, NULL);
+	install_node(&auth_node, NULL);
+	install_node(&auth_enable_node, NULL);
+	install_node(&config_node, config_write_host);
+
+	/* Each node's basic commands. */
+	install_element(VIEW_NODE, &show_version_cmd);
+	if (terminal) {
+		install_element(VIEW_NODE, &config_list_cmd);
+		install_element(VIEW_NODE, &config_exit_cmd);
+		install_element(VIEW_NODE, &config_quit_cmd);
+		install_element(VIEW_NODE, &config_help_cmd);
+		install_element(VIEW_NODE, &config_enable_cmd);
+		install_element(VIEW_NODE, &config_terminal_length_cmd);
+		install_element(VIEW_NODE, &config_terminal_no_length_cmd);
+		install_element(VIEW_NODE, &echo_cmd);
+	}
+
+	if (terminal) {
+		install_default(ENABLE_NODE);
+		install_element(ENABLE_NODE, &config_disable_cmd);
+		install_element(ENABLE_NODE, &config_terminal_cmd);
+		install_element (ENABLE_NODE, &copy_runningconfig_startupconfig_cmd);
+	}
+	install_element (ENABLE_NODE, &show_startup_config_cmd);
+	install_element(ENABLE_NODE, &show_version_cmd);
+
+	if (terminal) {
+		install_element(ENABLE_NODE, &config_terminal_length_cmd);
+		install_element(ENABLE_NODE, &config_terminal_no_length_cmd);
+		install_element(ENABLE_NODE, &echo_cmd);
+
+		install_default(CONFIG_NODE);
+	}
+
+	install_element(CONFIG_NODE, &hostname_cmd);
+	install_element(CONFIG_NODE, &no_hostname_cmd);
+
+	if (terminal) {
+		install_element(CONFIG_NODE, &password_cmd);
+		install_element(CONFIG_NODE, &password_text_cmd);
+		install_element(CONFIG_NODE, &enable_password_cmd);
+		install_element(CONFIG_NODE, &enable_password_text_cmd);
+		install_element(CONFIG_NODE, &no_enable_password_cmd);
+
+#ifdef VTY_CRYPT_PW
+		install_element(CONFIG_NODE, &service_password_encrypt_cmd);
+		install_element(CONFIG_NODE, &no_service_password_encrypt_cmd);
+#endif
+		install_element(CONFIG_NODE, &banner_motd_default_cmd);
+		install_element(CONFIG_NODE, &banner_motd_file_cmd);
+		install_element(CONFIG_NODE, &no_banner_motd_cmd);
+		install_element(CONFIG_NODE, &service_terminal_length_cmd);
+		install_element(CONFIG_NODE, &no_service_terminal_length_cmd);
+
+	}
+	srand(time(NULL));
+}
diff --git a/openbsc/src/vty/vector.c b/openbsc/src/vty/vector.c
new file mode 100644
index 0000000..db47ae5
--- /dev/null
+++ b/openbsc/src/vty/vector.c
@@ -0,0 +1,192 @@
+/* Generic vector interface routine
+ * Copyright (C) 1997 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <vty/vector.h>
+#include <vty/vty.h>
+#include <osmocore/talloc.h>
+#include <memory.h>
+
+void *tall_vty_vec_ctx;
+
+/* Initialize vector : allocate memory and return vector. */
+vector vector_init(unsigned int size)
+{
+	vector v = talloc_zero(tall_vty_vec_ctx, struct _vector);
+	if (!v)
+		return NULL;
+
+	/* allocate at least one slot */
+	if (size == 0)
+		size = 1;
+
+	v->alloced = size;
+	v->active = 0;
+	v->index = _talloc_zero(tall_vty_vec_ctx, sizeof(void *) * size,
+				"vector_init:index");
+	if (!v->index) {
+		talloc_free(v);
+		return NULL;
+	}
+	return v;
+}
+
+void vector_only_wrapper_free(vector v)
+{
+	talloc_free(v);
+}
+
+void vector_only_index_free(void *index)
+{
+	talloc_free(index);
+}
+
+void vector_free(vector v)
+{
+	talloc_free(v->index);
+	talloc_free(v);
+}
+
+vector vector_copy(vector v)
+{
+	unsigned int size;
+	vector new = talloc_zero(tall_vty_vec_ctx, struct _vector);
+	if (!new)
+		return NULL;
+
+	new->active = v->active;
+	new->alloced = v->alloced;
+
+	size = sizeof(void *) * (v->alloced);
+	new->index = _talloc_zero(tall_vty_vec_ctx, size, "vector_copy:index");
+	if (!new->index) {
+		talloc_free(new);
+		return NULL;
+	}
+	memcpy(new->index, v->index, size);
+
+	return new;
+}
+
+/* Check assigned index, and if it runs short double index pointer */
+void vector_ensure(vector v, unsigned int num)
+{
+	if (v->alloced > num)
+		return;
+
+	v->index = talloc_realloc_size(tall_vty_vec_ctx, v->index,
+				       sizeof(void *) * (v->alloced * 2));
+	memset(&v->index[v->alloced], 0, sizeof(void *) * v->alloced);
+	v->alloced *= 2;
+
+	if (v->alloced <= num)
+		vector_ensure(v, num);
+}
+
+/* This function only returns next empty slot index.  It dose not mean
+   the slot's index memory is assigned, please call vector_ensure()
+   after calling this function. */
+int vector_empty_slot(vector v)
+{
+	unsigned int i;
+
+	if (v->active == 0)
+		return 0;
+
+	for (i = 0; i < v->active; i++)
+		if (v->index[i] == 0)
+			return i;
+
+	return i;
+}
+
+/* Set value to the smallest empty slot. */
+int vector_set(vector v, void *val)
+{
+	unsigned int i;
+
+	i = vector_empty_slot(v);
+	vector_ensure(v, i);
+
+	v->index[i] = val;
+
+	if (v->active <= i)
+		v->active = i + 1;
+
+	return i;
+}
+
+/* Set value to specified index slot. */
+int vector_set_index(vector v, unsigned int i, void *val)
+{
+	vector_ensure(v, i);
+
+	v->index[i] = val;
+
+	if (v->active <= i)
+		v->active = i + 1;
+
+	return i;
+}
+
+/* Look up vector.  */
+void *vector_lookup(vector v, unsigned int i)
+{
+	if (i >= v->active)
+		return NULL;
+	return v->index[i];
+}
+
+/* Lookup vector, ensure it. */
+void *vector_lookup_ensure(vector v, unsigned int i)
+{
+	vector_ensure(v, i);
+	return v->index[i];
+}
+
+/* Unset value at specified index slot. */
+void vector_unset(vector v, unsigned int i)
+{
+	if (i >= v->alloced)
+		return;
+
+	v->index[i] = NULL;
+
+	if (i + 1 == v->active) {
+		v->active--;
+		while (i && v->index[--i] == NULL && v->active--) ;	/* Is this ugly ? */
+	}
+}
+
+/* Count the number of not emplty slot. */
+unsigned int vector_count(vector v)
+{
+	unsigned int i;
+	unsigned count = 0;
+
+	for (i = 0; i < v->active; i++)
+		if (v->index[i] != NULL)
+			count++;
+
+	return count;
+}
diff --git a/openbsc/src/vty/vty.c b/openbsc/src/vty/vty.c
new file mode 100644
index 0000000..0806856
--- /dev/null
+++ b/openbsc/src/vty/vty.c
@@ -0,0 +1,1675 @@
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <termios.h>
+
+#include <sys/utsname.h>
+#include <sys/param.h>
+
+#include <arpa/telnet.h>
+
+#include "cardshell.h"
+#include <vty/vty.h>
+#include <vty/command.h>
+#include <vty/buffer.h>
+#include <osmocore/talloc.h>
+
+/* our callback, located in telnet_interface.c */
+void vty_event(enum event event, int sock, struct vty *vty);
+
+extern struct host host;
+
+/* Vector which store each vty structure. */
+static vector vtyvec;
+
+vector Vvty_serv_thread;
+
+char *vty_cwd = NULL;
+
+/* Configure lock. */
+static int vty_config;
+
+static int no_password_check = 1;
+
+void *tall_vty_ctx;
+
+static void vty_clear_buf(struct vty *vty)
+{
+	memset(vty->buf, 0, vty->max);
+}
+
+/* Allocate new vty struct. */
+struct vty *vty_new()
+{
+	struct vty *new = talloc_zero(tall_vty_ctx, struct vty);
+
+	if (!new)
+		goto out;
+
+	new->obuf = buffer_new(new, 0);	/* Use default buffer size. */
+	if (!new->obuf)
+		goto out_new;
+	new->buf = _talloc_zero(new, VTY_BUFSIZ, "vty_new->buf");
+	if (!new->buf)
+		goto out_obuf;
+
+	new->max = VTY_BUFSIZ;
+
+	return new;
+
+out_obuf:
+	buffer_free(new->obuf);
+out_new:
+	talloc_free(new);
+	new = NULL;
+out:
+	return new;
+}
+
+/* Authentication of vty */
+static void vty_auth(struct vty *vty, char *buf)
+{
+	char *passwd = NULL;
+	enum node_type next_node = 0;
+	int fail;
+	char *crypt(const char *, const char *);
+
+	switch (vty->node) {
+	case AUTH_NODE:
+#ifdef VTY_CRYPT_PW
+		if (host.encrypt)
+			passwd = host.password_encrypt;
+		else
+#endif
+			passwd = host.password;
+		if (host.advanced)
+			next_node = host.enable ? VIEW_NODE : ENABLE_NODE;
+		else
+			next_node = VIEW_NODE;
+		break;
+	case AUTH_ENABLE_NODE:
+#ifdef VTY_CRYPT_PW
+		if (host.encrypt)
+			passwd = host.enable_encrypt;
+		else
+#endif
+			passwd = host.enable;
+		next_node = ENABLE_NODE;
+		break;
+	}
+
+	if (passwd) {
+#ifdef VTY_CRYPT_PW
+		if (host.encrypt)
+			fail = strcmp(crypt(buf, passwd), passwd);
+		else
+#endif
+			fail = strcmp(buf, passwd);
+	} else
+		fail = 1;
+
+	if (!fail) {
+		vty->fail = 0;
+		vty->node = next_node;	/* Success ! */
+	} else {
+		vty->fail++;
+		if (vty->fail >= 3) {
+			if (vty->node == AUTH_NODE) {
+				vty_out(vty,
+					"%% Bad passwords, too many failures!%s",
+					VTY_NEWLINE);
+				vty->status = VTY_CLOSE;
+			} else {
+				/* AUTH_ENABLE_NODE */
+				vty->fail = 0;
+				vty_out(vty,
+					"%% Bad enable passwords, too many failures!%s",
+					VTY_NEWLINE);
+				vty->node = VIEW_NODE;
+			}
+		}
+	}
+}
+
+/* Close vty interface. */
+void vty_close(struct vty *vty)
+{
+	int i;
+
+	if (vty->obuf)  {
+		/* Flush buffer. */
+		buffer_flush_all(vty->obuf, vty->fd);
+
+		/* Free input buffer. */
+		buffer_free(vty->obuf);
+		vty->obuf = NULL;
+	}
+
+	/* Free command history. */
+	for (i = 0; i < VTY_MAXHIST; i++)
+		if (vty->hist[i])
+			talloc_free(vty->hist[i]);
+
+	/* Unset vector. */
+	vector_unset(vtyvec, vty->fd);
+
+	/* Close socket. */
+	if (vty->fd > 0)
+		close(vty->fd);
+
+	if (vty->buf) {
+		talloc_free(vty->buf);
+		vty->buf = NULL;
+	}
+
+	/* Check configure. */
+	vty_config_unlock(vty);
+
+	/* VTY_CLOSED is handled by the telnet_interface */
+	vty_event(VTY_CLOSED, vty->fd, vty);
+
+	/* OK free vty. */
+	talloc_free(vty);
+}
+
+int vty_shell(struct vty *vty)
+{
+	return vty->type == VTY_SHELL ? 1 : 0;
+}
+
+
+/* VTY standard output function. */
+int vty_out(struct vty *vty, const char *format, ...)
+{
+	va_list args;
+	int len = 0;
+	int size = 1024;
+	char buf[1024];
+	char *p = NULL;
+
+	if (vty_shell(vty)) {
+		va_start(args, format);
+		vprintf(format, args);
+		va_end(args);
+	} else {
+		/* Try to write to initial buffer.  */
+		va_start(args, format);
+		len = vsnprintf(buf, sizeof buf, format, args);
+		va_end(args);
+
+		/* Initial buffer is not enough.  */
+		if (len < 0 || len >= size) {
+			while (1) {
+				if (len > -1)
+					size = len + 1;
+				else
+					size = size * 2;
+
+				p = talloc_realloc_size(vty, p, size);
+				if (!p)
+					return -1;
+
+				va_start(args, format);
+				len = vsnprintf(p, size, format, args);
+				va_end(args);
+
+				if (len > -1 && len < size)
+					break;
+			}
+		}
+
+		/* When initial buffer is enough to store all output.  */
+		if (!p)
+			p = buf;
+
+		/* Pointer p must point out buffer. */
+		buffer_put(vty->obuf, (u_char *) p, len);
+
+		/* If p is not different with buf, it is allocated buffer.  */
+		if (p != buf)
+			talloc_free(p);
+	}
+
+	vty_event(VTY_WRITE, vty->fd, vty);
+
+	return len;
+}
+
+int vty_out_newline(struct vty *vty)
+{
+	char *p = vty_newline(vty);
+	buffer_put(vty->obuf, p, strlen(p));
+	return 0;
+}
+
+int vty_config_lock(struct vty *vty)
+{
+	if (vty_config == 0) {
+		vty->config = 1;
+		vty_config = 1;
+	}
+	return vty->config;
+}
+
+int vty_config_unlock(struct vty *vty)
+{
+	if (vty_config == 1 && vty->config == 1) {
+		vty->config = 0;
+		vty_config = 0;
+	}
+	return vty->config;
+}
+
+/* Say hello to vty interface. */
+void vty_hello(struct vty *vty)
+{
+	if (host.motdfile) {
+		FILE *f;
+		char buf[4096];
+
+		f = fopen(host.motdfile, "r");
+		if (f) {
+			while (fgets(buf, sizeof(buf), f)) {
+				char *s;
+				/* work backwards to ignore trailling isspace() */
+				for (s = buf + strlen(buf);
+				     (s > buf) && isspace(*(s - 1)); s--) ;
+				*s = '\0';
+				vty_out(vty, "%s%s", buf, VTY_NEWLINE);
+			}
+			fclose(f);
+		} else
+			vty_out(vty, "MOTD file not found%s", VTY_NEWLINE);
+	} else if (host.motd)
+		vty_out(vty, "%s", host.motd);
+}
+
+/* Put out prompt and wait input from user. */
+static void vty_prompt(struct vty *vty)
+{
+	struct utsname names;
+	const char *hostname;
+
+	if (vty->type == VTY_TERM) {
+		hostname = host.name;
+		if (!hostname) {
+			uname(&names);
+			hostname = names.nodename;
+		}
+		vty_out(vty, cmd_prompt(vty->node), hostname);
+	}
+}
+
+/* Command execution over the vty interface. */
+static int vty_command(struct vty *vty, char *buf)
+{
+	int ret;
+	vector vline;
+
+	/* Split readline string up into the vector */
+	vline = cmd_make_strvec(buf);
+
+	if (vline == NULL)
+		return CMD_SUCCESS;
+
+	ret = cmd_execute_command(vline, vty, NULL, 0);
+	if (ret != CMD_SUCCESS)
+		switch (ret) {
+		case CMD_WARNING:
+			if (vty->type == VTY_FILE)
+				vty_out(vty, "Warning...%s", VTY_NEWLINE);
+			break;
+		case CMD_ERR_AMBIGUOUS:
+			vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
+			break;
+		case CMD_ERR_NO_MATCH:
+			vty_out(vty, "%% Unknown command.%s", VTY_NEWLINE);
+			break;
+		case CMD_ERR_INCOMPLETE:
+			vty_out(vty, "%% Command incomplete.%s", VTY_NEWLINE);
+			break;
+		}
+	cmd_free_strvec(vline);
+
+	return ret;
+}
+
+static const char telnet_backward_char = 0x08;
+static const char telnet_space_char = ' ';
+
+/* Basic function to write buffer to vty. */
+static void vty_write(struct vty *vty, const char *buf, size_t nbytes)
+{
+	if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
+		return;
+
+	/* Should we do buffering here ?  And make vty_flush (vty) ? */
+	buffer_put(vty->obuf, buf, nbytes);
+}
+
+/* Ensure length of input buffer.  Is buffer is short, double it. */
+static void vty_ensure(struct vty *vty, int length)
+{
+	if (vty->max <= length) {
+		vty->max *= 2;
+		vty->buf = talloc_realloc_size(vty, vty->buf, vty->max);
+		// FIXME: check return
+	}
+}
+
+/* Basic function to insert character into vty. */
+static void vty_self_insert(struct vty *vty, char c)
+{
+	int i;
+	int length;
+
+	vty_ensure(vty, vty->length + 1);
+	length = vty->length - vty->cp;
+	memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length);
+	vty->buf[vty->cp] = c;
+
+	vty_write(vty, &vty->buf[vty->cp], length + 1);
+	for (i = 0; i < length; i++)
+		vty_write(vty, &telnet_backward_char, 1);
+
+	vty->cp++;
+	vty->length++;
+}
+
+/* Self insert character 'c' in overwrite mode. */
+static void vty_self_insert_overwrite(struct vty *vty, char c)
+{
+	vty_ensure(vty, vty->length + 1);
+	vty->buf[vty->cp++] = c;
+
+	if (vty->cp > vty->length)
+		vty->length++;
+
+	if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
+		return;
+
+	vty_write(vty, &c, 1);
+}
+
+/* Insert a word into vty interface with overwrite mode. */
+static void vty_insert_word_overwrite(struct vty *vty, char *str)
+{
+	int len = strlen(str);
+	vty_write(vty, str, len);
+	strcpy(&vty->buf[vty->cp], str);
+	vty->cp += len;
+	vty->length = vty->cp;
+}
+
+/* Forward character. */
+static void vty_forward_char(struct vty *vty)
+{
+	if (vty->cp < vty->length) {
+		vty_write(vty, &vty->buf[vty->cp], 1);
+		vty->cp++;
+	}
+}
+
+/* Backward character. */
+static void vty_backward_char(struct vty *vty)
+{
+	if (vty->cp > 0) {
+		vty->cp--;
+		vty_write(vty, &telnet_backward_char, 1);
+	}
+}
+
+/* Move to the beginning of the line. */
+static void vty_beginning_of_line(struct vty *vty)
+{
+	while (vty->cp)
+		vty_backward_char(vty);
+}
+
+/* Move to the end of the line. */
+static void vty_end_of_line(struct vty *vty)
+{
+	while (vty->cp < vty->length)
+		vty_forward_char(vty);
+}
+
+/* Add current command line to the history buffer. */
+static void vty_hist_add(struct vty *vty)
+{
+	int index;
+
+	if (vty->length == 0)
+		return;
+
+	index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1;
+
+	/* Ignore the same string as previous one. */
+	if (vty->hist[index])
+		if (strcmp(vty->buf, vty->hist[index]) == 0) {
+			vty->hp = vty->hindex;
+			return;
+		}
+
+	/* Insert history entry. */
+	if (vty->hist[vty->hindex])
+		talloc_free(vty->hist[vty->hindex]);
+	vty->hist[vty->hindex] = talloc_strdup(vty, vty->buf);
+
+	/* History index rotation. */
+	vty->hindex++;
+	if (vty->hindex == VTY_MAXHIST)
+		vty->hindex = 0;
+
+	vty->hp = vty->hindex;
+}
+
+/* Get telnet window size. */
+static int
+vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes)
+{
+#ifdef TELNET_OPTION_DEBUG
+  int i;
+
+  for (i = 0; i < nbytes; i++)
+    {
+      switch (buf[i])
+	{
+	case IAC:
+	  vty_out (vty, "IAC ");
+	  break;
+	case WILL:
+	  vty_out (vty, "WILL ");
+	  break;
+	case WONT:
+	  vty_out (vty, "WONT ");
+	  break;
+	case DO:
+	  vty_out (vty, "DO ");
+	  break;
+	case DONT:
+	  vty_out (vty, "DONT ");
+	  break;
+	case SB:
+	  vty_out (vty, "SB ");
+	  break;
+	case SE:
+	  vty_out (vty, "SE ");
+	  break;
+	case TELOPT_ECHO:
+	  vty_out (vty, "TELOPT_ECHO %s", VTY_NEWLINE);
+	  break;
+	case TELOPT_SGA:
+	  vty_out (vty, "TELOPT_SGA %s", VTY_NEWLINE);
+	  break;
+	case TELOPT_NAWS:
+	  vty_out (vty, "TELOPT_NAWS %s", VTY_NEWLINE);
+	  break;
+	default:
+	  vty_out (vty, "%x ", buf[i]);
+	  break;
+	}
+    }
+  vty_out (vty, "%s", VTY_NEWLINE);
+
+#endif /* TELNET_OPTION_DEBUG */
+
+  switch (buf[0])
+    {
+    case SB:
+      vty->sb_len = 0;
+      vty->iac_sb_in_progress = 1;
+      return 0;
+      break;
+    case SE:
+      {
+	if (!vty->iac_sb_in_progress)
+	  return 0;
+
+	if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0'))
+	  {
+	    vty->iac_sb_in_progress = 0;
+	    return 0;
+	  }
+	switch (vty->sb_buf[0])
+	  {
+	  case TELOPT_NAWS:
+	    if (vty->sb_len != TELNET_NAWS_SB_LEN)
+	      vty_out(vty,"RFC 1073 violation detected: telnet NAWS option "
+			"should send %d characters, but we received %lu",
+			TELNET_NAWS_SB_LEN, (u_long)vty->sb_len);
+	    else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN)
+	      vty_out(vty, "Bug detected: sizeof(vty->sb_buf) %lu < %d, "
+		       "too small to handle the telnet NAWS option",
+		       (u_long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN);
+	    else
+	      {
+		vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]);
+		vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]);
+#ifdef TELNET_OPTION_DEBUG
+		vty_out(vty, "TELNET NAWS window size negotiation completed: "
+			      "width %d, height %d%s",
+			vty->width, vty->height, VTY_NEWLINE);
+#endif
+	      }
+	    break;
+	  }
+	vty->iac_sb_in_progress = 0;
+	return 0;
+	break;
+      }
+    default:
+      break;
+    }
+  return 1;
+}
+
+/* Execute current command line. */
+static int vty_execute(struct vty *vty)
+{
+	int ret;
+
+	ret = CMD_SUCCESS;
+
+	switch (vty->node) {
+	case AUTH_NODE:
+	case AUTH_ENABLE_NODE:
+		vty_auth(vty, vty->buf);
+		break;
+	default:
+		ret = vty_command(vty, vty->buf);
+		if (vty->type == VTY_TERM)
+			vty_hist_add(vty);
+		break;
+	}
+
+	/* Clear command line buffer. */
+	vty->cp = vty->length = 0;
+	vty_clear_buf(vty);
+
+	if (vty->status != VTY_CLOSE)
+		vty_prompt(vty);
+
+	return ret;
+}
+
+/* Send WILL TELOPT_ECHO to remote server. */
+static void
+vty_will_echo (struct vty *vty)
+{
+	unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' };
+	vty_out (vty, "%s", cmd);
+}
+
+/* Make suppress Go-Ahead telnet option. */
+static void
+vty_will_suppress_go_ahead (struct vty *vty)
+{
+	unsigned char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' };
+	vty_out (vty, "%s", cmd);
+}
+
+/* Make don't use linemode over telnet. */
+static void
+vty_dont_linemode (struct vty *vty)
+{
+	unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' };
+	vty_out (vty, "%s", cmd);
+}
+
+/* Use window size. */
+static void
+vty_do_window_size (struct vty *vty)
+{
+	unsigned char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' };
+	vty_out (vty, "%s", cmd);
+}
+
+static void vty_kill_line_from_beginning(struct vty *);
+static void vty_redraw_line(struct vty *);
+
+/* Print command line history.  This function is called from
+   vty_next_line and vty_previous_line. */
+static void vty_history_print(struct vty *vty)
+{
+	int length;
+
+	vty_kill_line_from_beginning(vty);
+
+	/* Get previous line from history buffer */
+	length = strlen(vty->hist[vty->hp]);
+	memcpy(vty->buf, vty->hist[vty->hp], length);
+	vty->cp = vty->length = length;
+
+	/* Redraw current line */
+	vty_redraw_line(vty);
+}
+
+/* Show next command line history. */
+static void vty_next_line(struct vty *vty)
+{
+	int try_index;
+
+	if (vty->hp == vty->hindex)
+		return;
+
+	/* Try is there history exist or not. */
+	try_index = vty->hp;
+	if (try_index == (VTY_MAXHIST - 1))
+		try_index = 0;
+	else
+		try_index++;
+
+	/* If there is not history return. */
+	if (vty->hist[try_index] == NULL)
+		return;
+	else
+		vty->hp = try_index;
+
+	vty_history_print(vty);
+}
+
+/* Show previous command line history. */
+static void vty_previous_line(struct vty *vty)
+{
+	int try_index;
+
+	try_index = vty->hp;
+	if (try_index == 0)
+		try_index = VTY_MAXHIST - 1;
+	else
+		try_index--;
+
+	if (vty->hist[try_index] == NULL)
+		return;
+	else
+		vty->hp = try_index;
+
+	vty_history_print(vty);
+}
+
+/* This function redraw all of the command line character. */
+static void vty_redraw_line(struct vty *vty)
+{
+	vty_write(vty, vty->buf, vty->length);
+	vty->cp = vty->length;
+}
+
+/* Forward word. */
+static void vty_forward_word(struct vty *vty)
+{
+	while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
+		vty_forward_char(vty);
+
+	while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
+		vty_forward_char(vty);
+}
+
+/* Backward word without skipping training space. */
+static void vty_backward_pure_word(struct vty *vty)
+{
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+		vty_backward_char(vty);
+}
+
+/* Backward word. */
+static void vty_backward_word(struct vty *vty)
+{
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
+		vty_backward_char(vty);
+
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+		vty_backward_char(vty);
+}
+
+/* When '^D' is typed at the beginning of the line we move to the down
+   level. */
+static void vty_down_level(struct vty *vty)
+{
+	vty_out(vty, "%s", VTY_NEWLINE);
+	(*config_exit_cmd.func) (NULL, vty, 0, NULL);
+	vty_prompt(vty);
+	vty->cp = 0;
+}
+
+/* When '^Z' is received from vty, move down to the enable mode. */
+static void vty_end_config(struct vty *vty)
+{
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	switch (vty->node) {
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		/* Nothing to do. */
+		break;
+	case CONFIG_NODE:
+	case VTY_NODE:
+		vty_config_unlock(vty);
+		vty->node = ENABLE_NODE;
+		break;
+	default:
+		/* Unknown node, we have to ignore it. */
+		break;
+	}
+
+	vty_prompt(vty);
+	vty->cp = 0;
+}
+
+/* Delete a charcter at the current point. */
+static void vty_delete_char(struct vty *vty)
+{
+	int i;
+	int size;
+
+	if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
+		return;
+
+	if (vty->length == 0) {
+		vty_down_level(vty);
+		return;
+	}
+
+	if (vty->cp == vty->length)
+		return;		/* completion need here? */
+
+	size = vty->length - vty->cp;
+
+	vty->length--;
+	memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1);
+	vty->buf[vty->length] = '\0';
+
+	vty_write(vty, &vty->buf[vty->cp], size - 1);
+	vty_write(vty, &telnet_space_char, 1);
+
+	for (i = 0; i < size; i++)
+		vty_write(vty, &telnet_backward_char, 1);
+}
+
+/* Delete a character before the point. */
+static void vty_delete_backward_char(struct vty *vty)
+{
+	if (vty->cp == 0)
+		return;
+
+	vty_backward_char(vty);
+	vty_delete_char(vty);
+}
+
+/* Kill rest of line from current point. */
+static void vty_kill_line(struct vty *vty)
+{
+	int i;
+	int size;
+
+	size = vty->length - vty->cp;
+
+	if (size == 0)
+		return;
+
+	for (i = 0; i < size; i++)
+		vty_write(vty, &telnet_space_char, 1);
+	for (i = 0; i < size; i++)
+		vty_write(vty, &telnet_backward_char, 1);
+
+	memset(&vty->buf[vty->cp], 0, size);
+	vty->length = vty->cp;
+}
+
+/* Kill line from the beginning. */
+static void vty_kill_line_from_beginning(struct vty *vty)
+{
+	vty_beginning_of_line(vty);
+	vty_kill_line(vty);
+}
+
+/* Delete a word before the point. */
+static void vty_forward_kill_word(struct vty *vty)
+{
+	while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
+		vty_delete_char(vty);
+	while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
+		vty_delete_char(vty);
+}
+
+/* Delete a word before the point. */
+static void vty_backward_kill_word(struct vty *vty)
+{
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
+		vty_delete_backward_char(vty);
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+		vty_delete_backward_char(vty);
+}
+
+/* Transpose chars before or at the point. */
+static void vty_transpose_chars(struct vty *vty)
+{
+	char c1, c2;
+
+	/* If length is short or point is near by the beginning of line then
+	   return. */
+	if (vty->length < 2 || vty->cp < 1)
+		return;
+
+	/* In case of point is located at the end of the line. */
+	if (vty->cp == vty->length) {
+		c1 = vty->buf[vty->cp - 1];
+		c2 = vty->buf[vty->cp - 2];
+
+		vty_backward_char(vty);
+		vty_backward_char(vty);
+		vty_self_insert_overwrite(vty, c1);
+		vty_self_insert_overwrite(vty, c2);
+	} else {
+		c1 = vty->buf[vty->cp];
+		c2 = vty->buf[vty->cp - 1];
+
+		vty_backward_char(vty);
+		vty_self_insert_overwrite(vty, c1);
+		vty_self_insert_overwrite(vty, c2);
+	}
+}
+
+/* Do completion at vty interface. */
+static void vty_complete_command(struct vty *vty)
+{
+	int i;
+	int ret;
+	char **matched = NULL;
+	vector vline;
+
+	if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
+		return;
+
+	vline = cmd_make_strvec(vty->buf);
+	if (vline == NULL)
+		return;
+
+	/* In case of 'help \t'. */
+	if (isspace((int)vty->buf[vty->length - 1]))
+		vector_set(vline, '\0');
+
+	matched = cmd_complete_command(vline, vty, &ret);
+
+	cmd_free_strvec(vline);
+
+	vty_out(vty, "%s", VTY_NEWLINE);
+	switch (ret) {
+	case CMD_ERR_AMBIGUOUS:
+		vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		break;
+	case CMD_ERR_NO_MATCH:
+		/* vty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE); */
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		break;
+	case CMD_COMPLETE_FULL_MATCH:
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		vty_backward_pure_word(vty);
+		vty_insert_word_overwrite(vty, matched[0]);
+		vty_self_insert(vty, ' ');
+		talloc_free(matched[0]);
+		break;
+	case CMD_COMPLETE_MATCH:
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		vty_backward_pure_word(vty);
+		vty_insert_word_overwrite(vty, matched[0]);
+		talloc_free(matched[0]);
+		break;
+	case CMD_COMPLETE_LIST_MATCH:
+		for (i = 0; matched[i] != NULL; i++) {
+			if (i != 0 && ((i % 6) == 0))
+				vty_out(vty, "%s", VTY_NEWLINE);
+			vty_out(vty, "%-10s ", matched[i]);
+			talloc_free(matched[i]);
+		}
+		vty_out(vty, "%s", VTY_NEWLINE);
+
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		break;
+	case CMD_ERR_NOTHING_TODO:
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		break;
+	default:
+		break;
+	}
+	if (matched)
+		vector_only_index_free(matched);
+}
+
+static void
+vty_describe_fold(struct vty *vty, int cmd_width,
+		  unsigned int desc_width, struct desc *desc)
+{
+	char *buf;
+	const char *cmd, *p;
+	int pos;
+
+	cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd;
+
+	if (desc_width <= 0) {
+		vty_out(vty, "  %-*s  %s%s", cmd_width, cmd, desc->str,
+			VTY_NEWLINE);
+		return;
+	}
+
+	buf = _talloc_zero(vty, strlen(desc->str) + 1, "describe_fold");
+	if (!buf)
+		return;
+
+	for (p = desc->str; strlen(p) > desc_width; p += pos + 1) {
+		for (pos = desc_width; pos > 0; pos--)
+			if (*(p + pos) == ' ')
+				break;
+
+		if (pos == 0)
+			break;
+
+		strncpy(buf, p, pos);
+		buf[pos] = '\0';
+		vty_out(vty, "  %-*s  %s%s", cmd_width, cmd, buf, VTY_NEWLINE);
+
+		cmd = "";
+	}
+
+	vty_out(vty, "  %-*s  %s%s", cmd_width, cmd, p, VTY_NEWLINE);
+
+	talloc_free(buf);
+}
+
+/* Describe matched command function. */
+static void vty_describe_command(struct vty *vty)
+{
+	int ret;
+	vector vline;
+	vector describe;
+	unsigned int i, width, desc_width;
+	struct desc *desc, *desc_cr = NULL;
+
+	vline = cmd_make_strvec(vty->buf);
+
+	/* In case of '> ?'. */
+	if (vline == NULL) {
+		vline = vector_init(1);
+		vector_set(vline, '\0');
+	} else if (isspace((int)vty->buf[vty->length - 1]))
+		vector_set(vline, '\0');
+
+	describe = cmd_describe_command(vline, vty, &ret);
+
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	/* Ambiguous error. */
+	switch (ret) {
+	case CMD_ERR_AMBIGUOUS:
+		cmd_free_strvec(vline);
+		vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		return;
+		break;
+	case CMD_ERR_NO_MATCH:
+		cmd_free_strvec(vline);
+		vty_out(vty, "%% There is no matched command.%s", VTY_NEWLINE);
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		return;
+		break;
+	}
+
+	/* Get width of command string. */
+	width = 0;
+	for (i = 0; i < vector_active(describe); i++)
+		if ((desc = vector_slot(describe, i)) != NULL) {
+			unsigned int len;
+
+			if (desc->cmd[0] == '\0')
+				continue;
+
+			len = strlen(desc->cmd);
+			if (desc->cmd[0] == '.')
+				len--;
+
+			if (width < len)
+				width = len;
+		}
+
+	/* Get width of description string. */
+	desc_width = vty->width - (width + 6);
+
+	/* Print out description. */
+	for (i = 0; i < vector_active(describe); i++)
+		if ((desc = vector_slot(describe, i)) != NULL) {
+			if (desc->cmd[0] == '\0')
+				continue;
+
+			if (strcmp(desc->cmd, "<cr>") == 0) {
+				desc_cr = desc;
+				continue;
+			}
+
+			if (!desc->str)
+				vty_out(vty, "  %-s%s",
+					desc->cmd[0] ==
+					'.' ? desc->cmd + 1 : desc->cmd,
+					VTY_NEWLINE);
+			else if (desc_width >= strlen(desc->str))
+				vty_out(vty, "  %-*s  %s%s", width,
+					desc->cmd[0] ==
+					'.' ? desc->cmd + 1 : desc->cmd,
+					desc->str, VTY_NEWLINE);
+			else
+				vty_describe_fold(vty, width, desc_width, desc);
+
+#if 0
+			vty_out(vty, "  %-*s %s%s", width
+				desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
+				desc->str ? desc->str : "", VTY_NEWLINE);
+#endif				/* 0 */
+		}
+
+	if ((desc = desc_cr)) {
+		if (!desc->str)
+			vty_out(vty, "  %-s%s",
+				desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
+				VTY_NEWLINE);
+		else if (desc_width >= strlen(desc->str))
+			vty_out(vty, "  %-*s  %s%s", width,
+				desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
+				desc->str, VTY_NEWLINE);
+		else
+			vty_describe_fold(vty, width, desc_width, desc);
+	}
+
+	cmd_free_strvec(vline);
+	vector_free(describe);
+
+	vty_prompt(vty);
+	vty_redraw_line(vty);
+}
+
+/* ^C stop current input and do not add command line to the history. */
+static void vty_stop_input(struct vty *vty)
+{
+	vty->cp = vty->length = 0;
+	vty_clear_buf(vty);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	switch (vty->node) {
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		/* Nothing to do. */
+		break;
+	case CONFIG_NODE:
+	case VTY_NODE:
+		vty_config_unlock(vty);
+		vty->node = ENABLE_NODE;
+		break;
+	default:
+		/* Unknown node, we have to ignore it. */
+		break;
+	}
+	vty_prompt(vty);
+
+	/* Set history pointer to the latest one. */
+	vty->hp = vty->hindex;
+}
+
+#define CONTROL(X)  ((X) - '@')
+#define VTY_NORMAL     0
+#define VTY_PRE_ESCAPE 1
+#define VTY_ESCAPE     2
+
+/* Escape character command map. */
+static void vty_escape_map(unsigned char c, struct vty *vty)
+{
+	switch (c) {
+	case ('A'):
+		vty_previous_line(vty);
+		break;
+	case ('B'):
+		vty_next_line(vty);
+		break;
+	case ('C'):
+		vty_forward_char(vty);
+		break;
+	case ('D'):
+		vty_backward_char(vty);
+		break;
+	default:
+		break;
+	}
+
+	/* Go back to normal mode. */
+	vty->escape = VTY_NORMAL;
+}
+
+/* Quit print out to the buffer. */
+static void vty_buffer_reset(struct vty *vty)
+{
+	buffer_reset(vty->obuf);
+	vty_prompt(vty);
+	vty_redraw_line(vty);
+}
+
+/* Read data via vty socket. */
+int vty_read(struct vty *vty)
+{
+	int i;
+	int nbytes;
+	unsigned char buf[VTY_READ_BUFSIZ];
+
+	int vty_sock = vty->fd;
+
+	/* Read raw data from socket */
+	if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) {
+		if (nbytes < 0) {
+			if (ERRNO_IO_RETRY(errno)) {
+				vty_event(VTY_READ, vty_sock, vty);
+				return 0;
+			}
+		}
+		buffer_reset(vty->obuf);
+		vty->status = VTY_CLOSE;
+	}
+
+	for (i = 0; i < nbytes; i++) {
+		if (buf[i] == IAC) {
+			if (!vty->iac) {
+				vty->iac = 1;
+				continue;
+			} else {
+				vty->iac = 0;
+			}
+		}
+
+		if (vty->iac_sb_in_progress && !vty->iac) {
+			if (vty->sb_len < sizeof(vty->sb_buf))
+				vty->sb_buf[vty->sb_len] = buf[i];
+			vty->sb_len++;
+			continue;
+		}
+
+		if (vty->iac) {
+			/* In case of telnet command */
+			int ret = 0;
+			ret = vty_telnet_option(vty, buf + i, nbytes - i);
+			vty->iac = 0;
+			i += ret;
+			continue;
+		}
+
+		if (vty->status == VTY_MORE) {
+			switch (buf[i]) {
+			case CONTROL('C'):
+			case 'q':
+			case 'Q':
+				vty_buffer_reset(vty);
+				break;
+#if 0				/* More line does not work for "show ip bgp".  */
+			case '\n':
+			case '\r':
+				vty->status = VTY_MORELINE;
+				break;
+#endif
+			default:
+				break;
+			}
+			continue;
+		}
+
+		/* Escape character. */
+		if (vty->escape == VTY_ESCAPE) {
+			vty_escape_map(buf[i], vty);
+			continue;
+		}
+
+		/* Pre-escape status. */
+		if (vty->escape == VTY_PRE_ESCAPE) {
+			switch (buf[i]) {
+			case '[':
+				vty->escape = VTY_ESCAPE;
+				break;
+			case 'b':
+				vty_backward_word(vty);
+				vty->escape = VTY_NORMAL;
+				break;
+			case 'f':
+				vty_forward_word(vty);
+				vty->escape = VTY_NORMAL;
+				break;
+			case 'd':
+				vty_forward_kill_word(vty);
+				vty->escape = VTY_NORMAL;
+				break;
+			case CONTROL('H'):
+			case 0x7f:
+				vty_backward_kill_word(vty);
+				vty->escape = VTY_NORMAL;
+				break;
+			default:
+				vty->escape = VTY_NORMAL;
+				break;
+			}
+			continue;
+		}
+
+		switch (buf[i]) {
+		case CONTROL('A'):
+			vty_beginning_of_line(vty);
+			break;
+		case CONTROL('B'):
+			vty_backward_char(vty);
+			break;
+		case CONTROL('C'):
+			vty_stop_input(vty);
+			break;
+		case CONTROL('D'):
+			vty_delete_char(vty);
+			break;
+		case CONTROL('E'):
+			vty_end_of_line(vty);
+			break;
+		case CONTROL('F'):
+			vty_forward_char(vty);
+			break;
+		case CONTROL('H'):
+		case 0x7f:
+			vty_delete_backward_char(vty);
+			break;
+		case CONTROL('K'):
+			vty_kill_line(vty);
+			break;
+		case CONTROL('N'):
+			vty_next_line(vty);
+			break;
+		case CONTROL('P'):
+			vty_previous_line(vty);
+			break;
+		case CONTROL('T'):
+			vty_transpose_chars(vty);
+			break;
+		case CONTROL('U'):
+			vty_kill_line_from_beginning(vty);
+			break;
+		case CONTROL('W'):
+			vty_backward_kill_word(vty);
+			break;
+		case CONTROL('Z'):
+			vty_end_config(vty);
+			break;
+		case '\n':
+		case '\r':
+			vty_out(vty, "%s", VTY_NEWLINE);
+			vty_execute(vty);
+			break;
+		case '\t':
+			vty_complete_command(vty);
+			break;
+		case '?':
+			if (vty->node == AUTH_NODE
+			    || vty->node == AUTH_ENABLE_NODE)
+				vty_self_insert(vty, buf[i]);
+			else
+				vty_describe_command(vty);
+			break;
+		case '\033':
+			if (i + 1 < nbytes && buf[i + 1] == '[') {
+				vty->escape = VTY_ESCAPE;
+				i++;
+			} else
+				vty->escape = VTY_PRE_ESCAPE;
+			break;
+		default:
+			if (buf[i] > 31 && buf[i] < 127)
+				vty_self_insert(vty, buf[i]);
+			break;
+		}
+	}
+
+	/* Check status. */
+	if (vty->status == VTY_CLOSE)
+		vty_close(vty);
+	else {
+		vty_event(VTY_WRITE, vty_sock, vty);
+		vty_event(VTY_READ, vty_sock, vty);
+	}
+	return 0;
+}
+
+/* Read up configuration file */
+static int
+vty_read_file(FILE *confp)
+{
+	int ret;
+	struct vty *vty;
+
+	vty = vty_new();
+	vty->fd = 0;
+	vty->type = VTY_FILE;
+	vty->node = CONFIG_NODE;
+
+	ret = config_from_file(vty, confp);
+
+	if (ret != CMD_SUCCESS) {
+		switch (ret) {
+		case CMD_ERR_AMBIGUOUS:
+			fprintf(stderr, "Ambiguous command.\n");
+			break;
+		case CMD_ERR_NO_MATCH:
+			fprintf(stderr, "There is no such command.\n");
+			break;
+		}
+		fprintf(stderr, "Error occurred during reading below "
+			"line:\n%s\n", vty->buf);
+		vty_close(vty);
+		return -EINVAL;
+	}
+
+	vty_close(vty);
+	return 0;
+}
+
+/* Create new vty structure. */
+struct vty *
+vty_create (int vty_sock, void *priv)
+{
+  struct vty *vty;
+
+	struct termios t;
+
+	tcgetattr(vty_sock, &t);
+	cfmakeraw(&t);
+	tcsetattr(vty_sock, TCSANOW, &t);
+
+  /* Allocate new vty structure and set up default values. */
+  vty = vty_new ();
+  vty->fd = vty_sock;
+  vty->priv = priv;
+  vty->type = VTY_TERM;
+  if (no_password_check)
+    {
+      if (host.advanced)
+	vty->node = ENABLE_NODE;
+      else
+	vty->node = VIEW_NODE;
+    }
+  else
+    vty->node = AUTH_NODE;
+  vty->fail = 0;
+  vty->cp = 0;
+  vty_clear_buf (vty);
+  vty->length = 0;
+  memset (vty->hist, 0, sizeof (vty->hist));
+  vty->hp = 0;
+  vty->hindex = 0;
+  vector_set_index (vtyvec, vty_sock, vty);
+  vty->status = VTY_NORMAL;
+  if (host.lines >= 0)
+    vty->lines = host.lines;
+  else
+    vty->lines = -1;
+
+  if (! no_password_check)
+    {
+      /* Vty is not available if password isn't set. */
+      if (host.password == NULL && host.password_encrypt == NULL)
+	{
+	  vty_out (vty, "Vty password is not set.%s", VTY_NEWLINE);
+	  vty->status = VTY_CLOSE;
+	  vty_close (vty);
+	  return NULL;
+	}
+    }
+
+  /* Say hello to the world. */
+  vty_hello (vty);
+  if (! no_password_check)
+    vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+  /* Setting up terminal. */
+  vty_will_echo (vty);
+  vty_will_suppress_go_ahead (vty);
+
+  vty_dont_linemode (vty);
+  vty_do_window_size (vty);
+  /* vty_dont_lflow_ahead (vty); */
+
+  vty_prompt (vty);
+
+  /* Add read/write thread. */
+  vty_event (VTY_WRITE, vty_sock, vty);
+  vty_event (VTY_READ, vty_sock, vty);
+
+  return vty;
+}
+
+DEFUN(config_who, config_who_cmd, "who", "Display who is on vty\n")
+{
+	unsigned int i;
+	struct vty *v;
+
+	for (i = 0; i < vector_active(vtyvec); i++)
+		if ((v = vector_slot(vtyvec, i)) != NULL)
+			vty_out(vty, "%svty[%d] %s",
+				v->config ? "*" : " ", i, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+/* Move to vty configuration mode. */
+DEFUN(line_vty,
+      line_vty_cmd,
+      "line vty", "Configure a terminal line\n" "Virtual terminal\n")
+{
+	vty->node = VTY_NODE;
+	return CMD_SUCCESS;
+}
+
+/* vty login. */
+DEFUN(vty_login, vty_login_cmd, "login", "Enable password checking\n")
+{
+	no_password_check = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_vty_login,
+      no_vty_login_cmd, "no login", NO_STR "Enable password checking\n")
+{
+	no_password_check = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(service_advanced_vty,
+      service_advanced_vty_cmd,
+      "service advanced-vty",
+      "Set up miscellaneous service\n" "Enable advanced mode vty interface\n")
+{
+	host.advanced = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_service_advanced_vty,
+      no_service_advanced_vty_cmd,
+      "no service advanced-vty",
+      NO_STR
+      "Set up miscellaneous service\n" "Enable advanced mode vty interface\n")
+{
+	host.advanced = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(terminal_monitor,
+      terminal_monitor_cmd,
+      "terminal monitor",
+      "Set terminal line parameters\n"
+      "Copy debug output to the current terminal line\n")
+{
+	vty->monitor = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(terminal_no_monitor,
+      terminal_no_monitor_cmd,
+      "terminal no monitor",
+      "Set terminal line parameters\n"
+      NO_STR "Copy debug output to the current terminal line\n")
+{
+	vty->monitor = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_history,
+      show_history_cmd,
+      "show history", SHOW_STR "Display the session command history\n")
+{
+	int index;
+
+	for (index = vty->hindex + 1; index != vty->hindex;) {
+		if (index == VTY_MAXHIST) {
+			index = 0;
+			continue;
+		}
+
+		if (vty->hist[index] != NULL)
+			vty_out(vty, "  %s%s", vty->hist[index], VTY_NEWLINE);
+
+		index++;
+	}
+
+	return CMD_SUCCESS;
+}
+
+/* Display current configuration. */
+static int vty_config_write(struct vty *vty)
+{
+	vty_out(vty, "line vty%s", VTY_NEWLINE);
+
+	/* login */
+	if (no_password_check)
+		vty_out(vty, " no login%s", VTY_NEWLINE);
+
+	vty_out(vty, "!%s", VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+struct cmd_node vty_node = {
+	VTY_NODE,
+	"%s(config-line)# ",
+	1,
+};
+
+/* Reset all VTY status. */
+void vty_reset()
+{
+	unsigned int i;
+	struct vty *vty;
+	struct thread *vty_serv_thread;
+
+	for (i = 0; i < vector_active(vtyvec); i++)
+		if ((vty = vector_slot(vtyvec, i)) != NULL) {
+			buffer_reset(vty->obuf);
+			vty->status = VTY_CLOSE;
+			vty_close(vty);
+		}
+
+	for (i = 0; i < vector_active(Vvty_serv_thread); i++)
+		if ((vty_serv_thread =
+		     vector_slot(Vvty_serv_thread, i)) != NULL) {
+			//thread_cancel (vty_serv_thread);
+			vector_slot(Vvty_serv_thread, i) = NULL;
+			close(i);
+		}
+}
+
+static void vty_save_cwd(void)
+{
+	char cwd[MAXPATHLEN];
+	char *c ;
+
+	c = getcwd(cwd, MAXPATHLEN);
+
+	if (!c) {
+		if (chdir(SYSCONFDIR) != 0)
+		    perror("chdir failed");
+		if (getcwd(cwd, MAXPATHLEN) == NULL)
+		    perror("getcwd failed");
+	}
+
+	vty_cwd = _talloc_zero(tall_vty_ctx, strlen(cwd) + 1, "save_cwd");
+	strcpy(vty_cwd, cwd);
+}
+
+char *vty_get_cwd()
+{
+	return vty_cwd;
+}
+
+int vty_shell_serv(struct vty *vty)
+{
+	return vty->type == VTY_SHELL_SERV ? 1 : 0;
+}
+
+void vty_init_vtysh()
+{
+	vtyvec = vector_init(VECTOR_MIN_SIZE);
+}
+
+extern void *tall_bsc_ctx;
+/* Install vty's own commands like `who' command. */
+void vty_init()
+{
+	tall_vty_ctx = talloc_named_const(NULL, 0, "vty");
+	tall_vty_vec_ctx = talloc_named_const(tall_vty_ctx, 0, "vty_vector");
+	tall_vty_cmd_ctx = talloc_named_const(tall_vty_ctx, 0, "vty_command");
+
+	/* For further configuration read, preserve current directory. */
+	vty_save_cwd();
+
+	vtyvec = vector_init(VECTOR_MIN_SIZE);
+
+	/* Install bgp top node. */
+	install_node(&vty_node, vty_config_write);
+
+	install_element(VIEW_NODE, &config_who_cmd);
+	install_element(VIEW_NODE, &show_history_cmd);
+	install_element(ENABLE_NODE, &config_who_cmd);
+	install_element(CONFIG_NODE, &line_vty_cmd);
+	install_element(CONFIG_NODE, &service_advanced_vty_cmd);
+	install_element(CONFIG_NODE, &no_service_advanced_vty_cmd);
+	install_element(CONFIG_NODE, &show_history_cmd);
+	install_element(ENABLE_NODE, &terminal_monitor_cmd);
+	install_element(ENABLE_NODE, &terminal_no_monitor_cmd);
+	install_element(ENABLE_NODE, &show_history_cmd);
+
+	install_default(VTY_NODE);
+	install_element(VTY_NODE, &vty_login_cmd);
+	install_element(VTY_NODE, &no_vty_login_cmd);
+}
+
+int vty_read_config_file(const char *file_name)
+{
+	FILE *cfile;
+	int rc;
+
+	cfile = fopen(file_name, "r");
+	if (!cfile)
+		return -ENOENT;
+
+	rc = vty_read_file(cfile);
+	fclose(cfile);
+
+	host_config_set(file_name);
+
+	return rc;
+}
diff --git a/openbsc/src/vty_interface.c b/openbsc/src/vty_interface.c
new file mode 100644
index 0000000..851a603
--- /dev/null
+++ b/openbsc/src/vty_interface.c
@@ -0,0 +1,1836 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009-2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <vty/command.h>
+#include <vty/buffer.h>
+#include <vty/vty.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/telnet_interface.h>
+#include <openbsc/vty.h>
+
+static struct gsm_network *gsmnet;
+
+struct cmd_node net_node = {
+	GSMNET_NODE,
+	"%s(network)#",
+	1,
+};
+
+struct cmd_node bts_node = {
+	BTS_NODE,
+	"%s(bts)#",
+	1,
+};
+
+struct cmd_node trx_node = {
+	TRX_NODE,
+	"%s(trx)#",
+	1,
+};
+
+struct cmd_node ts_node = {
+	TS_NODE,
+	"%s(ts)#",
+	1,
+};
+
+static int dummy_config_write(struct vty *v)
+{
+	return CMD_SUCCESS;
+}
+
+static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
+{
+	vty_out(vty,"Oper '%s', Admin %u, Avail '%s'%s",
+		nm_opstate_name(nms->operational), nms->administrative,
+		nm_avail_name(nms->availability), VTY_NEWLINE);
+}
+
+static void dump_pchan_load_vty(struct vty *vty, char *prefix,
+				const struct pchan_load *pl)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pl->pchan); i++) {
+		const struct load_counter *lc = &pl->pchan[i];
+		unsigned int percent;
+
+		if (lc->total == 0)
+			continue;
+
+		percent = (lc->used * 100) / lc->total;
+
+		vty_out(vty, "%s%20s: %3u%% (%u/%u)%s", prefix,
+			gsm_pchan_name(i), percent, lc->used, lc->total,
+			VTY_NEWLINE);
+	}
+}
+
+static void net_dump_vty(struct vty *vty, struct gsm_network *net)
+{
+	struct pchan_load pl;
+
+	vty_out(vty, "BSC is on Country Code %u, Network Code %u "
+		"and has %u BTS%s", net->country_code, net->network_code,
+		net->num_bts, VTY_NEWLINE);
+	vty_out(vty, "  Long network name: '%s'%s",
+		net->name_long, VTY_NEWLINE);
+	vty_out(vty, "  Short network name: '%s'%s",
+		net->name_short, VTY_NEWLINE);
+	vty_out(vty, "  Authentication policy: %s%s",
+		gsm_auth_policy_name(net->auth_policy), VTY_NEWLINE);
+	vty_out(vty, "  Location updating reject cause: %u%s",
+		net->reject_cause, VTY_NEWLINE);
+	vty_out(vty, "  Encryption: A5/%u%s", net->a5_encryption,
+		VTY_NEWLINE);
+	vty_out(vty, "  NECI (TCH/H): %u%s", net->neci,
+		VTY_NEWLINE);
+	vty_out(vty, "  RRLP Mode: %s%s", rrlp_mode_name(net->rrlp.mode),
+		VTY_NEWLINE);
+	vty_out(vty, "  MM Info: %s%s", net->send_mm_info ? "On" : "Off",
+		VTY_NEWLINE);
+	vty_out(vty, "  Handover: %s%s", net->handover.active ? "On" : "Off",
+		VTY_NEWLINE);
+	network_chan_load(&pl, net);
+	vty_out(vty, "  Current Channel Load:%s", VTY_NEWLINE);
+	dump_pchan_load_vty(vty, "    ", &pl);
+}
+
+DEFUN(show_net, show_net_cmd, "show network",
+	SHOW_STR "Display information about a GSM NETWORK\n")
+{
+	struct gsm_network *net = gsmnet;
+	net_dump_vty(vty, net);
+
+	return CMD_SUCCESS;
+}
+
+static void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l)
+{
+	struct e1inp_line *line;
+
+	if (!e1l) {
+		vty_out(vty, "   None%s", VTY_NEWLINE);
+		return;
+	}
+
+	line = e1l->ts->line;
+
+	vty_out(vty, "    E1 Line %u, Type %s: Timeslot %u, Mode %s%s",
+		line->num, line->driver->name, e1l->ts->num,
+		e1inp_signtype_name(e1l->type), VTY_NEWLINE);
+	vty_out(vty, "    E1 TEI %u, SAPI %u%s",
+		e1l->tei, e1l->sapi, VTY_NEWLINE);
+}
+
+static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+	struct pchan_load pl;
+
+	vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, "
+		"BSIC %u, TSC %u and %u TRX%s",
+		bts->nr, btstype2str(bts->type), gsm_band_name(bts->band),
+		bts->cell_identity,
+		bts->location_area_code, bts->bsic, bts->tsc,
+		bts->num_trx, VTY_NEWLINE);
+	vty_out(vty, "MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE);
+	vty_out(vty, "Minimum Rx Level for Access: %i dBm%s",
+		rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min),
+		VTY_NEWLINE);
+	vty_out(vty, "Cell Reselection Hysteresis: %u dBm%s",
+		bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+	vty_out(vty, "RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer,
+		VTY_NEWLINE);
+	vty_out(vty, "RACH Max transmissions: %u%s",
+		rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
+		VTY_NEWLINE);
+	if (bts->si_common.rach_control.cell_bar)
+		vty_out(vty, "  CELL IS BARRED%s", VTY_NEWLINE);
+	if (is_ipaccess_bts(bts))
+		vty_out(vty, "  Unit ID: %u/%u/0, OML Stream ID 0x%02x%s",
+			bts->ip_access.site_id, bts->ip_access.bts_id,
+			bts->oml_tei, VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &bts->nm_state);
+	vty_out(vty, "  Site Mgr NM State: ");
+	net_dump_nmstate(vty, &bts->site_mgr.nm_state);
+	vty_out(vty, "  Paging: FIXME pending requests, %u free slots%s",
+		bts->paging.available_slots, VTY_NEWLINE);
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "  E1 Signalling Link:%s", VTY_NEWLINE);
+		e1isl_dump_vty(vty, bts->oml_link);
+	}
+	/* FIXME: oml_link, chan_desc */
+	memset(&pl, 0, sizeof(pl));
+	bts_chan_load(&pl, bts);
+	vty_out(vty, "  Current Channel Load:%s", VTY_NEWLINE);
+	dump_pchan_load_vty(vty, "    ", &pl);
+}
+
+DEFUN(show_bts, show_bts_cmd, "show bts [number]",
+	SHOW_STR "Display information about a BTS\n"
+		"BTS number")
+{
+	struct gsm_network *net = gsmnet;
+	int bts_nr;
+
+	if (argc != 0) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr > net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+		return CMD_SUCCESS;
+	}
+	/* print all BTS's */
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
+		bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+
+	return CMD_SUCCESS;
+}
+
+/* utility functions */
+static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line,
+			  const char *ts, const char *ss)
+{
+	e1_link->e1_nr = atoi(line);
+	e1_link->e1_ts = atoi(ts);
+	if (!strcmp(ss, "full"))
+		e1_link->e1_ts_ss = 255;
+	else
+		e1_link->e1_ts_ss = atoi(ss);
+}
+
+static void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link,
+				 const char *prefix)
+{
+	if (!e1_link->e1_ts)
+		return;
+
+	if (e1_link->e1_ts_ss == 255)
+		vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s",
+			prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE);
+	else
+		vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s",
+			prefix, e1_link->e1_nr, e1_link->e1_ts,
+			e1_link->e1_ts_ss, VTY_NEWLINE);
+}
+
+
+static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+	vty_out(vty, "    timeslot %u%s", ts->nr, VTY_NEWLINE);
+	if (ts->pchan != GSM_PCHAN_NONE)
+		vty_out(vty, "     phys_chan_config %s%s",
+			gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+	config_write_e1_link(vty, &ts->e1_link, "     ");
+}
+
+static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx)
+{
+	int i;
+
+	vty_out(vty, "  trx %u%s", trx->nr, VTY_NEWLINE);
+	vty_out(vty, "   rf_locked %u%s",
+		trx->nm_state.administrative == NM_STATE_LOCKED ? 1 : 0,
+		VTY_NEWLINE);
+	vty_out(vty, "   arfcn %u%s", trx->arfcn, VTY_NEWLINE);
+	vty_out(vty, "   nominal power %u%s", trx->nominal_power, VTY_NEWLINE);
+	vty_out(vty, "   max_power_red %u%s", trx->max_power_red, VTY_NEWLINE);
+	config_write_e1_link(vty, &trx->rsl_e1_link, "   rsl ");
+	vty_out(vty, "   rsl e1 tei %u%s", trx->rsl_tei, VTY_NEWLINE);
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		config_write_ts_single(vty, &trx->ts[i]);
+}
+
+static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	int i;
+
+	vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE);
+	vty_out(vty, "  type %s%s", btstype2str(bts->type), VTY_NEWLINE);
+	vty_out(vty, "  band %s%s", gsm_band_name(bts->band), VTY_NEWLINE);
+	vty_out(vty, "  cell_identity %u%s", bts->cell_identity, VTY_NEWLINE);
+	vty_out(vty, "  location_area_code %u%s", bts->location_area_code,
+		VTY_NEWLINE);
+	vty_out(vty, "  training_sequence_code %u%s", bts->tsc, VTY_NEWLINE);
+	vty_out(vty, "  base_station_id_code %u%s", bts->bsic, VTY_NEWLINE);
+	vty_out(vty, "  ms max power %u%s", bts->ms_max_power, VTY_NEWLINE);
+	vty_out(vty, "  cell reselection hysteresis %u%s",
+		bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+	vty_out(vty, "  rxlev access min %u%s",
+		bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE);
+	if (bts->si_common.chan_desc.t3212)
+		vty_out(vty, "  periodic location update %u%s",
+			bts->si_common.chan_desc.t3212 * 10, VTY_NEWLINE);
+	vty_out(vty, "  channel allocator %s%s",
+		bts->chan_alloc_reverse ? "descending" : "ascending",
+		VTY_NEWLINE);
+	vty_out(vty, "  rach tx integer %u%s",
+		bts->si_common.rach_control.tx_integer, VTY_NEWLINE);
+	vty_out(vty, "  rach max transmission %u%s",
+		rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
+		VTY_NEWLINE);
+
+	if (bts->rach_b_thresh != -1)
+		vty_out(vty, "  rach nm busy threshold %u%s",
+			bts->rach_b_thresh, VTY_NEWLINE);
+	if (bts->rach_ldavg_slots != -1)
+		vty_out(vty, "  rach nm load average %u%s",
+			bts->rach_ldavg_slots, VTY_NEWLINE);
+	if (bts->si_common.rach_control.cell_bar)
+		vty_out(vty, "  cell barred 1%s", VTY_NEWLINE);
+	if (is_ipaccess_bts(bts)) {
+		vty_out(vty, "  ip.access unit_id %u %u%s",
+			bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE);
+		vty_out(vty, "  oml ip.access stream_id %u%s", bts->oml_tei, VTY_NEWLINE);
+	} else {
+		config_write_e1_link(vty, &bts->oml_e1_link, "  oml ");
+		vty_out(vty, "  oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE);
+	}
+	vty_out(vty, "  gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode),
+		VTY_NEWLINE);
+	if (bts->gprs.mode != BTS_GPRS_NONE) {
+		vty_out(vty, "  gprs routing area %u%s", bts->gprs.rac,
+			VTY_NEWLINE);
+		vty_out(vty, "  gprs cell bvci %u%s", bts->gprs.cell.bvci,
+			VTY_NEWLINE);
+		vty_out(vty, "  gprs nsei %u%s", bts->gprs.nse.nsei,
+			VTY_NEWLINE);
+		for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+			struct gsm_bts_gprs_nsvc *nsvc =
+						&bts->gprs.nsvc[i];
+			struct in_addr ia;
+
+			ia.s_addr = htonl(nsvc->remote_ip);
+			vty_out(vty, "  gprs nsvc %u nsvci %u%s", i,
+				nsvc->nsvci, VTY_NEWLINE);
+			vty_out(vty, "  gprs nsvc %u local udp port %u%s", i,
+				nsvc->local_port, VTY_NEWLINE);
+			vty_out(vty, "  gprs nsvc %u remote udp port %u%s", i,
+				nsvc->remote_port, VTY_NEWLINE);
+			vty_out(vty, "  gprs nsvc %u remote ip %s%s", i,
+				inet_ntoa(ia), VTY_NEWLINE);
+		}
+	}
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		config_write_trx_single(vty, trx);
+}
+
+static int config_write_bts(struct vty *v)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &gsmnet->bts_list, list)
+		config_write_bts_single(v, bts);
+
+	return CMD_SUCCESS;
+}
+
+static int config_write_net(struct vty *vty)
+{
+	vty_out(vty, "network%s", VTY_NEWLINE);
+	vty_out(vty, " network country code %u%s", gsmnet->country_code, VTY_NEWLINE);
+	vty_out(vty, " mobile network code %u%s", gsmnet->network_code, VTY_NEWLINE);
+	vty_out(vty, " short name %s%s", gsmnet->name_short, VTY_NEWLINE);
+	vty_out(vty, " long name %s%s", gsmnet->name_long, VTY_NEWLINE);
+	vty_out(vty, " auth policy %s%s", gsm_auth_policy_name(gsmnet->auth_policy), VTY_NEWLINE);
+	vty_out(vty, " location updating reject cause %u%s",
+		gsmnet->reject_cause, VTY_NEWLINE);
+	vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE);
+	vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE);
+	vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode),
+		VTY_NEWLINE);
+	vty_out(vty, " mm info %u%s", gsmnet->send_mm_info, VTY_NEWLINE);
+	vty_out(vty, " handover %u%s", gsmnet->handover.active, VTY_NEWLINE);
+	vty_out(vty, " handover window rxlev averaging %u%s",
+		gsmnet->handover.win_rxlev_avg, VTY_NEWLINE);
+	vty_out(vty, " handover window rxqual averaging %u%s",
+		gsmnet->handover.win_rxqual_avg, VTY_NEWLINE);
+	vty_out(vty, " handover window rxlev neighbor averaging %u%s",
+		gsmnet->handover.win_rxlev_avg_neigh, VTY_NEWLINE);
+	vty_out(vty, " handover power budget interval %u%s",
+		gsmnet->handover.pwr_interval, VTY_NEWLINE);
+	vty_out(vty, " handover power budget hysteresis %u%s",
+		gsmnet->handover.pwr_hysteresis, VTY_NEWLINE);
+	vty_out(vty, " handover maximum distance %u%s",
+		gsmnet->handover.max_distance, VTY_NEWLINE);
+	vty_out(vty, " timer t3101 %u%s", gsmnet->T3101, VTY_NEWLINE);
+	vty_out(vty, " timer t3103 %u%s", gsmnet->T3103, VTY_NEWLINE);
+	vty_out(vty, " timer t3105 %u%s", gsmnet->T3105, VTY_NEWLINE);
+	vty_out(vty, " timer t3107 %u%s", gsmnet->T3107, VTY_NEWLINE);
+	vty_out(vty, " timer t3109 %u%s", gsmnet->T3109, VTY_NEWLINE);
+	vty_out(vty, " timer t3111 %u%s", gsmnet->T3111, VTY_NEWLINE);
+	vty_out(vty, " timer t3113 %u%s", gsmnet->T3113, VTY_NEWLINE);
+	vty_out(vty, " timer t3115 %u%s", gsmnet->T3115, VTY_NEWLINE);
+	vty_out(vty, " timer t3117 %u%s", gsmnet->T3117, VTY_NEWLINE);
+	vty_out(vty, " timer t3119 %u%s", gsmnet->T3119, VTY_NEWLINE);
+	vty_out(vty, " timer t3141 %u%s", gsmnet->T3141, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx)
+{
+	vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
+		trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
+	vty_out(vty, "  RF Nominal Power: %d dBm, reduced by %u dB, "
+		"resulting BS power: %d dBm%s",
+		trx->nominal_power, trx->max_power_red,
+		trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &trx->nm_state);
+	vty_out(vty, "  Baseband Transceiver NM State: ");
+	net_dump_nmstate(vty, &trx->bb_transc.nm_state);
+	if (is_ipaccess_bts(trx->bts)) {
+		vty_out(vty, "  ip.access stream ID: 0x%02x%s",
+			trx->rsl_tei, VTY_NEWLINE);
+	} else {
+		vty_out(vty, "  E1 Signalling Link:%s", VTY_NEWLINE);
+		e1isl_dump_vty(vty, trx->rsl_link);
+	}
+}
+
+DEFUN(show_trx,
+      show_trx_cmd,
+      "show trx [bts_nr] [trx_nr]",
+	SHOW_STR "Display information about a TRX\n")
+{
+	struct gsm_network *net = gsmnet;
+	struct gsm_bts *bts = NULL;
+	struct gsm_bts_trx *trx;
+	int bts_nr, trx_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+		trx_dump_vty(vty, trx);
+		return CMD_SUCCESS;
+	}
+	if (bts) {
+		/* print all TRX in this BTS */
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			trx_dump_vty(vty, trx);
+		}
+		return CMD_SUCCESS;
+	}
+
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			trx_dump_vty(vty, trx);
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+	vty_out(vty, "Timeslot %u of TRX %u in BTS %u, phys cfg %s%s",
+		ts->nr, ts->trx->nr, ts->trx->bts->nr,
+		gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &ts->nm_state);
+	if (!is_ipaccess_bts(ts->trx->bts))
+		vty_out(vty, "  E1 Line %u, Timeslot %u, Subslot %u%s",
+			ts->e1_link.e1_nr, ts->e1_link.e1_ts,
+			ts->e1_link.e1_ts_ss, VTY_NEWLINE);
+}
+
+DEFUN(show_ts,
+      show_ts_cmd,
+      "show timeslot [bts_nr] [trx_nr] [ts_nr]",
+	SHOW_STR "Display information about a TS\n")
+{
+	struct gsm_network *net = gsmnet;
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	int bts_nr, trx_nr, ts_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+	}
+	if (argc >= 3) {
+		ts_nr = atoi(argv[2]);
+		if (ts_nr >= TRX_NR_TS) {
+			vty_out(vty, "%% can't find TS '%s'%s", argv[2],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &trx->ts[ts_nr];
+		ts_dump_vty(vty, ts);
+		return CMD_SUCCESS;
+	}
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+				ts = &trx->ts[ts_nr];
+				ts_dump_vty(vty, ts);
+			}
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void subscr_dump_vty(struct vty *vty, struct gsm_subscriber *subscr)
+{
+	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
+		subscr->authorized, VTY_NEWLINE);
+	if (subscr->name)
+		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
+	if (subscr->extension)
+		vty_out(vty, "    Extension: %s%s", subscr->extension,
+			VTY_NEWLINE);
+	if (subscr->imsi)
+		vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+	if (subscr->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+			VTY_NEWLINE);
+
+	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+static void meas_rep_dump_uni_vty(struct vty *vty,
+				  struct gsm_meas_rep_unidir *mru,
+				  const char *prefix,
+				  const char *dir)
+{
+	vty_out(vty, "%s  RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ",
+		prefix, dir, rxlev2dbm(mru->full.rx_lev),
+			dir, rxlev2dbm(mru->sub.rx_lev));
+	vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s",
+		dir, mru->full.rx_qual, dir, mru->sub.rx_qual,
+		VTY_NEWLINE);
+}
+
+static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr,
+			      const char *prefix)
+{
+	vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE);
+	vty_out(vty, "%s  Flags: %s%s%s%s%s", prefix,
+			mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "",
+			mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "",
+			mr->flags & MEAS_REP_F_FPC ? "FPC " : "",
+			mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ",
+			VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_MS_TO)
+		vty_out(vty, "%s  MS Timing Offset: %u%s", prefix,
+			mr->ms_timing_offset, VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_MS_L1)
+		vty_out(vty, "%s  L1 MS Power: %u dBm, Timing Advance: %u%s",
+			prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_DL_VALID)
+		meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl");
+	meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul");
+}
+
+static void lchan_dump_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+	int idx;
+
+	vty_out(vty, "Lchan %u in Timeslot %u of TRX %u in BTS %u, Type %s%s",
+		lchan->nr, lchan->ts->nr, lchan->ts->trx->nr,
+		lchan->ts->trx->bts->nr, gsm_lchant_name(lchan->type),
+		VTY_NEWLINE);
+	vty_out(vty, "  Use Count: %u, State: %s%s", lchan->conn.use_count,
+		gsm_lchans_name(lchan->state), VTY_NEWLINE);
+	vty_out(vty, "  BS Power: %u dBm, MS Power: %u dBm%s",
+		lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red
+		- lchan->bs_power*2,
+		ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+		VTY_NEWLINE);
+	if (lchan->conn.subscr) {
+		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
+		subscr_dump_vty(vty, lchan->conn.subscr);
+	} else
+		vty_out(vty, "  No Subscriber%s", VTY_NEWLINE);
+	if (is_ipaccess_bts(lchan->ts->trx->bts)) {
+		struct in_addr ia;
+		ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+		vty_out(vty, "  Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s",
+			inet_ntoa(ia), lchan->abis_ip.bound_port,
+			lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id,
+			VTY_NEWLINE);
+	}
+
+	/* we want to report the last measurement report */
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+			       lchan->meas_rep_idx, 1);
+	meas_rep_dump_vty(vty, &lchan->meas_rep[idx], "  ");
+}
+
+#if 0
+TODO: callref and remote callref of call must be resolved to get gsm_trans object
+static void call_dump_vty(struct vty *vty, struct gsm_call *call)
+{
+	vty_out(vty, "Call Type %u, State %u, Transaction ID %u%s",
+		call->type, call->state, call->transaction_id, VTY_NEWLINE);
+
+	if (call->local_lchan) {
+		vty_out(vty, "Call Local Channel:%s", VTY_NEWLINE);
+		lchan_dump_vty(vty, call->local_lchan);
+	} else
+		vty_out(vty, "Call has no Local Channel%s", VTY_NEWLINE);
+
+	if (call->remote_lchan) {
+		vty_out(vty, "Call Remote Channel:%s", VTY_NEWLINE);
+		lchan_dump_vty(vty, call->remote_lchan);
+	} else
+		vty_out(vty, "Call has no Remote Channel%s", VTY_NEWLINE);
+
+	if (call->called_subscr) {
+		vty_out(vty, "Called Subscriber:%s", VTY_NEWLINE);
+		subscr_dump_vty(vty, call->called_subscr);
+	} else
+		vty_out(vty, "Call has no Called Subscriber%s", VTY_NEWLINE);
+}
+#endif
+
+DEFUN(show_lchan,
+      show_lchan_cmd,
+      "show lchan [bts_nr] [trx_nr] [ts_nr] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n")
+{
+	struct gsm_network *net = gsmnet;
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_lchan *lchan;
+	int bts_nr, trx_nr, ts_nr, lchan_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS %s%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX %s%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+	}
+	if (argc >= 3) {
+		ts_nr = atoi(argv[2]);
+		if (ts_nr >= TRX_NR_TS) {
+			vty_out(vty, "%% can't find TS %s%s", argv[2],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &trx->ts[ts_nr];
+	}
+	if (argc >= 4) {
+		lchan_nr = atoi(argv[3]);
+		if (lchan_nr >= TS_MAX_LCHAN) {
+			vty_out(vty, "%% can't find LCHAN %s%s", argv[3],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		lchan = &ts->lchan[lchan_nr];
+		lchan_dump_vty(vty, lchan);
+		return CMD_SUCCESS;
+	}
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+				ts = &trx->ts[ts_nr];
+				for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN;
+				     lchan_nr++) {
+					lchan = &ts->lchan[lchan_nr];
+					if (lchan->type == GSM_LCHAN_NONE)
+						continue;
+					lchan_dump_vty(vty, lchan);
+				}
+			}
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void e1drv_dump_vty(struct vty *vty, struct e1inp_driver *drv)
+{
+	vty_out(vty, "E1 Input Driver %s%s", drv->name, VTY_NEWLINE);
+}
+
+DEFUN(show_e1drv,
+      show_e1drv_cmd,
+      "show e1_driver",
+	SHOW_STR "Display information about available E1 drivers\n")
+{
+	struct e1inp_driver *drv;
+
+	llist_for_each_entry(drv, &e1inp_driver_list, list)
+		e1drv_dump_vty(vty, drv);
+
+	return CMD_SUCCESS;
+}
+
+static void e1line_dump_vty(struct vty *vty, struct e1inp_line *line)
+{
+	vty_out(vty, "E1 Line Number %u, Name %s, Driver %s%s",
+		line->num, line->name ? line->name : "",
+		line->driver->name, VTY_NEWLINE);
+}
+
+DEFUN(show_e1line,
+      show_e1line_cmd,
+      "show e1_line [line_nr]",
+	SHOW_STR "Display information about a E1 line\n")
+{
+	struct e1inp_line *line;
+
+	if (argc >= 1) {
+		int num = atoi(argv[0]);
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			if (line->num == num) {
+				e1line_dump_vty(vty, line);
+				return CMD_SUCCESS;
+			}
+		}
+		return CMD_WARNING;
+	}	
+	
+	llist_for_each_entry(line, &e1inp_line_list, list)
+		e1line_dump_vty(vty, line);
+
+	return CMD_SUCCESS;
+}
+
+static void e1ts_dump_vty(struct vty *vty, struct e1inp_ts *ts)
+{
+	if (ts->type == E1INP_TS_TYPE_NONE)
+		return;
+	vty_out(vty, "E1 Timeslot %2u of Line %u is Type %s%s",
+		ts->num, ts->line->num, e1inp_tstype_name(ts->type),
+		VTY_NEWLINE);
+}
+
+DEFUN(show_e1ts,
+      show_e1ts_cmd,
+      "show e1_timeslot [line_nr] [ts_nr]",
+	SHOW_STR "Display information about a E1 timeslot\n")
+{
+	struct e1inp_line *line = NULL;
+	struct e1inp_ts *ts;
+	int ts_nr;
+
+	if (argc == 0) {
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			for (ts_nr = 0; ts_nr < NUM_E1_TS; ts_nr++) {
+				ts = &line->ts[ts_nr];
+				e1ts_dump_vty(vty, ts);
+			}
+		}
+		return CMD_SUCCESS;
+	}
+	if (argc >= 1) {
+		int num = atoi(argv[0]);
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			if (line->num == num)
+				break;
+		}
+		if (!line || line->num != num) {
+			vty_out(vty, "E1 line %s is invalid%s",
+				argv[0], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}	
+	if (argc >= 2) {
+		ts_nr = atoi(argv[1]);
+		if (ts_nr > NUM_E1_TS) {
+			vty_out(vty, "E1 timeslot %s is invalid%s",
+				argv[1], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &line->ts[ts_nr];
+		e1ts_dump_vty(vty, ts);
+		return CMD_SUCCESS;
+	} else {
+		for (ts_nr = 0; ts_nr < NUM_E1_TS; ts_nr++) {
+			ts = &line->ts[ts_nr];
+			e1ts_dump_vty(vty, ts);
+		}
+		return CMD_SUCCESS;
+	}
+	return CMD_SUCCESS;
+}
+
+static void paging_dump_vty(struct vty *vty, struct gsm_paging_request *pag)
+{
+	vty_out(vty, "Paging on BTS %u%s", pag->bts->nr, VTY_NEWLINE);
+	subscr_dump_vty(vty, pag->subscr);
+}
+
+static void bts_paging_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+	struct gsm_paging_request *pag;
+
+	llist_for_each_entry(pag, &bts->paging.pending_requests, entry)
+		paging_dump_vty(vty, pag);
+}
+
+DEFUN(show_paging,
+      show_paging_cmd,
+      "show paging [bts_nr]",
+	SHOW_STR "Display information about paging reuqests of a BTS\n")
+{
+	struct gsm_network *net = gsmnet;
+	struct gsm_bts *bts;
+	int bts_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS %s%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+		bts_paging_dump_vty(vty, bts);
+		
+		return CMD_SUCCESS;
+	}
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		bts_paging_dump_vty(vty, bts);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net,
+      cfg_net_cmd,
+      "network",
+      "Configure the GSM network")
+{
+	vty->index = gsmnet;
+	vty->node = GSMNET_NODE;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_net_ncc,
+      cfg_net_ncc_cmd,
+      "network country code <1-999>",
+      "Set the GSM network country code")
+{
+	gsmnet->country_code = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_mnc,
+      cfg_net_mnc_cmd,
+      "mobile network code <1-999>",
+      "Set the GSM mobile network code")
+{
+	gsmnet->network_code = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_name_short,
+      cfg_net_name_short_cmd,
+      "short name NAME",
+      "Set the short GSM network name")
+{
+	if (gsmnet->name_short)
+		talloc_free(gsmnet->name_short);
+
+	gsmnet->name_short = talloc_strdup(gsmnet, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_name_long,
+      cfg_net_name_long_cmd,
+      "long name NAME",
+      "Set the long GSM network name")
+{
+	if (gsmnet->name_long)
+		talloc_free(gsmnet->name_long);
+
+	gsmnet->name_long = talloc_strdup(gsmnet, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_auth_policy,
+      cfg_net_auth_policy_cmd,
+      "auth policy (closed|accept-all|token)",
+      "Set the GSM network authentication policy\n")
+{
+	enum gsm_auth_policy policy = gsm_auth_policy_parse(argv[0]);
+
+	gsmnet->auth_policy = policy;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_reject_cause,
+      cfg_net_reject_cause_cmd,
+      "location updating reject cause <2-111>",
+      "Set the reject cause of location updating reject\n")
+{
+	gsmnet->reject_cause = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_encryption,
+      cfg_net_encryption_cmd,
+      "encryption a5 (0|1|2)",
+      "Enable or disable encryption (A5) for this network\n")
+{
+	gsmnet->a5_encryption= atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_neci,
+      cfg_net_neci_cmd,
+      "neci (0|1)",
+      "Set if NECI of cell selection is to be set")
+{
+	gsmnet->neci = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rrlp_mode, cfg_net_rrlp_mode_cmd,
+      "rrlp mode (none|ms-based|ms-preferred|ass-preferred)",
+	"Set the Radio Resource Location Protocol Mode")
+{
+	gsmnet->rrlp.mode = rrlp_mode_parse(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_mm_info, cfg_net_mm_info_cmd,
+      "mm info (0|1)",
+	"Whether to send MM INFO after LOC UPD ACCEPT")
+{
+	gsmnet->send_mm_info = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_handover, cfg_net_handover_cmd,
+      "handover (0|1)",
+	"Whether or not to use in-call handover")
+{
+	int enable = atoi(argv[0]);
+
+	if (enable && ipacc_rtp_direct) {
+		vty_out(vty, "%% Cannot enable handover unless RTP Proxy mode "
+			"is enabled by using the -P command line option%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	gsmnet->handover.active = enable;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxlev_avg, cfg_net_ho_win_rxlev_avg_cmd,
+      "handover window rxlev averaging <1-10>",
+	"How many RxLev measurements are used for averaging")
+{
+	gsmnet->handover.win_rxlev_avg = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxqual_avg, cfg_net_ho_win_rxqual_avg_cmd,
+      "handover window rxqual averaging <1-10>",
+	"How many RxQual measurements are used for averaging")
+{
+	gsmnet->handover.win_rxqual_avg = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxlev_neigh_avg, cfg_net_ho_win_rxlev_avg_neigh_cmd,
+      "handover window rxlev neighbor averaging <1-10>",
+	"How many RxQual measurements are used for averaging")
+{
+	gsmnet->handover.win_rxlev_avg_neigh = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_pwr_interval, cfg_net_ho_pwr_interval_cmd,
+      "handover power budget interval <1-99>",
+	"How often to check if we have a better cell (SACCH frames)")
+{
+	gsmnet->handover.pwr_interval = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_pwr_hysteresis, cfg_net_ho_pwr_hysteresis_cmd,
+      "handover power budget hysteresis <0-999>",
+	"How many dB does a neighbor to be stronger to become a HO candidate")
+{
+	gsmnet->handover.pwr_hysteresis = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd,
+      "handover maximum distance <0-9999>",
+	"How big is the maximum timing advance before HO is forced")
+{
+	gsmnet->handover.max_distance = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+#define DECLARE_TIMER(number, doc) \
+    DEFUN(cfg_net_T##number,					\
+      cfg_net_T##number##_cmd,					\
+      "timer t" #number  " <0-65535>",				\
+      doc)							\
+{								\
+	int value = atoi(argv[0]);				\
+								\
+	if (value < 0 || value > 65535) {			\
+		vty_out(vty, "Timer value %s out of range.%s",	\
+		        argv[0], VTY_NEWLINE);			\
+		return CMD_WARNING;				\
+	}							\
+								\
+	gsmnet->T##number = value;				\
+	return CMD_SUCCESS;					\
+}
+
+DECLARE_TIMER(3101, "Set the timeout value for IMMEDIATE ASSIGNMENT.")
+DECLARE_TIMER(3103, "Set the timeout value for HANDOVER.")
+DECLARE_TIMER(3105, "Currently not used.")
+DECLARE_TIMER(3107, "Currently not used.")
+DECLARE_TIMER(3109, "Currently not used.")
+DECLARE_TIMER(3111, "Currently not used.")
+DECLARE_TIMER(3113, "Set the time to try paging a subscriber.")
+DECLARE_TIMER(3115, "Currently not used.")
+DECLARE_TIMER(3117, "Currently not used.")
+DECLARE_TIMER(3119, "Currently not used.")
+DECLARE_TIMER(3141, "Currently not used.")
+
+
+/* per-BTS configuration */
+DEFUN(cfg_bts,
+      cfg_bts_cmd,
+      "bts BTS_NR",
+      "Select a BTS to configure\n")
+{
+	int bts_nr = atoi(argv[0]);
+	struct gsm_bts *bts;
+
+	if (bts_nr > gsmnet->num_bts) {
+		vty_out(vty, "%% The next unused BTS number is %u%s",
+			gsmnet->num_bts, VTY_NEWLINE);
+		return CMD_WARNING;
+	} else if (bts_nr == gsmnet->num_bts) {
+		/* allocate a new one */
+		bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_UNKNOWN,
+				    HARDCODED_TSC, HARDCODED_BSIC);
+	} else
+		bts = gsm_bts_num(gsmnet, bts_nr);
+
+	if (!bts) {
+		vty_out(vty, "%% Unable to allocate BTS %u%s",
+			gsmnet->num_bts, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->index = bts;
+	vty->node = BTS_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_type,
+      cfg_bts_type_cmd,
+      "type TYPE",
+      "Set the BTS type\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int rc;
+
+	rc = gsm_set_bts_type(bts, parse_btstype(argv[0]));
+	if (rc < 0)
+		return CMD_WARNING;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_band,
+      cfg_bts_band_cmd,
+      "band BAND",
+      "Set the frequency band of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int band = gsm_band_parse(argv[0]);
+
+	if (band < 0) {
+		vty_out(vty, "%% BAND %d is not a valid GSM band%s",
+			band, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->band = band;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ci,
+      cfg_bts_ci_cmd,
+      "cell_identity <0-65535>",
+      "Set the Cell identity of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int ci = atoi(argv[0]);
+
+	if (ci < 0 || ci > 0xffff) {
+		vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s",
+			ci, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->cell_identity = ci;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_lac,
+      cfg_bts_lac_cmd,
+      "location_area_code <0-65535>",
+      "Set the Location Area Code (LAC) of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int lac = atoi(argv[0]);
+
+	if (lac < 0 || lac > 0xffff) {
+		vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+		vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->location_area_code = lac;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_tsc,
+      cfg_bts_tsc_cmd,
+      "training_sequence_code <0-255>",
+      "Set the Training Sequence Code (TSC) of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int tsc = atoi(argv[0]);
+
+	if (tsc < 0 || tsc > 0xff) {
+		vty_out(vty, "%% TSC %d is not in the valid range (0-255)%s",
+			tsc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->tsc = tsc;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_bsic,
+      cfg_bts_bsic_cmd,
+      "base_station_id_code <0-63>",
+      "Set the Base Station Identity Code (BSIC) of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int bsic = atoi(argv[0]);
+
+	if (bsic < 0 || bsic > 0x3f) {
+		vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s",
+			bsic, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->bsic = bsic;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_unit_id,
+      cfg_bts_unit_id_cmd,
+      "ip.access unit_id <0-65534> <0-255>",
+      "Set the ip.access BTS Unit ID of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int site_id = atoi(argv[0]);
+	int bts_id = atoi(argv[1]);
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->ip_access.site_id = site_id;
+	bts->ip_access.bts_id = bts_id;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_stream_id,
+      cfg_bts_stream_id_cmd,
+      "oml ip.access stream_id <0-255>",
+      "Set the ip.access Stream ID of the OML link of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int stream_id = atoi(argv[0]);
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->oml_tei = stream_id;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_oml_e1,
+      cfg_bts_oml_e1_cmd,
+      "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+      "E1 interface to be used for OML\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_oml_e1_tei,
+      cfg_bts_oml_e1_tei_cmd,
+      "oml e1 tei <0-63>",
+      "Set the TEI to be used for OML")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->oml_tei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_challoc, cfg_bts_challoc_cmd,
+      "channel allocator (ascending|descending)",
+      "Should the channel allocator allocate in reverse TRX order?")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (!strcmp(argv[0], "ascending"))
+		bts->chan_alloc_reverse = 0;
+	else
+		bts->chan_alloc_reverse = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_tx_integer,
+      cfg_bts_rach_tx_integer_cmd,
+      "rach tx integer <0-15>",
+      "Set the raw tx integer value in RACH Control parameters IE")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_max_trans,
+      cfg_bts_rach_max_trans_cmd,
+      "rach max transmission (1|2|4|7)",
+      "Set the maximum number of RACH burst transmissions")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_nm_b_thresh,
+      cfg_bts_rach_nm_b_thresh_cmd,
+      "rach nm busy threshold <0-255>",
+      "Set the NM Busy Threshold in DB")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->rach_b_thresh = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_nm_ldavg,
+      cfg_bts_rach_nm_ldavg_cmd,
+      "rach nm load average <0-65535>",
+      "Set the NM Loadaver Slots value")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->rach_ldavg_slots = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd,
+      "cell barred (0|1)",
+      "Should this cell be barred from access?")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.rach_control.cell_bar = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd,
+      "ms max power <0-40>",
+      "Maximum transmit power of the MS")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->ms_max_power = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd,
+      "cell reselection hysteresis <0-14>",
+      "Cell Re-Selection Hysteresis in dB")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd,
+      "rxlev access min <0-63>",
+      "Minimum RxLev needed for cell access (better than -110dBm)")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_per_loc_upd, cfg_bts_per_loc_upd_cmd,
+      "periodic location update <0-1530>",
+      "Periodic Location Updating Interval in Minutes")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.chan_desc.t3212 = atoi(argv[0]) / 10;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd,
+	"gprs cell bvci <2-65535>",
+	"GPRS BSSGP VC Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.cell.bvci = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd,
+	"gprs nsei <0-65535>",
+	"GPRS NS Entity Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nse.nsei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd,
+	"gprs nsvc <0-1> nsvci <0-65535>",
+	"GPRS NS VC Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].nsvci = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd,
+	"gprs nsvc <0-1> local udp port <0-65535>",
+	"GPRS NS Local UDP Port")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].local_port = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd,
+	"gprs nsvc <0-1> remote udp port <0-65535>",
+	"GPRS NS Remote UDP Port")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].remote_port = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd,
+	"gprs nsvc <0-1> remote ip A.B.C.D",
+	"GPRS NS Remote IP Address")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+	struct in_addr ia;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	inet_aton(argv[1], &ia);
+	bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd,
+	"gprs routing area <0-255>",
+	"GPRS Routing Area Code")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.rac = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd,
+	"gprs mode (none|gprs|egprs)",
+	"GPRS Mode for this BTS")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->gprs.mode = bts_gprs_mode_parse(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+
+/* per TRX configuration */
+DEFUN(cfg_trx,
+      cfg_trx_cmd,
+      "trx TRX_NR",
+      "Select a TRX to configure")
+{
+	int trx_nr = atoi(argv[0]);
+	struct gsm_bts *bts = vty->index;
+	struct gsm_bts_trx *trx;
+
+	if (trx_nr > bts->num_trx) {
+		vty_out(vty, "%% The next unused TRX number in this BTS is %u%s",
+			bts->num_trx, VTY_NEWLINE);
+		return CMD_WARNING;
+	} else if (trx_nr == bts->num_trx) {
+		/* we need to allocate a new one */
+		trx = gsm_bts_trx_alloc(bts);
+	} else
+		trx = gsm_bts_trx_num(bts, trx_nr);
+
+	if (!trx)
+		return CMD_WARNING;
+
+	vty->index = trx;
+	vty->node = TRX_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_arfcn,
+      cfg_trx_arfcn_cmd,
+      "arfcn <1-1024>",
+      "Set the ARFCN for this TRX\n")
+{
+	int arfcn = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+
+	/* FIXME: check if this ARFCN is supported by this TRX */
+
+	trx->arfcn = arfcn;
+
+	/* FIXME: patch ARFCN into SYSTEM INFORMATION */
+	/* FIXME: use OML layer to update the ARFCN */
+	/* FIXME: use RSL layer to update SYSTEM INFORMATION */
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_nominal_power,
+      cfg_trx_nominal_power_cmd,
+      "nominal power <0-100>",
+      "Nominal TRX RF Power in dB\n")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	trx->nominal_power = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_max_power_red,
+      cfg_trx_max_power_red_cmd,
+      "max_power_red <0-100>",
+      "Reduction of maximum BS RF Power in dB\n")
+{
+	int maxpwr_r = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+	int upper_limit = 24;	/* default 12.21 max power red. */
+
+	/* FIXME: check if our BTS type supports more than 12 */
+	if (maxpwr_r < 0 || maxpwr_r > upper_limit) {
+		vty_out(vty, "%% Power %d dB is not in the valid range%s",
+			maxpwr_r, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (maxpwr_r & 1) {
+		vty_out(vty, "%% Power %d dB is not an even value%s",
+			maxpwr_r, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	trx->max_power_red = maxpwr_r;
+
+	/* FIXME: make sure we update this using OML */
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rsl_e1,
+      cfg_trx_rsl_e1_cmd,
+      "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+      "E1 interface to be used for RSL\n")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rsl_e1_tei,
+      cfg_trx_rsl_e1_tei_cmd,
+      "rsl e1 tei <0-63>",
+      "Set the TEI to be used for RSL")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	trx->rsl_tei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rf_locked,
+      cfg_trx_rf_locked_cmd,
+      "rf_locked (0|1)",
+      "Turn off RF of the TRX.\n")
+{
+	int locked = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+
+	gsm_trx_lock_rf(trx, locked);
+	return CMD_SUCCESS;
+}
+
+/* per TS configuration */
+DEFUN(cfg_ts,
+      cfg_ts_cmd,
+      "timeslot <0-7>",
+      "Select a Timeslot to configure")
+{
+	int ts_nr = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+	struct gsm_bts_trx_ts *ts;
+
+	if (ts_nr >= TRX_NR_TS) {
+		vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s",
+			TRX_NR_TS, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	ts = &trx->ts[ts_nr];
+
+	vty->index = ts;
+	vty->node = TS_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_pchan,
+      cfg_ts_pchan_cmd,
+      "phys_chan_config PCHAN",
+      "Physical Channel configuration (TCH/SDCCH/...)")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int pchanc;
+
+	pchanc = gsm_pchan_parse(argv[0]);
+	if (pchanc < 0)
+		return CMD_WARNING;
+
+	ts->pchan = pchanc;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_e1_subslot,
+      cfg_ts_e1_subslot_cmd,
+      "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+      "E1 sub-slot connected to this on-air timeslot")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+
+	parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+extern int bsc_vty_init_extra(struct gsm_network *net);
+
+int bsc_vty_init(struct gsm_network *net)
+{
+	gsmnet = net;
+
+	cmd_init(1);
+	vty_init();
+
+	install_element(VIEW_NODE, &show_net_cmd);
+	install_element(VIEW_NODE, &show_bts_cmd);
+	install_element(VIEW_NODE, &show_trx_cmd);
+	install_element(VIEW_NODE, &show_ts_cmd);
+	install_element(VIEW_NODE, &show_lchan_cmd);
+
+	install_element(VIEW_NODE, &show_e1drv_cmd);
+	install_element(VIEW_NODE, &show_e1line_cmd);
+	install_element(VIEW_NODE, &show_e1ts_cmd);
+
+	install_element(VIEW_NODE, &show_paging_cmd);
+
+	openbsc_vty_add_cmds();
+
+	install_element(CONFIG_NODE, &cfg_net_cmd);
+	install_node(&net_node, config_write_net);
+	install_default(GSMNET_NODE);
+	install_element(GSMNET_NODE, &cfg_net_ncc_cmd);
+	install_element(GSMNET_NODE, &cfg_net_mnc_cmd);
+	install_element(GSMNET_NODE, &cfg_net_name_short_cmd);
+	install_element(GSMNET_NODE, &cfg_net_name_long_cmd);
+	install_element(GSMNET_NODE, &cfg_net_auth_policy_cmd);
+	install_element(GSMNET_NODE, &cfg_net_reject_cause_cmd);
+	install_element(GSMNET_NODE, &cfg_net_encryption_cmd);
+	install_element(GSMNET_NODE, &cfg_net_neci_cmd);
+	install_element(GSMNET_NODE, &cfg_net_rrlp_mode_cmd);
+	install_element(GSMNET_NODE, &cfg_net_mm_info_cmd);
+	install_element(GSMNET_NODE, &cfg_net_handover_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxqual_avg_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_neigh_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_pwr_interval_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_pwr_hysteresis_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_max_distance_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3101_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3103_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3105_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3107_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3109_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3111_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3113_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3115_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3117_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3119_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3141_cmd);
+
+	install_element(GSMNET_NODE, &cfg_bts_cmd);
+	install_node(&bts_node, config_write_bts);
+	install_default(BTS_NODE);
+	install_element(BTS_NODE, &cfg_bts_type_cmd);
+	install_element(BTS_NODE, &cfg_bts_band_cmd);
+	install_element(BTS_NODE, &cfg_bts_ci_cmd);
+	install_element(BTS_NODE, &cfg_bts_lac_cmd);
+	install_element(BTS_NODE, &cfg_bts_tsc_cmd);
+	install_element(BTS_NODE, &cfg_bts_bsic_cmd);
+	install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
+	install_element(BTS_NODE, &cfg_bts_stream_id_cmd);
+	install_element(BTS_NODE, &cfg_bts_oml_e1_cmd);
+	install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd);
+	install_element(BTS_NODE, &cfg_bts_challoc_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_barred_cmd);
+	install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd);
+	install_element(BTS_NODE, &cfg_bts_per_loc_upd_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd);
+	install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd);
+
+	install_element(BTS_NODE, &cfg_trx_cmd);
+	install_node(&trx_node, dummy_config_write);
+	install_default(TRX_NODE);
+	install_element(TRX_NODE, &cfg_trx_arfcn_cmd);
+	install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+	install_element(TRX_NODE, &cfg_trx_max_power_red_cmd);
+	install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd);
+	install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd);
+	install_element(TRX_NODE, &cfg_trx_rf_locked_cmd);
+
+	install_element(TRX_NODE, &cfg_ts_cmd);
+	install_node(&ts_node, dummy_config_write);
+	install_default(TS_NODE);
+	install_element(TS_NODE, &cfg_ts_pchan_cmd);
+	install_element(TS_NODE, &cfg_ts_e1_subslot_cmd);
+
+	bsc_vty_init_extra(net);
+
+	return 0;
+}
diff --git a/openbsc/src/vty_interface_cmds.c b/openbsc/src/vty_interface_cmds.c
new file mode 100644
index 0000000..3181892
--- /dev/null
+++ b/openbsc/src/vty_interface_cmds.c
@@ -0,0 +1,337 @@
+/* OpenBSC logging helper for the VTY */
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/vty.h>
+#include <openbsc/telnet_interface.h>
+
+#include <osmocore/talloc.h>
+
+#include <vty/command.h>
+#include <vty/buffer.h>
+#include <vty/vty.h>
+
+#include <stdlib.h>
+
+#define LOGGING_STR	"Configure log message to this terminal\n"
+
+static void _vty_output(struct log_target *tgt, const char *line)
+{
+	struct vty *vty = tgt->tgt_vty.vty;
+	vty_out(vty, "%s", line);
+	/* This is an ugly hack, but there is no easy way... */
+	if (strchr(line, '\n'))
+		vty_out(vty, "\r");
+}
+
+struct log_target *log_target_create_vty(struct vty *vty)
+{
+	struct log_target *target;
+
+	target = log_target_create();
+	if (!target)
+		return NULL;
+
+	target->tgt_vty.vty = vty;
+	target->output = _vty_output;
+	return target;
+}
+
+DEFUN(enable_logging,
+      enable_logging_cmd,
+      "logging enable",
+	LOGGING_STR
+      "Enables logging to this vty\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (conn->dbg) {
+		vty_out(vty, "Logging already enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	conn->dbg = log_target_create_vty(vty);
+	if (!conn->dbg)
+		return CMD_WARNING;
+
+	log_add_target(conn->dbg);
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_fltr_imsi,
+      logging_fltr_imsi_cmd,
+      "logging filter imsi IMSI",
+	LOGGING_STR
+      "Print all messages related to a IMSI\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_set_imsi_filter(conn->dbg, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_fltr_all,
+      logging_fltr_all_cmd,
+      "logging filter all <0-1>",
+	LOGGING_STR
+      "Print all messages to the console\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_set_all_filter(conn->dbg, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_use_clr,
+      logging_use_clr_cmd,
+      "logging color <0-1>",
+	LOGGING_STR
+      "Use color for printing messages\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_set_use_color(conn->dbg, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_prnt_timestamp,
+      logging_prnt_timestamp_cmd,
+      "logging timestamp <0-1>",
+	LOGGING_STR
+      "Print the timestamp of each message\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_set_print_timestamp(conn->dbg, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+/* FIXME: those have to be kept in sync with the log levels and categories */
+#define VTY_DEBUG_CATEGORIES "(rll|cc|mm|rr|rsl|nm|sms|pag|mncc|inp|mi|mib|mux|meas|sccp|msc|mgcp|ho|db|ref|gprs|ns|bssgp|all)"
+#define CATEGORIES_HELP	\
+	"A-bis Radio Link Layer (RLL)\n"			\
+	"Layer3 Call Control (CC)\n"				\
+	"Layer3 Mobility Management (MM)\n"			\
+	"Layer3 Radio Resource (RR)\n"				\
+	"A-bis Radio Signalling Link (RSL)\n"			\
+	"A-bis Network Management / O&M (NM/OML)\n"		\
+	"Layer3 Short Messagaging Service (SMS)\n"		\
+	"Paging Subsystem\n"					\
+	"MNCC API for Call Control application\n"		\
+	"A-bis Input Subsystem\n"				\
+	"A-bis Input Driver for Signalling\n"			\
+	"A-bis Input Driver for B-Channel (voice data)\n"	\
+	"A-bis B-Channel / Sub-channel Multiplexer\n"		\
+	"Radio Measurements\n"					\
+	"SCCP\n"						\
+	"Mobile Switching Center\n"				\
+	"Media Gateway Control Protocol\n"			\
+	"Hand-over\n"						\
+	"Database Layer\n"					\
+	"Reference Counting\n"					\
+	"GPRS Core\n"						\
+	"GPRS Network Service (NS)\n"				\
+	"GPRS BSS Gateway Protocol (BSSGP)\n"			\
+	"Global setting for all subsytems\n"
+
+#define VTY_DEBUG_LEVELS "(everything|debug|info|notice|error|fatal)"
+#define LEVELS_HELP	\
+	"Log simply everything\n"				\
+	"Log debug messages and higher levels\n"		\
+	"Log informational messages and higher levels\n"	\
+	"Log noticable messages and higher levels\n"		\
+	"Log error messages and higher levels\n"		\
+	"Log only fatal messages\n"
+DEFUN(logging_level,
+      logging_level_cmd,
+      "logging level " VTY_DEBUG_CATEGORIES " " VTY_DEBUG_LEVELS,
+      LOGGING_STR
+      "Set the log level for a specified category\n"
+      CATEGORIES_HELP
+      LEVELS_HELP)
+{
+	struct telnet_connection *conn;
+	int category = log_parse_category(argv[0]);
+	int level = log_parse_level(argv[1]);
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (level < 0) {
+		vty_out(vty, "Invalid level `%s'%s", argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Check for special case where we want to set global log level */
+	if (!strcmp(argv[0], "all")) {
+		log_set_log_level(conn->dbg, level);
+		return CMD_SUCCESS;
+	}
+
+	if (category < 0) {
+		vty_out(vty, "Invalid category `%s'%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	conn->dbg->categories[category].enabled = 1;
+	conn->dbg->categories[category].loglevel = level;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_set_category_mask,
+      logging_set_category_mask_cmd,
+      "logging set log mask MASK",
+	LOGGING_STR
+      "Decide which categories to output.\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_parse_category_mask(conn->dbg, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(diable_logging,
+      disable_logging_cmd,
+      "logging disable",
+	LOGGING_STR
+      "Disables logging to this vty\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_del_target(conn->dbg);
+	talloc_free(conn->dbg);
+	conn->dbg = NULL;
+	return CMD_SUCCESS;
+}
+
+static void vty_print_logtarget(struct vty *vty, const struct log_info *info,
+				const struct log_target *tgt)
+{
+	unsigned int i;
+
+	vty_out(vty, " Global Loglevel: %s%s",
+		log_level_str(tgt->loglevel), VTY_NEWLINE);
+	vty_out(vty, " Use color: %s, Print Timestamp: %s%s",
+		tgt->use_color ? "On" : "Off",
+		tgt->print_timestamp ? "On" : "Off", VTY_NEWLINE);
+
+	vty_out(vty, " Log Level specific information:%s", VTY_NEWLINE);
+
+	for (i = 0; i < info->num_cat; i++) {
+		const struct log_category *cat = &tgt->categories[i];
+		vty_out(vty, "  %-10s %-10s %-8s %s%s",
+			info->cat[i].name+1, log_level_str(cat->loglevel),
+			cat->enabled ? "Enabled" : "Disabled",
+ 			info->cat[i].description,
+			VTY_NEWLINE);
+	}
+}
+
+#define SHOW_LOG_STR "Show current logging configuration\n"
+
+DEFUN(show_logging_vty,
+      show_logging_vty_cmd,
+      "show logging vty",
+	SHOW_STR SHOW_LOG_STR
+	"Show current logging configuration for this vty\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg) {
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	vty_print_logtarget(vty, &log_info, conn->dbg);
+
+	return CMD_SUCCESS;
+}
+
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net)
+{
+	vty_out(vty, "Channel Requests        : %lu total, %lu no channel%s",
+		counter_get(net->stats.chreq.total),
+		counter_get(net->stats.chreq.no_channel), VTY_NEWLINE);
+	vty_out(vty, "Channel Failures        : %lu rf_failures, %lu rll failures%s",
+		counter_get(net->stats.chan.rf_fail),
+		counter_get(net->stats.chan.rll_err), VTY_NEWLINE);
+	vty_out(vty, "Paging                  : %lu attempted, %lu complete, %lu expired%s",
+		counter_get(net->stats.paging.attempted),
+		counter_get(net->stats.paging.completed),
+		counter_get(net->stats.paging.expired), VTY_NEWLINE);
+	vty_out(vty, "BTS failures            : %lu OML, %lu RSL%s",
+		counter_get(net->stats.bts.oml_fail),
+		counter_get(net->stats.bts.rsl_fail), VTY_NEWLINE);
+}
+
+void openbsc_vty_add_cmds()
+{
+	install_element(VIEW_NODE, &enable_logging_cmd);
+	install_element(VIEW_NODE, &disable_logging_cmd);
+	install_element(VIEW_NODE, &logging_fltr_imsi_cmd);
+	install_element(VIEW_NODE, &logging_fltr_all_cmd);
+	install_element(VIEW_NODE, &logging_use_clr_cmd);
+	install_element(VIEW_NODE, &logging_prnt_timestamp_cmd);
+	install_element(VIEW_NODE, &logging_set_category_mask_cmd);
+	install_element(VIEW_NODE, &logging_level_cmd);
+	install_element(VIEW_NODE, &show_logging_vty_cmd);
+
+}
diff --git a/openbsc/src/vty_interface_layer3.c b/openbsc/src/vty_interface_layer3.c
new file mode 100644
index 0000000..fee5bae
--- /dev/null
+++ b/openbsc/src/vty_interface_layer3.c
@@ -0,0 +1,568 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <vty/command.h>
+#include <vty/buffer.h>
+#include <vty/vty.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+
+static struct gsm_network *gsmnet;
+
+struct cmd_node subscr_node = {
+	SUBSCR_NODE,
+	"%s(subscriber)#",
+	1,
+};
+
+static int dummy_config_write(struct vty *v)
+{
+	return CMD_SUCCESS;
+}
+
+static struct buffer *argv_to_buffer(int argc, const char *argv[], int base)
+{
+	struct buffer *b = buffer_new(NULL, 1024);
+	int i;
+
+	if (!b)
+		return NULL;
+
+	for (i = base; i < argc; i++) {
+		buffer_putstr(b, argv[i]);
+		buffer_putc(b, ' ');
+	}
+	buffer_putc(b, '\0');
+
+	return b;
+}
+
+static int hexparse(const char *str, u_int8_t *b, int max_len)
+
+{
+	int i, l, v;
+
+	l = strlen(str);
+	if ((l&1) || ((l>>1) > max_len))
+		return -1;
+
+	memset(b, 0x00, max_len);
+
+	for (i=0; i<l; i++) {
+		char c = str[i];
+		if (c >= '0' && c <= '9')
+			v = c - '0';
+		else if (c >= 'a' && c <= 'f')
+			v = 10 + (c - 'a');
+		else if (c >= 'A' && c <= 'F')
+			v = 10 + (c - 'a');
+		else
+			return -1;
+		b[i>>1] |= v << (i&1 ? 0 : 4);
+	}
+
+	return i>>1;
+}
+
+/* per-subscriber configuration */
+DEFUN(cfg_subscr,
+      cfg_subscr_cmd,
+      "subscriber IMSI",
+      "Select a Subscriber to configure\n")
+{
+	const char *imsi = argv[0];
+	struct gsm_subscriber *subscr;
+
+	subscr = subscr_get_by_imsi(gsmnet, imsi);
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber for IMSI %s%s",
+			imsi, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* vty_go_parent should put this subscriber */
+	vty->index = subscr;
+	vty->node = SUBSCR_NODE;
+
+	return CMD_SUCCESS;
+}
+
+static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr)
+{
+	int rc;
+	struct gsm_auth_info ainfo;
+	struct gsm_auth_tuple atuple;
+
+	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
+		subscr->authorized, VTY_NEWLINE);
+	if (subscr->name)
+		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
+	if (subscr->extension)
+		vty_out(vty, "    Extension: %s%s", subscr->extension,
+			VTY_NEWLINE);
+	if (subscr->imsi)
+		vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+	if (subscr->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+			VTY_NEWLINE);
+
+	rc = get_authinfo_by_subscr(&ainfo, subscr);
+	if (!rc) {
+		vty_out(vty, "    A3A8 algorithm id: %d%s",
+			ainfo.auth_algo, VTY_NEWLINE);
+		vty_out(vty, "    A3A8 Ki: %s%s",
+			hexdump(ainfo.a3a8_ki, ainfo.a3a8_ki_len),
+			VTY_NEWLINE);
+	}
+
+	rc = get_authtuple_by_subscr(&atuple, subscr);
+	if (!rc) {
+		vty_out(vty, "    A3A8 last tuple (used %d times):%s",
+			atuple.use_count, VTY_NEWLINE);
+		vty_out(vty, "     seq # : %d%s",
+			atuple.key_seq, VTY_NEWLINE);
+		vty_out(vty, "     RAND  : %s%s",
+			hexdump(atuple.rand, sizeof(atuple.rand)),
+			VTY_NEWLINE);
+		vty_out(vty, "     SRES  : %s%s",
+			hexdump(atuple.sres, sizeof(atuple.sres)),
+			VTY_NEWLINE);
+		vty_out(vty, "     Kc    : %s%s",
+			hexdump(atuple.kc, sizeof(atuple.kc)),
+			VTY_NEWLINE);
+	}
+
+	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+
+/* Subscriber */
+DEFUN(show_subscr,
+      show_subscr_cmd,
+      "show subscriber [IMSI]",
+	SHOW_STR "Display information about a subscriber\n")
+{
+	const char *imsi;
+	struct gsm_subscriber *subscr;
+
+	if (argc >= 1) {
+		imsi = argv[0];
+		subscr = subscr_get_by_imsi(gsmnet, imsi);
+		if (!subscr) {
+			vty_out(vty, "%% unknown subscriber%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		subscr_dump_full_vty(vty, subscr);
+		subscr_put(subscr);
+
+		return CMD_SUCCESS;
+	}
+
+	/* FIXME: iterate over all subscribers ? */
+	return CMD_WARNING;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_subscr_cache,
+      show_subscr_cache_cmd,
+      "show subscriber cache",
+	SHOW_STR "Display contents of subscriber cache\n")
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, &active_subscribers, entry) {
+		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
+		subscr_dump_full_vty(vty, subscr);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(sms_send_pend,
+      sms_send_pend_cmd,
+      "sms send pending",
+      "Send all pending SMS")
+{
+	struct gsm_sms *sms;
+	int id = 0;
+
+	while (1) {
+		sms = db_sms_get_unsent_by_subscr(gsmnet, id);
+		if (!sms)
+			break;
+
+		gsm411_send_sms_subscr(sms->receiver, sms);
+
+		id = sms->receiver->id + 1;
+	}
+
+	return CMD_SUCCESS;
+}
+
+struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver, const char *text)
+{
+	struct gsm_sms *sms = sms_alloc();
+
+	if (!sms)
+		return NULL;
+
+	if (!receiver->lac) {
+		/* subscriber currently not attached, store in database? */
+		return NULL;
+	}
+
+	sms->receiver = subscr_get(receiver);
+	strncpy(sms->text, text, sizeof(sms->text)-1);
+
+	/* FIXME: don't use ID 1 static */
+	sms->sender = subscr_get_by_id(gsmnet, 1);
+	sms->reply_path_req = 0;
+	sms->status_rep_req = 0;
+	sms->ud_hdr_ind = 0;
+	sms->protocol_id = 0; /* implicit */
+	sms->data_coding_scheme = 0; /* default 7bit */
+	strncpy(sms->dest_addr, receiver->extension, sizeof(sms->dest_addr)-1);
+	/* Generate user_data */
+	sms->user_data_len = gsm_7bit_encode(sms->user_data, sms->text);
+
+	return sms;
+}
+
+static int _send_sms_buffer(struct gsm_subscriber *receiver,
+			     struct buffer *b, u_int8_t tp_pid)
+{
+	struct gsm_sms *sms;
+
+	sms = sms_from_text(receiver, buffer_getstr(b));
+	sms->protocol_id = tp_pid;
+	gsm411_send_sms_subscr(receiver, sms);
+
+	return CMD_SUCCESS;
+}
+
+static struct gsm_subscriber *get_subscr_by_argv(const char *type,
+						 const char *id)
+{
+	if (!strcmp(type, "extension"))
+		return subscr_get_by_extension(gsmnet, id);
+	else if (!strcmp(type, "imsi"))
+		return subscr_get_by_imsi(gsmnet, id);
+	else if (!strcmp(type, "tmsi"))
+		return subscr_get_by_tmsi(gsmnet, atoi(id));
+	else if (!strcmp(type, "id"))
+		return subscr_get_by_id(gsmnet, atoi(id));
+
+	return NULL;
+}
+#define SUBSCR_TYPES "(extension|imsi|tmsi|id)"
+
+DEFUN(subscriber_send_sms,
+      subscriber_send_sms_cmd,
+      "subscriber " SUBSCR_TYPES " EXTEN sms send .LINE",
+      "Select subscriber based on extension")
+{
+	struct gsm_subscriber *subscr = get_subscr_by_argv(argv[0], argv[1]);
+	struct buffer *b;
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	b = argv_to_buffer(argc, argv, 2);
+	rc = _send_sms_buffer(subscr, b, 0);
+	buffer_free(b);
+
+	subscr_put(subscr);
+
+	return rc;
+}
+
+DEFUN(subscriber_silent_sms,
+      subscriber_silent_sms_cmd,
+      "subscriber " SUBSCR_TYPES " EXTEN silent sms send .LINE",
+      "Select subscriber based on extension")
+{
+	struct gsm_subscriber *subscr = get_subscr_by_argv(argv[0], argv[1]);
+	struct buffer *b;
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	b = argv_to_buffer(argc, argv, 2);
+	rc = _send_sms_buffer(subscr, b, 64);
+	buffer_free(b);
+
+	subscr_put(subscr);
+
+	return rc;
+}
+
+DEFUN(subscriber_silent_call_start,
+      subscriber_silent_call_start_cmd,
+      "subscriber " SUBSCR_TYPES " EXTEN silent call start (any|tch/f|tch/any|sdcch)",
+      "Start a silent call to a subscriber")
+{
+	struct gsm_subscriber *subscr = get_subscr_by_argv(argv[0], argv[1]);
+	int rc, type;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[2], "tch/f"))
+		type = RSL_CHANNEED_TCH_F;
+	else if (!strcmp(argv[2], "tch/any"))
+		type = RSL_CHANNEED_TCH_ForH;
+	else if (!strcmp(argv[2], "sdcch"))
+		type = RSL_CHANNEED_SDCCH;
+	else
+		type = RSL_CHANNEED_ANY;	/* Defaults to ANY */
+
+	rc = gsm_silent_call_start(subscr, vty, type);
+	if (rc <= 0) {
+		vty_out(vty, "%% Subscriber not attached%s",
+			VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_silent_call_stop,
+      subscriber_silent_call_stop_cmd,
+      "subscriber " SUBSCR_TYPES " EXTEN silent call stop",
+      "Stop a silent call to a subscriber")
+{
+	struct gsm_subscriber *subscr = get_subscr_by_argv(argv[0], argv[1]);
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	rc = gsm_silent_call_stop(subscr);
+	if (rc < 0) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_subscr_name,
+      cfg_subscr_name_cmd,
+      "name NAME",
+      "Set the name of the subscriber")
+{
+	const char *name = argv[0];
+	struct gsm_subscriber *subscr = vty->index;
+
+	strncpy(subscr->name, name, sizeof(subscr->name));
+
+	db_sync_subscriber(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_subscr_extension,
+      cfg_subscr_extension_cmd,
+      "extension EXTENSION",
+      "Set the extension of the subscriber")
+{
+	const char *name = argv[0];
+	struct gsm_subscriber *subscr = vty->index;
+
+	strncpy(subscr->extension, name, sizeof(subscr->extension));
+
+	db_sync_subscriber(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_subscr_authorized,
+      cfg_subscr_authorized_cmd,
+      "auth <0-1>",
+      "Set the authorization status of the subscriber")
+{
+	int auth = atoi(argv[0]);
+	struct gsm_subscriber *subscr = vty->index;
+
+	if (auth)
+		subscr->authorized = 1;
+	else
+		subscr->authorized = 0;
+
+	db_sync_subscriber(subscr);
+
+	return CMD_SUCCESS;
+}
+
+#define A3A8_ALG_TYPES "(none|comp128v1)"
+
+DEFUN(cfg_subscr_a3a8,
+      cfg_subscr_a3a8_cmd,
+      "a3a8 " A3A8_ALG_TYPES " [KI]",
+      "Set a3a8 parameters for the subscriber")
+{
+	struct gsm_subscriber *subscr = vty->index;
+	const char *alg_str = argv[0];
+	const char *ki_str = argv[1];
+	struct gsm_auth_info ainfo;
+	int rc;
+
+	if (!strcasecmp(alg_str, "none")) {
+		/* Just erase */
+		rc = set_authinfo_for_subscr(NULL, subscr);
+	} else if (!strcasecmp(alg_str, "comp128v1")) {
+		/* Parse hex string Ki */
+		rc = hexparse(ki_str, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
+		if (rc != 16)
+			return CMD_WARNING;
+
+		/* Set the infos */
+		ainfo.auth_algo = AUTH_ALGO_COMP128v1;
+		ainfo.a3a8_ki_len = rc;
+		rc = set_authinfo_for_subscr(&ainfo, subscr);
+	} else {
+		/* Unknown method */
+		return CMD_WARNING;
+	}
+
+	return rc ? CMD_WARNING : CMD_SUCCESS;
+}
+
+static int scall_cbfn(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct scall_signal_data *sigdata = signal_data;
+	struct vty *vty = sigdata->data;
+
+	switch (signal) {
+	case S_SCALL_SUCCESS:
+		vty_out(vty, "%% silent call on ARFCN %u timeslot %u%s",
+			sigdata->lchan->ts->trx->arfcn, sigdata->lchan->ts->nr,
+			VTY_NEWLINE);
+		break;
+	case S_SCALL_EXPIRED:
+		vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE);
+		break;
+	}
+	return 0;
+}
+
+DEFUN(show_stats,
+      show_stats_cmd,
+      "show statistics",
+	SHOW_STR "Display network statistics\n")
+{
+	struct gsm_network *net = gsmnet;
+
+	openbsc_vty_print_statistics(vty, net);
+	vty_out(vty, "Location Update         : %lu attach, %lu normal, %lu periodic%s",
+		counter_get(net->stats.loc_upd_type.attach),
+		counter_get(net->stats.loc_upd_type.normal),
+		counter_get(net->stats.loc_upd_type.periodic), VTY_NEWLINE);
+	vty_out(vty, "IMSI Detach Indications : %lu%s",
+		counter_get(net->stats.loc_upd_type.detach), VTY_NEWLINE);
+	vty_out(vty, "Location Update Response: %lu accept, %lu reject%s",
+		counter_get(net->stats.loc_upd_resp.accept),
+		counter_get(net->stats.loc_upd_resp.reject), VTY_NEWLINE);
+	vty_out(vty, "Handover                : %lu attempted, %lu no_channel, %lu timeout, "
+		"%lu completed, %lu failed%s",
+		counter_get(net->stats.handover.attempted),
+		counter_get(net->stats.handover.no_channel),
+		counter_get(net->stats.handover.timeout),
+		counter_get(net->stats.handover.completed),
+		counter_get(net->stats.handover.failed), VTY_NEWLINE);
+	vty_out(vty, "SMS MO                  : %lu submitted, %lu no receiver%s",
+		counter_get(net->stats.sms.submitted),
+		counter_get(net->stats.sms.no_receiver), VTY_NEWLINE);
+	vty_out(vty, "SMS MT                  : %lu delivered, %lu no memory, %lu other error%s",
+		counter_get(net->stats.sms.delivered),
+		counter_get(net->stats.sms.rp_err_mem),
+		counter_get(net->stats.sms.rp_err_other), VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+
+int bsc_vty_init_extra(struct gsm_network *net)
+{
+	gsmnet = net;
+
+	register_signal_handler(SS_SCALL, scall_cbfn, NULL);
+
+	install_element(VIEW_NODE, &show_subscr_cmd);
+	install_element(VIEW_NODE, &show_subscr_cache_cmd);
+
+	install_element(VIEW_NODE, &sms_send_pend_cmd);
+
+	install_element(VIEW_NODE, &subscriber_send_sms_cmd);
+	install_element(VIEW_NODE, &subscriber_silent_sms_cmd);
+	install_element(VIEW_NODE, &subscriber_silent_call_start_cmd);
+	install_element(VIEW_NODE, &subscriber_silent_call_stop_cmd);
+	install_element(VIEW_NODE, &show_stats_cmd);
+
+	install_element(CONFIG_NODE, &cfg_subscr_cmd);
+	install_node(&subscr_node, dummy_config_write);
+
+	install_default(SUBSCR_NODE);
+	install_element(SUBSCR_NODE, &cfg_subscr_name_cmd);
+	install_element(SUBSCR_NODE, &cfg_subscr_extension_cmd);
+	install_element(SUBSCR_NODE, &cfg_subscr_authorized_cmd);
+	install_element(SUBSCR_NODE, &cfg_subscr_a3a8_cmd);
+
+	return 0;
+}
diff --git a/openbsc/tests/Makefile.am b/openbsc/tests/Makefile.am
new file mode 100644
index 0000000..3b1b931
--- /dev/null
+++ b/openbsc/tests/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = debug gsm0408 db channel sccp
diff --git a/openbsc/tests/channel/Makefile.am b/openbsc/tests/channel/Makefile.am
new file mode 100644
index 0000000..7729659
--- /dev/null
+++ b/openbsc/tests/channel/Makefile.am
@@ -0,0 +1,15 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS)
+
+noinst_PROGRAMS = channel_test
+
+channel_test_SOURCES = channel_test.c \
+	$(top_srcdir)/src/db.c \
+	$(top_srcdir)/src/gsm_subscriber_base.c \
+	$(top_srcdir)/src/gsm_subscriber.c \
+	$(top_srcdir)/src/debug.c \
+	$(top_srcdir)/src/gsm_data.c \
+	$(top_srcdir)/src/bts_ipaccess_nanobts.c \
+	$(top_srcdir)/src/bts_siemens_bs11.c
+channel_test_LDADD = -ldl -ldbi $(LIBOSMOCORE_LIBS)
+
diff --git a/openbsc/tests/channel/channel_test.c b/openbsc/tests/channel/channel_test.c
new file mode 100644
index 0000000..759001c
--- /dev/null
+++ b/openbsc/tests/channel/channel_test.c
@@ -0,0 +1,81 @@
+/*
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <assert.h>
+
+#include <osmocore/select.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/abis_rsl.h>
+
+/* our handler */
+static int subscr_cb(unsigned int hook, unsigned int event, struct msgb *msg, void *data, void *param)
+{
+	assert(hook == 101);
+	assert(event == 200);
+	assert(msg == (void*)0x1323L);
+	assert(data == (void*)0x4242L);
+	assert(param == (void*)0x2342L);
+	printf("Reached, didn't crash, test passed\n");
+	return 0;
+}
+
+/* mock object for testing, directly invoke the cb... maybe later through the timer */
+void paging_request(struct gsm_bts *bts, struct gsm_subscriber *subscriber, int type, gsm_cbfn *cbfn, void *data)
+{
+	cbfn(101, 200, (void*)0x1323L, (void*)0x4242L, data);
+}
+
+
+int main(int argc, char** argv)
+{
+	struct gsm_network *network;
+	struct gsm_bts *bts;
+
+	printf("Testing the gsm_subscriber chan logic\n");
+
+	/* Create a dummy network */
+	network = gsm_network_init(1, 1, NULL);
+	if (!network)
+		exit(1);
+	bts = gsm_bts_alloc(network, GSM_BTS_TYPE_BS11, 0, 0);
+	bts->location_area_code = 23;
+
+	/* Create a dummy subscriber */
+	struct gsm_subscriber *subscr = subscr_alloc();
+	subscr->lac = 23;
+	subscr->net = network;
+
+	/* Ask for a channel... */
+	subscr_get_channel(subscr, RSL_CHANNEED_TCH_F, subscr_cb, (void*)0x2342L);
+
+	while (1) {
+		bsc_select_main(0);
+	}
+}
+
+void nm_state_event() {}
+void input_event() {}
+void sms_alloc() {}
+
+struct tlv_definition nm_att_tlvdef;
+
diff --git a/openbsc/tests/db/Makefile.am b/openbsc/tests/db/Makefile.am
new file mode 100644
index 0000000..6eb9180
--- /dev/null
+++ b/openbsc/tests/db/Makefile.am
@@ -0,0 +1,8 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS)
+
+noinst_PROGRAMS = db_test
+
+db_test_SOURCES = db_test.c
+db_test_LDADD = $(top_builddir)/src/libbsc.a $(top_builddir)/src/libmsc.a $(top_builddir)/src/libbsc.a $(LIBOSMOCORE_LIBS) -ldl -ldbi
+
diff --git a/openbsc/tests/db/db_test.c b/openbsc/tests/db/db_test.c
new file mode 100644
index 0000000..f168acb
--- /dev/null
+++ b/openbsc/tests/db/db_test.c
@@ -0,0 +1,106 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/db.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define COMPARE(original, copy) \
+	if (original->id != copy->id) \
+		fprintf(stderr, "Ids do not match in %s:%d %llu %llu\n", \
+			__FUNCTION__, __LINE__, original->id, copy->id); \
+	if (original->lac != copy->lac) \
+		fprintf(stderr, "LAC do not match in %s:%d %d %d\n", \
+			__FUNCTION__, __LINE__, original->lac, copy->lac); \
+	if (original->authorized != copy->authorized) \
+		fprintf(stderr, "Authorize do not match in %s:%d %d %d\n", \
+			__FUNCTION__, __LINE__, original->authorized, \
+			copy->authorized); \
+	if (strcmp(original->imsi, copy->imsi) != 0) \
+		fprintf(stderr, "IMSIs do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->imsi, copy->imsi); \
+	if (original->tmsi != copy->tmsi) \
+		fprintf(stderr, "TMSIs do not match in %s:%d '%u' '%u'\n", \
+			__FUNCTION__, __LINE__, original->tmsi, copy->tmsi); \
+	if (strcmp(original->name, copy->name) != 0) \
+		fprintf(stderr, "names do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->name, copy->name); \
+	if (strcmp(original->extension, copy->extension) != 0) \
+		fprintf(stderr, "names do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->extension, copy->extension); \
+
+int main() {
+
+	if (db_init("hlr.sqlite3")) {
+		printf("DB: Failed to init database. Please check the option settings.\n");
+		return 1;
+	}	 
+	printf("DB: Database initialized.\n");
+
+	if (db_prepare()) {
+		printf("DB: Failed to prepare database.\n");
+		return 1;
+	}
+	printf("DB: Database prepared.\n");
+
+	struct gsm_subscriber *alice = NULL;
+	struct gsm_subscriber *alice_db;
+
+	char *alice_imsi = "3243245432345";
+	alice = db_create_subscriber(NULL, alice_imsi);
+	db_sync_subscriber(alice);
+	alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice->imsi);
+	COMPARE(alice, alice_db);
+	subscr_put(alice_db);
+	subscr_put(alice);
+
+	alice_imsi = "3693245423445";
+	alice = db_create_subscriber(NULL, alice_imsi);
+	db_subscriber_assoc_imei(alice, "1234567890");
+	db_subscriber_alloc_tmsi(alice);
+	alice->lac=42;
+	db_sync_subscriber(alice);
+	alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice_imsi);
+	COMPARE(alice, alice_db);
+	subscr_put(alice);
+	subscr_put(alice_db);
+
+	alice_imsi = "9993245423445";
+	alice = db_create_subscriber(NULL, alice_imsi);
+	db_subscriber_alloc_tmsi(alice);
+	alice->lac=42;
+	db_sync_subscriber(alice);
+	db_subscriber_assoc_imei(alice, "1234567890");
+	db_subscriber_assoc_imei(alice, "6543560920");
+	alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice_imsi);
+	COMPARE(alice, alice_db);
+	subscr_put(alice);
+	subscr_put(alice_db);
+
+	db_fini();
+
+	return 0;
+}
+
+/* stubs */
+void input_event(void) {}
+void nm_state_event(void) {}
diff --git a/openbsc/tests/debug/Makefile.am b/openbsc/tests/debug/Makefile.am
new file mode 100644
index 0000000..8423fd1
--- /dev/null
+++ b/openbsc/tests/debug/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+noinst_PROGRAMS = debug_test
+
+debug_test_SOURCES = debug_test.c $(top_srcdir)/src/debug.c 
+debug_test_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/openbsc/tests/debug/debug_test.c b/openbsc/tests/debug/debug_test.c
new file mode 100644
index 0000000..f3e4837
--- /dev/null
+++ b/openbsc/tests/debug/debug_test.c
@@ -0,0 +1,43 @@
+/* simple test for the debug interface */
+/*
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/debug.h>
+
+
+int main(int argc, char** argv)
+{
+	struct log_target *stderr_target;
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	log_parse_category_mask(stderr_target, "DRLL");
+	DEBUGP(DCC, "You should not see this\n");
+
+	log_parse_category_mask(stderr_target, "DRLL:DCC");
+	DEBUGP(DRLL, "You should see this\n");
+	DEBUGP(DCC, "You should see this\n");
+	DEBUGP(DMM, "You should not see this\n");
+
+	return 0;
+}
diff --git a/openbsc/tests/gsm0408/Makefile.am b/openbsc/tests/gsm0408/Makefile.am
new file mode 100644
index 0000000..f98c673
--- /dev/null
+++ b/openbsc/tests/gsm0408/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+noinst_PROGRAMS = gsm0408_test
+
+gsm0408_test_SOURCES = gsm0408_test.c
+gsm0408_test_LDADD = $(top_builddir)/src/libbsc.a $(top_builddir)/src/libmsc.a $(top_builddir)/src/libbsc.a $(LIBOSMOCORE_LIBS) -ldbi
diff --git a/openbsc/tests/gsm0408/gsm0408_test.c b/openbsc/tests/gsm0408/gsm0408_test.c
new file mode 100644
index 0000000..287d4ee
--- /dev/null
+++ b/openbsc/tests/gsm0408/gsm0408_test.c
@@ -0,0 +1,112 @@
+/* simple test for the gsm0408 formatting functions */
+/*
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+#define COMPARE(result, op, value) \
+    if (!((result) op (value))) {\
+	fprintf(stderr, "Compare failed. Was %x should be %x in %s:%d\n",result, value, __FILE__, __LINE__); \
+	exit(-1); \
+    }
+
+#define COMPARE_STR(result, value) \
+	if (strcmp(result, value) != 0) { \
+		fprintf(stderr, "Compare failed. Was %s should be %s in %s:%d\n",result, value, __FILE__, __LINE__); \
+		exit(-1); \
+	}
+
+/*
+ * Test Location Area Identifier formatting. Table 10.5.3 of 04.08
+ */
+static void test_location_area_identifier(void)
+{
+    struct gsm48_loc_area_id lai48;
+
+    printf("Testing test location area identifier\n");
+
+    /*
+     * Test the default/test setup. Coming from
+     * bsc_hack.c dumps
+     */
+    gsm48_generate_lai(&lai48, 1, 1, 1);
+    COMPARE(lai48.digits[0], ==, 0x00);
+    COMPARE(lai48.digits[1], ==, 0xF1);
+    COMPARE(lai48.digits[2], ==, 0x10);
+    COMPARE(lai48.lac, ==, htons(0x0001));
+
+    gsm48_generate_lai(&lai48, 602, 1, 15);
+    COMPARE(lai48.digits[0], ==, 0x06);
+    COMPARE(lai48.digits[1], ==, 0xF2);
+    COMPARE(lai48.digits[2], ==, 0x10);
+    COMPARE(lai48.lac, ==, htons(0x000f));
+}
+
+static void test_mi_functionality(void)
+{
+	const char *imsi_odd  = "987654321098763";
+	const char *imsi_even = "9876543210987654";
+	const u_int32_t tmsi = 0xfabeacd0;
+	u_int8_t mi[128];
+	unsigned int mi_len;
+	char mi_parsed[GSM48_MI_SIZE];
+
+	printf("Testing parsing and generating TMSI/IMSI\n");
+
+	/* tmsi code */
+	mi_len = gsm48_generate_mid_from_tmsi(mi, tmsi);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len - 2);
+	COMPARE((u_int32_t)strtoul(mi_parsed, NULL, 10), ==, tmsi);
+
+	/* imsi code */
+	mi_len = gsm48_generate_mid_from_imsi(mi, imsi_odd);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2);
+	printf("hex: %s\n", hexdump(mi, mi_len));
+	COMPARE_STR(mi_parsed, imsi_odd);
+
+	mi_len = gsm48_generate_mid_from_imsi(mi, imsi_even);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2);
+	printf("hex: %s\n", hexdump(mi, mi_len));
+	COMPARE_STR(mi_parsed, imsi_even);
+}
+
+int main(int argc, char** argv)
+{
+	test_location_area_identifier();
+	test_mi_functionality();
+
+	exit(0);
+}
+
+
+
+/*
+ * Stubs to compile and link
+ */
+void input_event(void) {}
+void nm_state_event(void) {}
diff --git a/openbsc/tests/sccp/Makefile.am b/openbsc/tests/sccp/Makefile.am
new file mode 100644
index 0000000..b35693e
--- /dev/null
+++ b/openbsc/tests/sccp/Makefile.am
@@ -0,0 +1,8 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS)
+
+noinst_PROGRAMS = sccp_test
+
+sccp_test_SOURCES = sccp_test.c
+sccp_test_LDADD = $(top_builddir)/src/libsccp.a $(top_builddir)/src/libbsc.a $(LIBOSMOCORE_LIBS)
+
diff --git a/openbsc/tests/sccp/sccp_test.c b/openbsc/tests/sccp/sccp_test.c
new file mode 100644
index 0000000..31eb47f
--- /dev/null
+++ b/openbsc/tests/sccp/sccp_test.c
@@ -0,0 +1,851 @@
+/*
+ * SCCP testing code
+ *
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+
+#include <sccp/sccp.h>
+
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+
+/* BSC -> MSC */
+static const u_int8_t bssmap_reset[] = {
+	0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe,
+	0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04,
+	0x01, 0x20,
+};
+
+/* MSC -> BSC reset ack */
+static const u_int8_t bssmap_reset_ack[] = {
+	0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+	0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03,
+	0x00, 0x01, 0x31,
+};
+
+/* MSC -> BSC paging, connection less */
+static const u_int8_t bssmap_paging[] = {
+	0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+	0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x10,
+	0x00, 0x0e, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10,
+	0x02, 0x01, 0x31, 0x97, 0x61, 0x1a, 0x01, 0x06,
+};
+
+/* MSC -> BSC paging, UDT without PC  */
+static const u_int8_t bssmap_udt[] = {
+	0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe,
+	0x02, 0x42, 0xfe, 0x10, 0x00, 0x0e, 0x52, 0x08,
+	0x08, 0x29, 0x47, 0x10, 0x02, 0x01, 0x31, 0x97,
+	0x61, 0x1a, 0x01, 0x06,
+};
+
+/* BSC -> MSC connection open */
+static const u_int8_t bssmap_cr[] = {
+	0x01, 0x01, 0x02, 0x03, 0x02, 0x02, 0x04, 0x02,
+	0x42, 0xfe, 0x0f, 0x1f, 0x00, 0x1d, 0x57, 0x05,
+	0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x12, 0xc3,
+	0x50, 0x17, 0x10, 0x05, 0x24, 0x11, 0x03, 0x33,
+	0x19, 0xa2, 0x08, 0x29, 0x47, 0x10, 0x02, 0x01,
+	0x31, 0x97, 0x61, 0x00
+};
+
+/* MSC -> BSC connection confirm */
+static const u_int8_t bssmap_cc[] = {
+	0x02, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00,
+};
+
+/* MSC -> BSC DTAP
+ *
+ * we fake a bit and make it BSC -> MSC... so the
+ * payload does not make any sense..
+ */
+static const u_int8_t bssmap_dtap[] = {
+	0x06, 0x00, 0x00, 0x03, 0x00, 0x01, 0x0f, 0x01, 0x00, 0x0c,
+	0x03, 0x05, 0x5c, 0x08, 0x11, 0x81, 0x33, 0x66, 0x02, 0x13,
+	0x45, 0xf4,
+};
+
+/* MSC -> BSC clear command */
+static const u_int8_t bssmap_clear[] = {
+	0x06, 0x00, 0x00, 0x03, 0x00, 0x01, 0x06, 0x00, 0x04, 0x20,
+	0x04, 0x01, 0x09,
+};
+
+/* MSC -> BSC released */
+static const u_int8_t bssmap_released[] = {
+	0x04, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x00, 0x01, 0x0f,
+	0x02, 0x23, 0x42, 0x00,
+};
+
+/* BSC -> MSC released */
+static const u_int8_t bssmap_release_complete[] = {
+	0x05, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03
+};
+
+struct test_data {
+	int length;
+	const u_int8_t *data;
+	int payload_start;
+	int payload_length;
+	u_int8_t first_byte;
+
+        /* in case it should trigger a sccp response */
+	int write;
+	const u_int8_t  *response;
+	int response_length;
+};
+
+static const struct test_data test_data[] = {
+	{
+		.length		= ARRAY_SIZE(bssmap_reset),
+		.data		= &bssmap_reset[0],
+		.payload_start	= 12,
+		.payload_length = ARRAY_SIZE(bssmap_reset) - 12,
+		.first_byte	= 0x0,
+	},
+	{
+		.length		= ARRAY_SIZE(bssmap_reset_ack),
+		.data		= &bssmap_reset_ack[0],
+		.payload_start	= 16,
+		.payload_length = ARRAY_SIZE(bssmap_reset_ack) - 16,
+		.first_byte	= 0x0,
+	},
+	{
+		.length		= ARRAY_SIZE(bssmap_paging),
+		.data		= &bssmap_paging[0],
+		.payload_start	= 16,
+		.payload_length = ARRAY_SIZE(bssmap_paging) - 16,
+		.first_byte	= 0x0,
+	},
+	{
+		.length		= ARRAY_SIZE(bssmap_cr),
+		.data		= &bssmap_cr[0],
+		.payload_start	= 12,
+		/* 0x00 is end of optional data, subtract this byte */
+		.payload_length = 31,
+		.first_byte	= 0x0,
+
+		/* the connection request should trigger a connection confirm */
+		.write		= 1,
+		.response	= &bssmap_cc[0],
+		.response_length= ARRAY_SIZE(bssmap_cc),
+	},
+	{
+		.length		= ARRAY_SIZE(bssmap_dtap),
+		.data		= &bssmap_dtap[0],
+		.payload_start	= 7,
+		.payload_length = 15,
+		.first_byte	= 0x01,
+	},
+	{
+		.length		= ARRAY_SIZE(bssmap_clear),
+		.data		= &bssmap_clear[0],
+		.payload_start	= 7,
+		.payload_length = 6,
+		.first_byte	= 0x00,
+	},
+	{
+		.length		= ARRAY_SIZE(bssmap_released),
+		.data		= &bssmap_released[0],
+		.payload_length = 2,
+		.payload_start  = 11,
+		.first_byte	= 0x23,
+
+		.write		= 1,
+		.response	= &bssmap_release_complete[0],
+		.response_length= ARRAY_SIZE(bssmap_release_complete),
+	},
+};
+
+/* we will send UDTs and verify they look like this */
+static const struct test_data send_data[] = {
+	{
+		.length		= ARRAY_SIZE(bssmap_udt),
+		.data		= &bssmap_udt[0],
+		.payload_start	= 12,
+		.payload_length = ARRAY_SIZE(bssmap_udt) - 12,
+		.first_byte	= 0x0,
+	},
+	{
+		.length		= ARRAY_SIZE(bssmap_reset),
+		.data		= &bssmap_reset[0],
+		.payload_start	= 12,
+		.payload_length = ARRAY_SIZE(bssmap_reset) - 12,
+		.first_byte	= 0x0,
+	},
+};
+
+struct connection_test {
+	/* should the connection be refused? */
+	int refuse;
+
+	int with_data;
+
+	/* on which side to close the connection? */
+	int close_side;
+	int close_cause;
+};
+
+/* sccp connection handling we want to test */
+static const struct connection_test connection_tests[] = {
+	{
+		.refuse	= 1,
+	},
+	{
+		.refuse	= 1,
+		.with_data = 1,
+	},
+	{
+		.refuse = 0,
+		.close_side = 0,
+		.close_cause = 5,
+	},
+	{
+		.refuse = 0,
+		.close_side = 0,
+		.close_cause = 5,
+		.with_data = 1,
+	},
+	{
+		.refuse = 0,
+		.close_side = 1,
+		.close_cause = 5,
+	},
+	{
+		.refuse = 0,
+		.close_side = 1,
+		.close_cause = 5,
+		.with_data = 1,
+	},
+};
+
+struct sccp_parse_header_result {
+	/* results */
+	int msg_type;
+	int wanted_len;
+	int src_ssn;
+	int dst_ssn;
+
+	int has_src_ref, has_dst_ref;
+	struct sccp_source_reference src_ref;
+	struct sccp_source_reference dst_ref;
+
+	/* the input */
+	const u_int8_t *input;
+	int input_len;
+};
+
+static const u_int8_t it_test[] = {
+0x10, 0x01, 0x07, 
+0x94, 0x01, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00 };
+
+static const u_int8_t proto_err[] = {
+0x0f, 0x0c, 0x04, 0x00, 0x00,
+};
+
+static const struct sccp_parse_header_result parse_result[] = {
+	{
+		.msg_type	= SCCP_MSG_TYPE_IT,
+		.wanted_len	= 0,
+		.src_ssn	= -1,
+		.dst_ssn	= -1,
+		.has_src_ref	= 1,
+		.has_dst_ref	= 1,
+
+		.src_ref	= {
+			.octet1 = 0x01,
+			.octet2 = 0x04,
+			.octet3 = 0x00
+		},
+		.dst_ref	= {
+			.octet1 = 0x01,
+			.octet2 = 0x07,
+			.octet3 = 0x94,
+		},
+
+		.input		= it_test,
+		.input_len	= sizeof(it_test),
+	},
+	{
+		.msg_type	= SCCP_MSG_TYPE_ERR,
+		.wanted_len	= 0,
+		.src_ssn	= -1,
+		.dst_ssn	= -1,
+		.has_src_ref	= 0,
+		.has_dst_ref	= 1,
+		.dst_ref	= {
+			.octet1 = 0x0c,
+			.octet2 = 0x04,
+			.octet3 = 0x00,
+		},
+		.input		= proto_err,
+		.input_len	= sizeof(proto_err),
+	},
+};
+
+
+/* testing procedure:
+ *	- we will use sccp_write and see what will be set in the
+ *	  outgoing callback
+ *	- we will call sccp_system_incoming and see which calls
+ *	  are made. And then compare it to the ones we expect. We
+ *	  want the payload to arrive, or callbacks to be called.
+ *	- we will use sccp_connection_socket and sccp_connection_write
+ *	  and verify state handling of connections
+ */
+
+static int current_test;
+
+/*
+ * test state...
+ */
+static int called  = 0;
+static int matched = 0;
+static int write_called = 0;
+
+#define FAIL(x, args...) printf("FAILURE in %s:%d: " x, __FILE__, __LINE__, ## args)
+
+/*
+ * writing these packets and expecting a result
+ */
+int sccp_read_cb(struct msgb *data, unsigned len, void *context)
+{
+	u_int16_t payload_length = test_data[current_test].payload_length;
+	const u_int8_t *got, *wanted;
+	int i;
+
+	called = 1;
+
+	if (msgb_l3len(data) < len) {
+		/* this should never be reached */
+		FAIL("Something horrible happened.. invalid packet..\n");
+		exit(-1);
+	}
+
+	if (len == 0 || len != payload_length) {
+		FAIL("length mismatch: got: %d wanted: %d\n", msgb_l3len(data), payload_length);
+		return -1;
+	}
+
+	if (data->l3h[0] !=  test_data[current_test].first_byte) {
+		FAIL("The first bytes of l3 do not match: 0x%x 0x%x\n",
+			data->l3h[0], test_data[current_test].first_byte);
+		return -1;
+	}
+
+	got = &data->l3h[0];
+	wanted = test_data[current_test].data + test_data[current_test].payload_start;
+
+	for (i = 0; i < len; ++i) {
+		if (got[i] != wanted[i]) {
+			FAIL("Failed to compare byte. Got: 0x%x Wanted: 0x%x at %d\n",
+			     got[i], wanted[i], i);
+			return -1;
+		}
+	}
+
+	matched = 1;
+	return 0;
+}
+
+void sccp_write_cb(struct msgb *data, void *ctx)
+{
+	int i = 0;
+	const u_int8_t *got, *wanted;
+
+	if (test_data[current_test].response == NULL) {
+		FAIL("Didn't expect write callback\n");
+		goto exit;
+	} else if (test_data[current_test].response_length != msgb_l2len(data)) {
+		FAIL("Size does not match. Got: %d Wanted: %d\n",
+		     msgb_l2len(data), test_data[current_test].response_length);
+	}
+
+	got = &data->l2h[0];
+	wanted = test_data[current_test].response;
+
+	for (i = 0; i < msgb_l2len(data); ++i) {
+		if (got[i] != wanted[i]) {
+			FAIL("Failed to compare byte. Got: 0x%x Wanted: 0x%x at %d\n",
+			     got[i], wanted[i], i);
+			goto exit;
+		}
+	}
+
+	write_called = 1;
+
+exit:
+	msgb_free(data);
+}
+
+void sccp_c_read(struct sccp_connection *connection, struct msgb *msgb, unsigned int len)
+{
+	sccp_read_cb(msgb, len, connection->data_ctx);
+}
+
+void sccp_c_state(struct sccp_connection *connection, int old_state)
+{
+	if (connection->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE)
+		sccp_connection_free(connection);
+}
+
+int sccp_accept_cb(struct sccp_connection *connection, void *user_data)
+{
+	called = 1;
+	unsigned int ref = 0;
+	ref |= connection->destination_local_reference.octet1 << 24;
+	ref |= connection->destination_local_reference.octet2 << 16;
+	ref |= connection->destination_local_reference.octet3 <<  8;
+	ref = ntohl(ref);
+
+	connection->data_cb = sccp_c_read;
+	connection->state_cb = sccp_c_state;
+
+	/* accept this */
+	return 0;
+}
+
+static void sccp_udt_write_cb(struct msgb *data, void *context)
+{
+	const u_int8_t *got, *wanted;
+	int i;
+
+	write_called = 1;
+
+	if (send_data[current_test].length != msgb_l2len(data)) {
+		FAIL("Size does not match. Got: %d Wanted: %d\n",
+		     msgb_l2len(data), send_data[current_test].length);
+		goto exit;
+	}
+
+	got = &data->l2h[0];
+	wanted = send_data[current_test].data;
+
+	for (i = 0; i < msgb_l2len(data); ++i) {
+		if (got[i] != wanted[i]) {
+			FAIL("Failed to compare byte. Got: 0x%x Wanted: 0x%x at %d\n",
+			     got[i], wanted[i], i);
+			goto exit;
+		}
+	}
+
+	matched = 1;
+
+exit:
+	msgb_free(data);
+}
+
+static void test_sccp_system(void)
+{
+	sccp_system_init(sccp_write_cb, NULL);
+	sccp_set_read(&sccp_ssn_bssap, sccp_read_cb, NULL);
+	sccp_connection_set_incoming(&sccp_ssn_bssap, sccp_accept_cb, NULL);
+
+	for (current_test = 0; current_test < ARRAY_SIZE(test_data); ++current_test) {
+		unsigned int length = test_data[current_test].length;
+		struct msgb *msg = msgb_alloc_headroom(length + 2, 2, __func__);
+		msg->l2h = msgb_put(msg, length);
+		memcpy(msg->l2h, test_data[current_test].data, length);
+
+		called = matched = write_called = 0;
+		printf("Testing packet: %d\n", current_test);
+		sccp_system_incoming(msg);
+
+		if (!called || !matched || (test_data[current_test].write != write_called))
+			FAIL("current test: %d called: %d matched: %d write: %d\n",
+			     current_test, called, matched, write_called);
+
+		msgb_free(msg);
+	}
+}
+
+/* test sending of udt */
+static void test_sccp_send_udt(void)
+{
+	sccp_system_init(sccp_udt_write_cb, NULL);
+	sccp_set_read(NULL, NULL, NULL);
+	sccp_connection_set_incoming(NULL, NULL, NULL);
+
+	for (current_test = 0; current_test < ARRAY_SIZE(send_data); ++current_test) {
+		const struct test_data *test = &send_data[current_test];
+
+		struct msgb *msg = msgb_alloc(test->payload_length, __func__);
+		msg->l3h = msgb_put(msg, test->payload_length);
+		memcpy(msg->l3h, test->data + test->payload_start, test->payload_length);
+
+		matched = write_called = 0;
+		printf("Testing packet: %d\n", current_test);
+		sccp_write(msg, &sccp_ssn_bssap, &sccp_ssn_bssap, 0);
+
+		if (!matched || !write_called)
+			FAIL("current test: %d matched: %d write: %d\n",
+			     current_test, matched, write_called);
+
+		msgb_free(msg);
+	}
+}
+
+/* send udt from one end to another */
+static unsigned int test_value = 0x2442;
+static int sccp_udt_read(struct msgb *data, unsigned int len, void *context)
+{
+	unsigned int *val;
+
+	if (len != 4) {
+		FAIL("Wrong size: %d\n", msgb_l3len(data));
+		return -1;
+	}
+
+	val = (unsigned int*)data->l3h;
+	matched = test_value == *val;
+
+	return 0;
+}
+
+static void sccp_write_loop(struct msgb *data, void *context)
+{
+	/* send it back to us */
+	sccp_system_incoming(data);
+	msgb_free(data);
+}
+
+static void test_sccp_udt_communication(void)
+{
+	struct msgb *data;
+	unsigned int *val;
+
+	sccp_system_init(sccp_write_loop, NULL);
+	sccp_set_read(&sccp_ssn_bssap, sccp_udt_read, NULL);
+	sccp_connection_set_incoming(NULL, NULL, NULL);
+
+
+	data = msgb_alloc(4, "test data");
+	data->l3h = &data->data[0];
+	val = (unsigned int *)msgb_put(data, 4);
+	*val = test_value;
+
+	matched = 0;
+	sccp_write(data, &sccp_ssn_bssap, &sccp_ssn_bssap, 0);
+
+	if (!matched)
+	    FAIL("Talking with us didn't work\n");
+
+	msgb_free(data);
+}
+
+
+/* connection testing... open, send, close */
+static const struct connection_test *current_con_test;
+static struct sccp_connection *outgoing_con;
+static struct sccp_connection *incoming_con;
+static int outgoing_data, incoming_data, incoming_state, outgoing_state;
+
+static struct msgb *test_data1, *test_data2, *test_data3;
+
+static void sccp_conn_in_state(struct sccp_connection *conn, int old_state)
+{
+	printf("\tincome: %d -> %d\n", old_state, conn->connection_state);
+	if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) {
+		if (conn == incoming_con) {
+			sccp_connection_free(conn);
+			incoming_con = NULL;
+		}
+	}
+}
+
+static void sccp_conn_in_data(struct sccp_connection *conn, struct msgb *msg, unsigned int len)
+{
+	/* compare the data */
+	++incoming_data;
+	printf("\tincoming data: %d\n", len);
+
+	/* compare the data */
+	if (len != 4) {
+		FAIL("Length of packet is wrong: %u %u\n", msgb_l3len(msg), len);
+		return;
+	}
+
+	if (incoming_data == 1) {
+		if (memcmp(msg->l3h, test_data1->l3h, len) != 0) {
+			FAIL("Comparing the data failed: %d\n", incoming_data);
+			incoming_state = 0;
+			printf("Got:    %s\n", hexdump(msg->l3h, len));
+			printf("Wanted: %s\n", hexdump(test_data1->l3h, len));
+
+		}
+	} else if (incoming_data == 2) {
+		if (memcmp(msg->l3h, test_data2->l3h, len) != 0) {
+			FAIL("Comparing the data failed: %d\n", incoming_data);
+			incoming_state = 0;
+			printf("Got:    %s\n", hexdump(msg->l3h, len));
+			printf("Wanted: %s\n", hexdump(test_data2->l3h, len));
+		}
+	}
+
+	/* sending out data */
+	if (incoming_data == 2) {
+		printf("\tReturning data3\n");
+		sccp_connection_write(conn, test_data3);
+	}
+}
+
+static int sccp_conn_accept(struct sccp_connection *conn, void *ctx)
+{
+	printf("\taccept: %p\n", conn);
+	conn->state_cb = sccp_conn_in_state;
+	conn->data_cb = sccp_conn_in_data;
+
+	if (current_con_test->refuse)
+		return -1;
+
+	incoming_con = conn;
+	return 0;
+}
+
+/* callbacks for the outgoing side */
+static void sccp_conn_out_state(struct sccp_connection *conn, int old_state)
+{
+	printf("\toutgoing: %p %d -> %d\n", conn, old_state, conn->connection_state);
+
+	if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) {
+		if (conn == outgoing_con) {
+			sccp_connection_free(conn);
+			outgoing_con = NULL;
+		}
+	}
+}
+
+static void sccp_conn_out_data(struct sccp_connection *conn, struct msgb *msg, unsigned int len)
+{
+	++outgoing_data;
+	printf("\toutgoing data: %p %d\n", conn, len);
+
+	if (len != 4)
+		FAIL("Length of packet is wrong: %u %u\n", msgb_l3len(msg), len);
+
+	if (outgoing_data == 1) {
+		if (memcmp(msg->l3h, test_data3->l3h, len) != 0) {
+			FAIL("Comparing the data failed\n");
+			outgoing_state = 0;
+		}
+	}
+}
+
+static void do_test_sccp_connection(const struct connection_test *test)
+{
+	int ret;
+
+	current_con_test = test;
+	outgoing_con = incoming_con = 0;
+
+	outgoing_con = sccp_connection_socket();
+	if (!outgoing_con) {
+		FAIL("Connection is NULL\n");
+		return;
+	}
+
+	outgoing_con->state_cb = sccp_conn_out_state;
+	outgoing_con->data_cb = sccp_conn_out_data;
+	outgoing_data = incoming_data = 0;
+	incoming_state = outgoing_state = 1;
+
+	/* start testing */
+	if (test->with_data) {
+		if (sccp_connection_connect(outgoing_con, &sccp_ssn_bssap, test_data1) != 0)
+			FAIL("Binding failed\n");
+	} else {
+		++incoming_data;
+		if (sccp_connection_connect(outgoing_con, &sccp_ssn_bssap, NULL) != 0)
+			FAIL("Binding failed\n");
+	}
+
+	if (test->refuse) {
+		if (outgoing_con)
+			FAIL("Outgoing connection should have been refused.\n");
+	} else {
+		if (!incoming_con)
+			FAIL("Creating incoming didn't work.\n");
+
+		printf("\tWriting test data2\n");
+		sccp_connection_write(outgoing_con, test_data2);
+		sccp_connection_send_it(outgoing_con);
+
+		/* closing connection */
+		if (test->close_side == 0)
+			ret = sccp_connection_close(outgoing_con, 0);
+		else
+			ret = sccp_connection_close(incoming_con, 0);
+
+		if (ret != 0)
+			FAIL("Closing the connection failed\n");
+	}
+
+	/* outgoing should be gone now */
+	if (outgoing_con)
+		FAIL("Outgoing connection was not properly closed\n");
+
+	if (incoming_con)
+		FAIL("Incoming connection was not propery closed.\n");
+
+	if (test->refuse == 0) {
+		if (outgoing_data != 1 || incoming_data != 2) {
+			FAIL("Data sending failed: %d/%d %d/%d\n",
+			     outgoing_data, 1,
+			     incoming_data, 2);
+		}
+	}
+
+	if (!incoming_state || !outgoing_state)
+		FAIL("Failure with the state transition. %d %d\n",
+		     outgoing_state, incoming_state);
+}
+
+static void test_sccp_connection(void)
+{
+	sccp_system_init(sccp_write_loop, NULL);
+	sccp_set_read(NULL, NULL, NULL);
+	sccp_connection_set_incoming(&sccp_ssn_bssap, sccp_conn_accept, NULL);
+
+	test_data1 = msgb_alloc(4, "data1");
+	test_data1->l3h = msgb_put(test_data1, 4);
+	*((unsigned int*)test_data1->l3h) = 0x23421122;
+
+	test_data2 = msgb_alloc(4, "data2");
+	test_data2->l3h = msgb_put(test_data2, 4);
+	*((unsigned int*)test_data2->l3h) = 0x42232211;
+
+	test_data3 = msgb_alloc(4, "data3");
+	test_data3->l3h = msgb_put(test_data3, 4);
+	*((unsigned int*)test_data3->l3h) = 0x2323ff55;
+
+
+	for (current_test = 0; current_test < ARRAY_SIZE(connection_tests); ++current_test) {
+		printf("Testing %d refuse: %d with_data: %d\n",
+			current_test, connection_tests[current_test].refuse,
+			connection_tests[current_test].with_data);
+		do_test_sccp_connection(&connection_tests[current_test]);
+	}
+
+	msgb_free(test_data1);
+	msgb_free(test_data2);
+	msgb_free(test_data3);
+}
+
+/* invalid input */
+static void test_sccp_system_crash(void)
+{
+	printf("trying to provoke a crash with invalid input\n");
+	sccp_set_read(&sccp_ssn_bssap, sccp_read_cb, NULL);
+	sccp_connection_set_incoming(&sccp_ssn_bssap, sccp_accept_cb, NULL);
+
+	for (current_test = 0; current_test < ARRAY_SIZE(test_data); ++current_test) {
+		int original_length = test_data[current_test].length;
+		int length = original_length + 2;
+		int i;
+
+		printf("Testing packet: %d\n", current_test);
+
+		for (i = length; i >= 0; --i) {
+			unsigned int length = MIN(test_data[current_test].length, i);
+			struct msgb *msg = msgb_alloc_headroom(length + 2, 2, __func__);
+			msg->l2h = msgb_put(msg, length);
+			memcpy(msg->l2h, test_data[current_test].data, length);
+			sccp_system_incoming(msg);
+			msgb_free(msg);
+		}
+	}
+
+	printf("survived\n");
+}
+
+static void test_sccp_parsing(void)
+{
+	for (current_test = 0; current_test < ARRAY_SIZE(parse_result); ++current_test) {
+		struct msgb *msg;
+		struct sccp_parse_result result;
+
+		msg = msgb_alloc_headroom(1024, 128, "parse-test");
+		msgb_put(msg, 1);
+		msg->l2h = msgb_put(msg, parse_result[current_test].input_len);
+		memcpy(msg->l2h, parse_result[current_test].input, msgb_l2len(msg));
+
+		memset(&result, 0, sizeof(result));
+		if (sccp_parse_header(msg, &result) != 0) {
+			fprintf(stderr, "Failed to sccp parse test: %d\n", current_test);
+		} else {
+			if (parse_result[current_test].wanted_len != result.data_len) {
+				fprintf(stderr, "Unexpected data length.\n");
+				abort();
+			}
+
+			if (parse_result[current_test].has_src_ref) {
+				if (memcmp(result.source_local_reference,
+					   &parse_result[current_test].src_ref,
+					   sizeof(struct sccp_source_reference)) != 0) {
+					fprintf(stderr, "SRC REF did not match\n");
+					abort();
+				}
+			}
+
+			if (parse_result[current_test].has_dst_ref) {
+				if (memcmp(result.destination_local_reference,
+					   &parse_result[current_test].dst_ref,
+					   sizeof(struct sccp_source_reference)) != 0) {
+					fprintf(stderr, "DST REF did not match\n");
+					abort();
+				}
+			}
+
+			if (parse_result[current_test].src_ssn != -1) {
+				fprintf(stderr, "Not implemented.\n");
+				abort();
+			}
+
+			if (parse_result[current_test].dst_ssn != -1) {
+				fprintf(stderr, "Not implemented.\n");
+				abort();
+			}
+		}
+
+		msgb_free(msg);
+	}
+}
+
+
+int main(int argc, char **argv)
+{
+	test_sccp_system();
+	test_sccp_send_udt();
+	test_sccp_udt_communication();
+	test_sccp_connection();
+	test_sccp_system_crash();
+	test_sccp_parsing();
+	return 0;
+}
+
+void db_store_counter() {}
diff --git a/openbsc/tests/sms.txt b/openbsc/tests/sms.txt
new file mode 100644
index 0000000..06c885b
--- /dev/null
+++ b/openbsc/tests/sms.txt
@@ -0,0 +1,50 @@
+03 02 01 0a 02 43 0b 00 1d 39 01 1a 00 01 00 07 91 55 11 18 31 28 00 0e 31 20 04 81 21 43 00 00 ff 04 d4 f2 9c 0e
+
+03 02 01 0a 02 43 0b 00 1d -
+39 - TransactionID 3, SMS messages	:: gh->proto_discr
+01 - CP-DATA				:: gh->msg_type
+1a - Length: 26				:: gh->data[0]
+00 - MTI 0 RP-DATA (ms->n)
+01 - MR 1
+00 - RP-OA
+07 - RP-DA (SMSC Length)
+91 - International No, Numbering Plan
+55 11 18 31 28 00 - 551181138200
+0e - RP-UD len (14)
+
+TPDU (14 byte):
+31 - MTI(01), VPF(2:relative), MMS(0), SRI(1), UDHI(0), RP(0)
+20 - Message Reference
+04 - DA length
+81 - Numbering Plan, National number
+21 43 - DA 1234
+00 - PID
+00 - DCS
+ff - Validity period
+04 - User Data length (04)
+d4 f2 9c 0e - gsm_default 7bit encoded "Test" (4 byte)
+
+03 02 01 0a 02 43 0b 00 9f 09 01 9c 00 da 00 07 91 88 96 13 00 00 99 90 11 7b 04 81 22 22 00 08 ff 86 6c 38 8c 50 92 80 88 4c 00 4d 00 4d 00 41 6a 19 67 03 74 06 8c a1 7d b2 00 20 00 20 51 68 74 03 99 96 52 75 7d b2 8d ef 6a 19 67 03 ff 0c 6a 19 67 03 96 f6 98 a8 96 aa ff 01 8b 93 60 a8 80 70 66 0e 51 32 84 c4 ff 0c 97 48 6d 3b 62 95 8c c7 ff 01 73 fe 57 28 52 a0 51 65 90 01 96 50 91 cf 59 27 80 6f 76 df 6d 0b 57 fa 96 8a 91 77 5e 63 53 61 ff 0c 8a cb 4e 0a 7d b2 64 1c 5c 0b 30 0c 6a 19 67 03 30 0d
+
+03 02 01 0a 02 43 0b 00 9f - lower levels
+09 - TransactionID 0, SMS messages
+01 - CP-DATA
+9c - Length: (156)
+00 - MTI 0 RP-DATA (ms->n)
+da - MR (?)
+00 - RP-OA
+07 - RP-DA (SMSC Length)
+91 - International No.
+88 96 13 00 00 99
+90 - RP-UD len (144)
+11 -
+7b - Message Reference
+04 - DA length
+81 - Numbering Plan
+22 22 - Address 2222
+00 - PID
+08 - DCS (UCS2 charset)
+ff - Validity period
+86 - User Data length (134)
+6c 38 8c 50 92 80 88 4c 00 4d 00 4d 00 41 6a 19 67 03 74 06 8c a1 7d b2 00 20 00 20 51 68 74 03 99 96 52 75 7d b2 8d ef 6a 19 67 03 ff 0c 6a 19 67 03 96 f6 98 a8 96 aa ff 01 8b 93 60 a8 80 70 66 0e 51 32 84 c4 ff 0c 97 48 6d 3b 62 95 8c c7 ff 01 73 fe 57 28 52 a0 51 65 90 01 96 50 91 cf 59 27 80 6f 76 df 6d 0b 57 fa 96 8a 91 77 5e 63 53 61 ff 0c 8a cb 4e 0a 7d b2 64 1c 5c 0b 30 0c 6a 19 67 03 30 0d
+
diff --git a/openbsc/tools/hlrstat.pl b/openbsc/tools/hlrstat.pl
new file mode 100755
index 0000000..668fc9a
--- /dev/null
+++ b/openbsc/tools/hlrstat.pl
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+
+use strict;
+use DBI;
+my $dbh = DBI->connect("dbi:SQLite:dbname=hlr.sqlite3","","");
+
+
+my %mcc_names;
+my %mcc_mnc_names;
+
+sub get_mcc_mnc_name($)
+{
+	my $mcc_mnc = shift;
+	my $ret = $mcc_mnc;
+
+	if ($mcc_mnc_names{$mcc_mnc} ne '') {
+		$ret = $mcc_mnc_names{$mcc_mnc};
+	}
+
+	return $ret;
+}
+
+sub read_networks($)
+{
+	my $filename = shift;
+	my $cur_name;
+
+	open(INFILE, $filename);
+	while (my $l = <INFILE>) {
+		chomp($l);
+		if ($l =~ /^#/) {
+			next;
+		}
+		if ($l =~ /^\t/) {
+			my ($mcc, $mnc, $brand, $r) = split(' ', $l, 4);
+			#printf("%s|%s|%s\n", $mcc, $mnc, $brand);
+			$mcc_mnc_names{"$mcc-$mnc"} = $brand;
+			$mcc_names{$mcc} = $cur_name;
+		} elsif ($l =~ /^(\w\w)\t(.*)/) {
+			#printf("%s|%s\n", $1, $2);
+			$cur_name = $2;
+		}
+	}
+	close(INFILE);
+}
+
+read_networks("networks.tab");
+
+my %oper_count;
+my %country_count;
+
+#my $sth = $dbh->prepare("SELECT imsi FROM subscriber where authorized=1");
+my $sth = $dbh->prepare("SELECT imsi FROM subscriber");
+
+$sth->execute();
+
+while (my $href = $sth->fetchrow_hashref) {
+	my ($mcc, $mnc) = $$href{imsi} =~ /(\d{3})(\d{2}).*/;
+	#printf("%s %s-%s \n", $$href{imsi}, $mcc, $mnc);
+	$oper_count{"$mcc-$mnc"}++;
+	$country_count{$mcc}++;
+}
+
+
+foreach my $c (sort{$country_count{$b} <=> $country_count{$a}} keys %country_count) {
+	printf("%s: %d\n", $mcc_names{$c}, $country_count{$c});
+
+	foreach my $k (sort{$oper_count{$b} <=> $oper_count{$a}} keys %oper_count) {
+		if ($k =~ /^$c-/) {
+			printf("\t%s: %d\n", get_mcc_mnc_name($k), $oper_count{$k});
+		}
+	}
+}
diff --git a/rrlp-ephemeris/.gitignore b/rrlp-ephemeris/.gitignore
new file mode 100644
index 0000000..88b45e4
--- /dev/null
+++ b/rrlp-ephemeris/.gitignore
@@ -0,0 +1,4 @@
+asn1_gen/*
+*.o
+*.a
+rrlp-test
diff --git a/rrlp-ephemeris/COPYING b/rrlp-ephemeris/COPYING
new file mode 100644
index 0000000..8d7088e
--- /dev/null
+++ b/rrlp-ephemeris/COPYING
@@ -0,0 +1,2 @@
+See the included gpl-2.0.txt or gpl-3.0.txt depending on your
+preferences.
diff --git a/rrlp-ephemeris/Makefile b/rrlp-ephemeris/Makefile
new file mode 100644
index 0000000..742cac7
--- /dev/null
+++ b/rrlp-ephemeris/Makefile
@@ -0,0 +1,44 @@
+
+ASN1C=../../../tmp/rrlp/asn1c/asn1c/asn1c
+ASN1_INCLUDE=/home/tnt/tmp/rrlp/asn1c/skeletons
+CC=gcc
+CFLAGS=-I$(ASN1_INCLUDE) -Iasn1_gen -O3 -Wall
+
+ASN1_FILES=$(wildcard asn1/*.asn)
+
+
+all: rrlp-test
+
+
+rrlp-test: libgsm-asn1.a gps.o ubx.o ubx-parse.o rrlp.o main.o
+	$(CC) -o $@ gps.o ubx.o ubx-parse.o rrlp.o main.o -L. -lgsm-asn1 -lm
+
+
+#
+# ASN1 file autogeneration (need recursive makefile call)
+#
+
+ASN1_SOURCES = $(wildcard asn1_gen/*.c)
+ASN1_OBJECTS = $(ASN1_SOURCES:.c=.o)
+
+libgsm-asn1.a: $(ASN1_FILES)
+	mkdir -p asn1_gen && \
+	cd asn1_gen && \
+	$(ASN1C) -fskeletons-copy -fnative-types -gen-PER $(addprefix ../,$^)
+	@rm asn1_gen/converter-sample.c asn1_gen/Makefile.am.sample
+	@$(MAKE) libgsm-asn1.a.submake
+
+libgsm-asn1.a.submake: $(ASN1_OBJECTS)
+	$(AR) rcs libgsm-asn1.a $^
+
+.PHONY: libgsm-asn1.a.submake
+
+
+#
+# Clean
+#
+
+clean:
+	rm -Rf asn1_gen
+	rm -f libgsm-asn1.a *.o rrlp-test
+
diff --git a/rrlp-ephemeris/asn1/MAP-BS-Code.asn b/rrlp-ephemeris/asn1/MAP-BS-Code.asn
new file mode 100644
index 0000000..1d25366
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-BS-Code.asn
@@ -0,0 +1,131 @@
+-- $Id: MAP-BS-Code.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- 3GPP TS 29.002 V8.9.0 (2009-04) 
+-- 17.7.10	Bearer Service Codes
+ 
+MAP-BS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-BS-Code (20) version11 (11)}
+
+DEFINITIONS
+
+::=
+
+BEGIN
+
+BearerServiceCode ::= OCTET STRING (SIZE (1))
+	-- This type is used to represent the code identifying a single
+	-- bearer service, a group of bearer services, or all bearer
+	-- services. The services are defined in TS 3GPP TS 22.002 [3].
+	-- The internal structure is defined as follows:
+	--
+	-- plmn-specific bearer services:
+	-- bits 87654321: defined by the HPLMN operator
+
+	-- rest of bearer services:
+	-- bit 8: 0 (unused)
+	-- bits 7654321: group (bits 7654), and rate, if applicable
+	-- (bits 321)
+
+Ext-BearerServiceCode ::= OCTET STRING (SIZE (1..5))
+	-- This type is used to represent the code identifying a single
+	-- bearer service, a group of bearer services, or all bearer
+	-- services. The services are defined in TS 3GPP TS 22.002 [3].
+	-- The internal structure is defined as follows:
+	--
+	-- OCTET 1:
+	-- plmn-specific bearer services:
+	-- bits 87654321: defined by the HPLMN operator
+	--
+	-- rest of bearer services:
+	-- bit 8: 0 (unused)
+	-- bits 7654321: group (bits 7654), and rate, if applicable
+	-- (bits 321)
+
+	-- OCTETS 2-5: reserved for future use. If received the
+    -- Ext-TeleserviceCode shall be
+	-- treated according to the exception handling defined for the
+	-- operation that uses this type. 
+
+
+	-- Ext-BearerServiceCode includes all values defined for BearerServiceCode.
+
+allBearerServices	BearerServiceCode ::= '00000000'B
+
+allDataCDA-Services	BearerServiceCode ::= '00010000'B
+dataCDA-300bps		BearerServiceCode ::= '00010001'B
+dataCDA-1200bps	BearerServiceCode ::= '00010010'B
+dataCDA-1200-75bps	BearerServiceCode ::= '00010011'B
+dataCDA-2400bps	BearerServiceCode ::= '00010100'B
+dataCDA-4800bps	BearerServiceCode ::= '00010101'B
+dataCDA-9600bps	BearerServiceCode ::= '00010110'B
+general-dataCDA	BearerServiceCode ::= '00010111'B
+
+allDataCDS-Services	BearerServiceCode ::= '00011000'B
+dataCDS-1200bps	BearerServiceCode ::= '00011010'B
+dataCDS-2400bps	BearerServiceCode ::= '00011100'B
+dataCDS-4800bps	BearerServiceCode ::= '00011101'B
+dataCDS-9600bps	BearerServiceCode ::= '00011110'B
+general-dataCDS	BearerServiceCode ::= '00011111'B
+
+allPadAccessCA-Services	BearerServiceCode ::= '00100000'B
+padAccessCA-300bps	BearerServiceCode ::= '00100001'B
+padAccessCA-1200bps	BearerServiceCode ::= '00100010'B
+padAccessCA-1200-75bps	BearerServiceCode ::= '00100011'B
+padAccessCA-2400bps	BearerServiceCode ::= '00100100'B
+padAccessCA-4800bps	BearerServiceCode ::= '00100101'B
+padAccessCA-9600bps	BearerServiceCode ::= '00100110'B
+general-padAccessCA	BearerServiceCode ::= '00100111'B
+
+allDataPDS-Services	BearerServiceCode ::= '00101000'B
+dataPDS-2400bps	BearerServiceCode ::= '00101100'B
+dataPDS-4800bps	BearerServiceCode ::= '00101101'B
+dataPDS-9600bps	BearerServiceCode ::= '00101110'B
+general-dataPDS	BearerServiceCode ::= '00101111'B
+
+allAlternateSpeech-DataCDA	BearerServiceCode ::= '00110000'B
+
+allAlternateSpeech-DataCDS	BearerServiceCode ::= '00111000'B
+
+allSpeechFollowedByDataCDA	BearerServiceCode ::= '01000000'B
+
+allSpeechFollowedByDataCDS	BearerServiceCode ::= '01001000'B
+
+-- The following non-hierarchical Compound Bearer Service
+-- Groups are defined in TS 3GPP TS 22.030:
+allDataCircuitAsynchronous	BearerServiceCode ::= '01010000'B
+	-- covers "allDataCDA-Services", "allAlternateSpeech-DataCDA" and
+	-- "allSpeechFollowedByDataCDA"
+allAsynchronousServices	BearerServiceCode ::= '01100000'B
+	-- covers "allDataCDA-Services", "allAlternateSpeech-DataCDA",
+	-- "allSpeechFollowedByDataCDA" and "allPadAccessCDA-Services"
+allDataCircuitSynchronous	BearerServiceCode ::= '01011000'B
+	-- covers "allDataCDS-Services", "allAlternateSpeech-DataCDS" and
+	-- "allSpeechFollowedByDataCDS"
+allSynchronousServices	BearerServiceCode ::= '01101000'B
+	-- covers "allDataCDS-Services", "allAlternateSpeech-DataCDS",
+	-- "allSpeechFollowedByDataCDS" and "allDataPDS-Services"
+--
+-- Compound Bearer Service Group Codes are only used in call
+-- independent supplementary service operations, i.e. they
+-- are not used in InsertSubscriberData or in
+-- DeleteSubscriberData messages.
+
+allPLMN-specificBS	BearerServiceCode ::= '11010000'B
+plmn-specificBS-1	BearerServiceCode ::= '11010001'B
+plmn-specificBS-2	BearerServiceCode ::= '11010010'B
+plmn-specificBS-3	BearerServiceCode ::= '11010011'B
+plmn-specificBS-4	BearerServiceCode ::= '11010100'B
+plmn-specificBS-5	BearerServiceCode ::= '11010101'B
+plmn-specificBS-6	BearerServiceCode ::= '11010110'B
+plmn-specificBS-7	BearerServiceCode ::= '11010111'B
+plmn-specificBS-8	BearerServiceCode ::= '11011000'B
+plmn-specificBS-9	BearerServiceCode ::= '11011001'B
+plmn-specificBS-A	BearerServiceCode ::= '11011010'B
+plmn-specificBS-B	BearerServiceCode ::= '11011011'B
+plmn-specificBS-C	BearerServiceCode ::= '11011100'B
+plmn-specificBS-D	BearerServiceCode ::= '11011101'B
+plmn-specificBS-E	BearerServiceCode ::= '11011110'B
+plmn-specificBS-F	BearerServiceCode ::= '11011111'B
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-CommonDataTypes.asn b/rrlp-ephemeris/asn1/MAP-CommonDataTypes.asn
new file mode 100644
index 0000000..f3d202e
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-CommonDataTypes.asn
@@ -0,0 +1,633 @@
+-- $Id: MAP-CommonDataTypes.asn 30470 2009-10-10 12:37:56Z krj $
+-- 3GPP TS 29.002 V8.9.0 (2009-04)
+-- 17.7.8	Common data types
+
+MAP-CommonDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-CommonDataTypes (18) version11 (11)}
+
+DEFINITIONS
+
+IMPLICIT TAGS
+
+::=
+
+BEGIN
+
+EXPORTS
+
+	-- general data types and values
+	AddressString,
+	ISDN-AddressString,
+	maxISDN-AddressLength,
+	FTN-AddressString,
+	ISDN-SubaddressString,
+	ExternalSignalInfo, 
+	Ext-ExternalSignalInfo, 
+AccessNetworkSignalInfo,
+	SignalInfo,
+	maxSignalInfoLength,
+	AlertingPattern,
+	TBCD-STRING,
+
+	-- data types for numbering and identification
+	IMSI,
+	TMSI, 
+	Identity,
+	SubscriberId,
+	IMEI,
+	HLR-List,
+	LMSI,
+	GlobalCellId,
+	NetworkResource,
+	AdditionalNetworkResource,
+	NAEA-PreferredCI, 
+	NAEA-CIC, 
+	ASCI-CallReference,
+	SubscriberIdentity,
+	PLMN-Id,
+
+	-- data types for CAMEL
+	CellGlobalIdOrServiceAreaIdOrLAI, 
+	CellGlobalIdOrServiceAreaIdFixedLength,
+	LAIFixedLength,
+
+	-- data types for subscriber management
+	BasicServiceCode,
+	Ext-BasicServiceCode,
+	EMLPP-Info,
+	EMLPP-Priority, 
+	MC-SS-Info,
+	MaxMC-Bearers,
+	MC-Bearers,
+	Ext-SS-Status,
+
+	-- data types for geographic location
+	AgeOfLocationInformation,
+	LCSClientExternalID,
+	LCSClientInternalID,
+	LCSServiceTypeID,
+--- WS added exports needed by gsm_map.asn (extra asn1 file to handle older prot. ver.)
+	ProtocolId,
+	LCSServiceTypeID	
+;
+
+IMPORTS
+	TeleserviceCode,
+	Ext-TeleserviceCode
+FROM MAP-TS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-TS-Code (19) version11 (11)}
+
+	BearerServiceCode,
+	Ext-BearerServiceCode
+FROM MAP-BS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-BS-Code (20) version11 (11)}
+
+	SS-Code
+FROM MAP-SS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SS-Code (15) version11 (11)}
+
+	ExtensionContainer
+FROM MAP-ExtensionDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+;
+
+
+-- general data types
+
+TBCD-STRING ::= OCTET STRING
+	-- This type (Telephony Binary Coded Decimal String) is used to
+	-- represent several digits from 0 through 9, *, #, a, b, c, two
+	-- digits per octet, each digit encoded 0000 to 1001 (0 to 9),
+	-- 1010 (*), 1011 (#), 1100 (a), 1101 (b) or 1110 (c); 1111 used
+	-- as filler when there is an odd number of digits.
+
+	-- bits 8765 of octet n encoding digit 2n
+	-- bits 4321 of octet n encoding digit 2(n-1) +1
+
+AddressString ::= OCTET STRING (SIZE (1..maxAddressLength))
+	-- This type is used to represent a number for addressing
+	-- purposes. It is composed of
+	--	a)	one octet for nature of address, and numbering plan
+	--		indicator.
+	--	b)	digits of an address encoded as TBCD-String.
+
+	-- a)	The first octet includes a one bit extension indicator, a
+	--		3 bits nature of address indicator and a 4 bits numbering
+	--		plan indicator, encoded as follows:
+
+	-- bit 8: 1  (no extension)
+
+	-- bits 765: nature of address indicator
+	--	000  unknown
+	--	001  international number
+	--	010  national significant number
+	--	011  network specific number
+	--	100  subscriber number
+	--	101  reserved
+	--	110  abbreviated number
+	--	111  reserved for extension
+
+	-- bits 4321: numbering plan indicator
+	--	0000  unknown
+	--	0001  ISDN/Telephony Numbering Plan (Rec ITU-T E.164)
+	--	0010  spare
+	--	0011  data numbering plan (ITU-T Rec X.121)
+	--	0100  telex numbering plan (ITU-T Rec F.69)
+	--	0101  spare
+	--	0110  land mobile numbering plan (ITU-T Rec E.212)
+	--	0111  spare
+	--	1000  national numbering plan
+	--	1001  private numbering plan
+	--	1111  reserved for extension
+
+	--	all other values are reserved.
+
+	-- b)	The following octets representing digits of an address
+	--		encoded as a TBCD-STRING.
+
+maxAddressLength  INTEGER ::= 20
+
+ISDN-AddressString ::= 
+			AddressString (SIZE (1..maxISDN-AddressLength))
+	-- This type is used to represent ISDN numbers.
+
+maxISDN-AddressLength  INTEGER ::= 9
+
+FTN-AddressString ::= 
+			AddressString (SIZE (1..maxFTN-AddressLength))
+	-- This type is used to represent forwarded-to numbers. 
+	-- If NAI = international the first digits represent the country code (CC)
+	-- and the network destination code (NDC) as for E.164.
+
+maxFTN-AddressLength  INTEGER ::= 15
+
+ISDN-SubaddressString ::= 
+			OCTET STRING (SIZE (1..maxISDN-SubaddressLength))
+	-- This type is used to represent ISDN subaddresses.
+	-- It is composed of
+	--	a)	one octet for type of subaddress and odd/even indicator.
+	--	b)	20 octets for subaddress information.
+
+	--	a)	The first octet includes a one bit extension indicator, a
+	--		3 bits type of subaddress and a one bit odd/even indicator,
+	--		encoded as follows:
+
+	--	bit 8: 1  (no extension)
+
+	--	bits 765: type of subaddress
+	--		000  NSAP (X.213/ISO 8348 AD2)
+	--		010  User Specified
+	--		All other values are reserved
+
+	--	bit 4: odd/even indicator
+	--		0  even number of address signals
+	--		1  odd number of address signals
+	--		The odd/even indicator is used when the type of subaddress
+	--		is "user specified" and the coding is BCD.
+
+	--	bits 321: 000 (unused)
+
+	--	b) Subaddress information.
+	--	The NSAP X.213/ISO8348AD2 address shall be formatted as specified
+	--	by octet 4 which contains the Authority and Format Identifier
+	--	(AFI). The encoding is made according to the "preferred binary
+	--	encoding" as defined in X.213/ISO834AD2. For the definition
+	--	of this type of subaddress, see ITU-T Rec I.334.
+
+	--	For User-specific subaddress, this field is encoded according
+	--	to the user specification, subject to a maximum length of 20
+	--	octets. When interworking with X.25 networks BCD coding should
+	--	be applied.
+
+maxISDN-SubaddressLength  INTEGER ::= 21
+
+ExternalSignalInfo ::= SEQUENCE {
+	protocolId	ProtocolId,
+	signalInfo	SignalInfo,
+	-- Information about the internal structure is given in
+	-- clause 7.6.9.
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	-- extensionContainer must not be used in version 2
+	...}
+
+SignalInfo ::= OCTET STRING (SIZE (1..maxSignalInfoLength))
+
+maxSignalInfoLength  INTEGER ::= 200
+	-- This NamedValue represents the theoretical maximum number of octets which is
+	-- available to carry a single instance of the SignalInfo data type,
+	-- without requiring segmentation to cope with the network layer service.
+	-- However, the actual maximum size available for an instance of the data
+	-- type may be lower, especially when other information elements
+	-- have to be included in the same component.
+
+ProtocolId ::= ENUMERATED {
+	gsm-0408  (1),
+	gsm-0806  (2),
+	gsm-BSSMAP  (3),
+	-- Value 3 is reserved and must not be used
+	ets-300102-1  (4)}
+
+Ext-ExternalSignalInfo ::= SEQUENCE {
+	ext-ProtocolId	Ext-ProtocolId,
+	signalInfo	SignalInfo,
+	-- Information about the internal structure is given in
+	-- clause 7.6.9.10
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+Ext-ProtocolId ::= ENUMERATED {
+	ets-300356  (1),
+	... 
+	 }
+-- exception handling:
+-- For Ext-ExternalSignalInfo sequences containing this parameter with any
+-- other value than the ones listed the receiver shall ignore the whole 
+-- Ext-ExternalSignalInfo sequence.
+
+AccessNetworkSignalInfo ::= SEQUENCE {
+	accessNetworkProtocolId	AccessNetworkProtocolId,
+	signalInfo	LongSignalInfo,
+	-- Information about the internal structure is given in clause 7.6.9.1
+	
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+LongSignalInfo ::= OCTET STRING (SIZE (1..maxLongSignalInfoLength))
+
+maxLongSignalInfoLength  INTEGER ::= 2560
+	-- This Named Value represents the maximum number of octets which is available
+	-- to carry a single instance of the LongSignalInfo data type using
+	-- White Book SCCP with the maximum number of segments.
+	-- It takes account of the octets used by the lower layers of the protocol, and
+	-- other information elements which may be included in the same component.
+
+AccessNetworkProtocolId ::= ENUMERATED {
+	ts3G-48006   (1),
+	ts3G-25413 (2),
+	...}
+	-- exception handling:
+	-- For AccessNetworkSignalInfo sequences containing this parameter with any
+	-- other value than the ones listed the receiver shall ignore the whole 
+	-- AccessNetworkSignalInfo sequence.
+
+AlertingPattern ::= OCTET STRING (SIZE (1) )
+	-- This type is used to represent Alerting Pattern
+
+	--	bits 8765 : 0000 (unused)
+
+	--	bits 43 : type of Pattern
+	--		00 level
+	--		01 category
+	--		10 category
+	--		all other values are reserved.
+
+	--	bits 21 : type of alerting
+
+alertingLevel-0   AlertingPattern ::= '00000000'B
+alertingLevel-1   AlertingPattern ::= '00000001'B
+alertingLevel-2   AlertingPattern ::= '00000010'B
+	-- all other values of Alerting level are reserved
+	-- Alerting Levels are defined in GSM 02.07
+	
+alertingCategory-1   AlertingPattern ::= '00000100'B
+alertingCategory-2   AlertingPattern ::= '00000101'B
+alertingCategory-3   AlertingPattern ::= '00000110'B
+alertingCategory-4   AlertingPattern ::= '00000111'B
+alertingCategory-5   AlertingPattern ::= '00001000'B
+	-- all other values of Alerting Category are reserved
+	-- Alerting categories are defined in GSM 02.07
+
+-- data types for numbering and identification
+
+IMSI ::= TBCD-STRING (SIZE (3..8))
+	-- digits of MCC, MNC, MSIN are concatenated in this order.
+
+Identity ::= CHOICE {
+	imsi			IMSI,
+	imsi-WithLMSI	IMSI-WithLMSI}
+
+IMSI-WithLMSI ::= SEQUENCE {
+	imsi			IMSI,
+	lmsi			LMSI,
+	-- a special value 00000000 indicates that the LMSI is not in use
+	...}
+
+ASCI-CallReference ::= TBCD-STRING (SIZE (1..8))
+	-- digits of VGCS/VBS-area,Group-ID are concatenated in this order if there is a
+	-- VGCS/VBS-area.
+
+TMSI ::= OCTET STRING (SIZE (1..4))
+
+SubscriberId ::= CHOICE {
+	imsi			[0] IMSI,
+	tmsi			[1] TMSI}
+
+IMEI ::= TBCD-STRING (SIZE (8))
+	--	Refers to International Mobile Station Equipment Identity
+	--	and Software Version Number (SVN) defined in TS 3GPP TS 23.003 [17].
+	--	If the SVN is not present the last octet shall contain the
+	--	digit 0 and a filler.
+	--	If present the SVN shall be included in the last octet.
+
+HLR-Id ::= IMSI
+	-- leading digits of IMSI, i.e. (MCC, MNC, leading digits of
+	-- MSIN) forming HLR Id defined in TS 3GPP TS 23.003 [17].
+
+HLR-List ::= SEQUENCE SIZE (1..maxNumOfHLR-Id) OF
+				HLR-Id
+
+maxNumOfHLR-Id  INTEGER ::= 50
+
+LMSI ::= OCTET STRING (SIZE (4))
+
+GlobalCellId ::= OCTET STRING (SIZE (5..7))
+	-- Refers to Cell Global Identification defined in TS 3GPP TS 23.003 [17].
+	-- The internal structure is defined as follows:
+	-- octet 1 bits 4321	Mobile Country Code 1st digit
+	--         bits 8765	Mobile Country Code 2nd digit
+	-- octet 2 bits 4321	Mobile Country Code 3rd digit
+	--         bits 8765	Mobile Network Code 3rd digit
+	--			or filler (1111) for 2 digit MNCs
+	-- octet 3 bits 4321	Mobile Network Code 1st digit
+	--         bits 8765	Mobile Network Code 2nd digit
+	-- octets 4 and 5	Location Area Code according to TS 3GPP TS 24.008 [35]
+	-- octets 6 and 7	Cell Identity (CI) according to TS 3GPP TS 24.008 [35]
+
+NetworkResource ::= ENUMERATED {
+	plmn  (0),
+	hlr  (1),
+	vlr  (2),
+	pvlr  (3),
+	controllingMSC  (4),
+	vmsc  (5),
+	eir  (6),
+	rss  (7)}
+
+AdditionalNetworkResource ::= ENUMERATED {
+	sgsn (0),
+	ggsn (1),
+	gmlc (2),
+	gsmSCF (3),
+	nplr (4),
+	auc (5),
+	... ,
+	ue (6),
+	mme (7)}
+	-- if unknown value is received in AdditionalNetworkResource
+	-- it shall be ignored.
+
+
+NAEA-PreferredCI ::= SEQUENCE {
+	naea-PreferredCIC	[0] NAEA-CIC,
+	extensionContainer	[1] ExtensionContainer	OPTIONAL,
+	...}
+
+NAEA-CIC ::= OCTET STRING (SIZE (3))
+	-- The internal structure is defined by the Carrier Identification
+	-- parameter in ANSI T1.113.3. Carrier codes between "000" and "999" may
+	-- be encoded as 3 digits using "000" to "999" or as 4 digits using 
+	-- "0000" to "0999". Carrier codes between "1000" and "9999" are encoded
+	-- using 4 digits.
+
+SubscriberIdentity ::= CHOICE {
+	imsi			[0] IMSI,
+	msisdn		[1] ISDN-AddressString
+	}
+
+LCSClientExternalID ::= SEQUENCE {
+	externalAddress	[0] ISDN-AddressString	OPTIONAL,
+	extensionContainer	[1] ExtensionContainer	OPTIONAL,
+	... }
+
+LCSClientInternalID ::= ENUMERATED {
+	broadcastService	(0),
+	o-andM-HPLMN	(1),
+	o-andM-VPLMN	(2),
+	anonymousLocation	(3),
+	targetMSsubscribedService	(4),
+	... }
+-- for a CAMEL phase 3 PLMN operator client, the value targetMSsubscribedService shall be used
+
+LCSServiceTypeID ::= INTEGER (0..127)
+	-- the integer values 0-63 are reserved for Standard LCS service types
+	-- the integer values 64-127 are reserved for Non Standard LCS service types
+
+-- Standard LCS Service Types
+emergencyServices		LCSServiceTypeID ::= 0
+emergencyAlertServices		LCSServiceTypeID ::= 1
+personTracking			LCSServiceTypeID ::= 2
+fleetManagement		LCSServiceTypeID ::= 3
+assetManagement		LCSServiceTypeID ::= 4
+trafficCongestionReporting		LCSServiceTypeID ::= 5
+roadsideAssistance		LCSServiceTypeID ::= 6
+routingToNearestCommercialEnterprise		LCSServiceTypeID ::= 7
+navigation			LCSServiceTypeID ::= 8
+	--this service type is reserved for use in previous releases
+citySightseeing		LCSServiceTypeID ::= 9
+localizedAdvertising		LCSServiceTypeID ::= 10
+mobileYellowPages		LCSServiceTypeID ::= 11 
+trafficAndPublicTransportationInfo		LCSServiceTypeID ::= 12
+weather				LCSServiceTypeID ::= 13
+assetAndServiceFinding		LCSServiceTypeID ::= 14
+gaming				LCSServiceTypeID ::= 15
+findYourFriend			LCSServiceTypeID ::= 16
+dating				LCSServiceTypeID ::= 17
+chatting				LCSServiceTypeID ::= 18
+routeFinding			LCSServiceTypeID ::= 19
+whereAmI				LCSServiceTypeID ::= 20
+
+-- The values of LCSServiceTypeID are defined according to 3GPP TS 22.071.
+
+-- Non Standard LCS Service Types
+serv64				LCSServiceTypeID ::= 64
+serv65				LCSServiceTypeID ::= 65
+serv66				LCSServiceTypeID ::= 66
+serv67				LCSServiceTypeID ::= 67
+serv68				LCSServiceTypeID ::= 68
+serv69				LCSServiceTypeID ::= 69
+serv70				LCSServiceTypeID ::= 70
+serv71				LCSServiceTypeID ::= 71
+serv72				LCSServiceTypeID ::= 72
+serv73				LCSServiceTypeID ::= 73
+serv74				LCSServiceTypeID ::= 74
+serv75				LCSServiceTypeID ::= 75
+serv76				LCSServiceTypeID ::= 76
+serv77				LCSServiceTypeID ::= 77
+serv78				LCSServiceTypeID ::= 78
+serv79				LCSServiceTypeID ::= 79
+serv80				LCSServiceTypeID ::= 80
+serv81				LCSServiceTypeID ::= 81
+serv82				LCSServiceTypeID ::= 82
+serv83				LCSServiceTypeID ::= 83
+serv84				LCSServiceTypeID ::= 84
+serv85				LCSServiceTypeID ::= 85
+serv86				LCSServiceTypeID ::= 86
+serv87				LCSServiceTypeID ::= 87
+serv88				LCSServiceTypeID ::= 88
+serv89				LCSServiceTypeID ::= 89
+serv90				LCSServiceTypeID ::= 90
+serv91				LCSServiceTypeID ::= 91
+serv92				LCSServiceTypeID ::= 92
+serv93				LCSServiceTypeID ::= 93
+serv94				LCSServiceTypeID ::= 94
+serv95				LCSServiceTypeID ::= 95
+serv96				LCSServiceTypeID ::= 96
+serv97				LCSServiceTypeID ::= 97
+serv98				LCSServiceTypeID ::= 98
+serv99				LCSServiceTypeID ::= 99
+serv100				LCSServiceTypeID ::= 100
+serv101				LCSServiceTypeID ::= 101
+serv102				LCSServiceTypeID ::= 102
+serv103				LCSServiceTypeID ::= 103
+serv104				LCSServiceTypeID ::= 104
+serv105				LCSServiceTypeID ::= 105
+serv106				LCSServiceTypeID ::= 106
+serv107				LCSServiceTypeID ::= 107
+serv108				LCSServiceTypeID ::= 108
+serv109				LCSServiceTypeID ::= 109
+serv110				LCSServiceTypeID ::= 110
+serv111				LCSServiceTypeID ::= 111
+serv112				LCSServiceTypeID ::= 112
+serv113				LCSServiceTypeID ::= 113
+serv114				LCSServiceTypeID ::= 114
+serv115				LCSServiceTypeID ::= 115
+serv116				LCSServiceTypeID ::= 116
+serv117				LCSServiceTypeID ::= 117
+serv118				LCSServiceTypeID ::= 118
+serv119				LCSServiceTypeID ::= 119
+serv120				LCSServiceTypeID ::= 120
+serv121				LCSServiceTypeID ::= 121
+serv122				LCSServiceTypeID ::= 122
+serv123				LCSServiceTypeID ::= 123
+serv124				LCSServiceTypeID ::= 124
+serv125				LCSServiceTypeID ::= 125
+serv126				LCSServiceTypeID ::= 126
+serv127				LCSServiceTypeID ::= 127
+
+PLMN-Id ::= OCTET STRING (SIZE (3))
+	-- The internal structure is defined as follows:
+	-- octet 1 bits 4321	Mobile Country Code 1st digit
+	--         bits 8765	Mobile Country Code 2nd digit
+	-- octet 2 bits 4321	Mobile Country Code 3rd digit
+	--         bits 8765	Mobile Network Code 3rd digit
+	--			or filler (1111) for 2 digit MNCs
+	-- octet 3 bits 4321	Mobile Network Code 1st digit
+	--         bits 8765	Mobile Network Code 2nd digit
+
+-- data types for CAMEL
+
+CellGlobalIdOrServiceAreaIdOrLAI ::= CHOICE {
+	cellGlobalIdOrServiceAreaIdFixedLength	[0] CellGlobalIdOrServiceAreaIdFixedLength,
+	laiFixedLength	[1] LAIFixedLength}
+
+CellGlobalIdOrServiceAreaIdFixedLength ::= OCTET STRING (SIZE (7))
+	-- Refers to Cell Global Identification or Service Are Identification
+	-- defined in 3GPP TS 23.003.
+	-- The internal structure is defined as follows:
+	-- octet 1 bits 4321	Mobile Country Code 1st digit
+	--         bits 8765	Mobile Country Code 2nd digit
+	-- octet 2 bits 4321	Mobile Country Code 3rd digit
+	--         bits 8765	Mobile Network Code 3rd digit
+	--			or filler (1111) for 2 digit MNCs
+	-- octet 3 bits 4321	Mobile Network Code 1st digit
+	--         bits 8765	Mobile Network Code 2nd digit
+	-- octets 4 and 5	Location Area Code according to 3GPP TS 24.008
+	-- octets 6 and 7	Cell Identity (CI) value or 
+	-- 			Service Area Code (SAC) value 
+	--			according to 3GPP TS 23.003
+
+LAIFixedLength ::= OCTET STRING (SIZE (5))
+	-- Refers to Location Area Identification defined in 3GPP TS 23.003 [17].
+	-- The internal structure is defined as follows:
+	-- octet 1 bits 4321	Mobile Country Code 1st digit
+	--         bits 8765	Mobile Country Code 2nd digit
+	-- octet 2 bits 4321	Mobile Country Code 3rd digit
+	--         bits 8765	Mobile Network Code 3rd digit
+	--			or filler (1111) for 2 digit MNCs
+	-- octet 3 bits 4321	Mobile Network Code 1st digit
+	--         bits 8765	Mobile Network Code 2nd digit
+	-- octets 4 and 5	Location Area Code according to 3GPP TS 24.008 [35]
+
+-- data types for subscriber management
+
+BasicServiceCode ::= CHOICE {
+	bearerService	[2] BearerServiceCode,
+	teleservice	[3] TeleserviceCode}
+
+Ext-BasicServiceCode ::= CHOICE {
+	ext-BearerService	[2] Ext-BearerServiceCode,
+	ext-Teleservice	[3] Ext-TeleserviceCode}
+
+EMLPP-Info ::= SEQUENCE {
+	maximumentitledPriority	EMLPP-Priority,
+	defaultPriority	EMLPP-Priority,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+EMLPP-Priority ::= INTEGER (0..15)
+	-- The mapping from the values A,B,0,1,2,3,4 to the integer-value is
+	-- specified as follows where A is the highest and 4 is the lowest
+	-- priority level
+	-- the integer values 7-15 are spare and shall be mapped to value 4
+
+priorityLevelA		EMLPP-Priority ::= 6
+priorityLevelB		EMLPP-Priority ::= 5
+priorityLevel0		EMLPP-Priority ::= 0
+priorityLevel1		EMLPP-Priority ::= 1
+priorityLevel2		EMLPP-Priority ::= 2
+priorityLevel3		EMLPP-Priority ::= 3
+priorityLevel4		EMLPP-Priority ::= 4
+
+MC-SS-Info ::= SEQUENCE {
+	ss-Code		[0] SS-Code,
+	ss-Status		[1] Ext-SS-Status,
+	nbrSB		[2] MaxMC-Bearers,
+	nbrUser		[3] MC-Bearers,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	...}
+
+MaxMC-Bearers ::= INTEGER (2..maxNumOfMC-Bearers)
+
+MC-Bearers ::= INTEGER (1..maxNumOfMC-Bearers)
+
+maxNumOfMC-Bearers  INTEGER ::= 7
+
+Ext-SS-Status ::= OCTET STRING (SIZE (1..5))
+
+	-- OCTET 1:
+	--
+	-- bits 8765: 0000 (unused)
+	-- bits 4321: Used to convey the "P bit","R bit","A bit" and "Q bit",
+	--		    representing supplementary service state information
+	--		    as defined in TS 3GPP TS 23.011 [22]
+
+	-- bit 4: "Q bit"
+
+	-- bit 3: "P bit"
+
+	-- bit 2: "R bit"
+
+	-- bit 1: "A bit"
+
+	-- OCTETS 2-5: reserved for future use. They shall be discarded if
+	-- received and not understood.
+
+
+	-- data types for geographic location
+
+AgeOfLocationInformation ::= INTEGER (0..32767)
+-- the value represents the elapsed time in minutes since the last
+-- network contact of the mobile station (i.e. the actuality of the
+-- location information).
+-- value "0" indicates that the MS is currently in contact with the
+--           network
+-- value "32767" indicates that the location information is at least
+--               32767 minutes old
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-ER-DataTypes.asn b/rrlp-ephemeris/asn1/MAP-ER-DataTypes.asn
new file mode 100644
index 0000000..d0b90fc
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-ER-DataTypes.asn
@@ -0,0 +1,415 @@
+-- $Id: MAP-ER-DataTypes.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- 3GPP TS 29.002 V8.9.0 (2009-04) 
+-- 17.7.7	Error data types
+
+MAP-ER-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ER-DataTypes (17) version11 (11)}
+
+DEFINITIONS
+
+IMPLICIT TAGS
+
+::=
+
+BEGIN
+
+EXPORTS
+	RoamingNotAllowedParam,
+	CallBarredParam,
+	CUG-RejectParam,
+	SS-IncompatibilityCause,
+	PW-RegistrationFailureCause,
+	SM-DeliveryFailureCause,
+	SystemFailureParam,
+	DataMissingParam,
+	UnexpectedDataParam,
+	FacilityNotSupParam,
+	OR-NotAllowedParam,
+	UnknownSubscriberParam,
+	NumberChangedParam,
+	UnidentifiedSubParam,
+	IllegalSubscriberParam,
+	IllegalEquipmentParam,
+	BearerServNotProvParam,
+	TeleservNotProvParam,
+	TracingBufferFullParam,
+	NoRoamingNbParam,
+	AbsentSubscriberParam,
+	BusySubscriberParam,
+	NoSubscriberReplyParam,
+	ForwardingViolationParam,
+	ForwardingFailedParam, 
+	ATI-NotAllowedParam,
+	SubBusyForMT-SMS-Param,
+	MessageWaitListFullParam,
+	AbsentSubscriberSM-Param,
+	AbsentSubscriberDiagnosticSM,
+	ResourceLimitationParam,
+	NoGroupCallNbParam,
+	IncompatibleTerminalParam,
+	ShortTermDenialParam,
+	LongTermDenialParam,
+	UnauthorizedRequestingNetwork-Param,
+	UnauthorizedLCSClient-Param,
+	PositionMethodFailure-Param,
+UnknownOrUnreachableLCSClient-Param,
+	MM-EventNotSupported-Param,
+ATSI-NotAllowedParam,
+ATM-NotAllowedParam,
+IllegalSS-OperationParam,
+SS-NotAvailableParam,
+SS-SubscriptionViolationParam,
+InformationNotAvailableParam,
+TargetCellOutsideGCA-Param,
+OngoingGroupCallParam
+
+;
+
+IMPORTS
+	SS-Status
+FROM MAP-SS-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SS-DataTypes (14) version11 (11)}
+
+	SignalInfo,
+	BasicServiceCode,
+	NetworkResource,
+	AdditionalNetworkResource
+FROM MAP-CommonDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-CommonDataTypes (18) version11 (11)}
+
+
+	SS-Code
+FROM MAP-SS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SS-Code (15) version11 (11)}
+
+	ExtensionContainer
+FROM MAP-ExtensionDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+;
+
+RoamingNotAllowedParam ::= SEQUENCE {
+	roamingNotAllowedCause	RoamingNotAllowedCause,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	additionalRoamingNotAllowedCause	[0] AdditionalRoamingNotAllowedCause OPTIONAL }
+
+--	if the additionalRoamingNotallowedCause is received by the MSC/VLR or SGSN then the 
+--	roamingNotAllowedCause shall be discarded.
+
+AdditionalRoamingNotAllowedCause ::= ENUMERATED {
+	supportedRAT-TypesNotAllowed (0),
+	...}
+
+RoamingNotAllowedCause ::= ENUMERATED {
+	plmnRoamingNotAllowed  (0),
+	operatorDeterminedBarring  (3)}
+
+CallBarredParam ::= CHOICE {
+	callBarringCause	CallBarringCause,
+	-- call BarringCause must not be used in version 3 and higher
+	extensibleCallBarredParam	ExtensibleCallBarredParam
+	-- extensibleCallBarredParam must not be used in version <3
+	}
+
+CallBarringCause ::= ENUMERATED {
+	barringServiceActive  (0),
+	operatorBarring  (1)}
+
+ExtensibleCallBarredParam ::= SEQUENCE {
+	callBarringCause	CallBarringCause	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	unauthorisedMessageOriginator	[1] NULL		OPTIONAL }
+
+CUG-RejectParam ::= SEQUENCE {
+	cug-RejectCause	CUG-RejectCause	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+CUG-RejectCause ::= ENUMERATED {
+	incomingCallsBarredWithinCUG  (0),
+	subscriberNotMemberOfCUG  (1),
+	requestedBasicServiceViolatesCUG-Constraints  (5),
+	calledPartySS-InteractionViolation  (7)}
+
+SS-IncompatibilityCause ::= SEQUENCE {
+	ss-Code		[1] SS-Code	OPTIONAL,
+	basicService	BasicServiceCode	OPTIONAL,
+	ss-Status		[4] SS-Status	OPTIONAL,
+	...}
+
+PW-RegistrationFailureCause ::= ENUMERATED {
+	undetermined  (0),
+	invalidFormat  (1),
+	newPasswordsMismatch  (2)}
+
+SM-EnumeratedDeliveryFailureCause ::= ENUMERATED {
+	memoryCapacityExceeded  (0),
+	equipmentProtocolError  (1),
+	equipmentNotSM-Equipped  (2),
+	unknownServiceCentre  (3),
+	sc-Congestion  (4),
+	invalidSME-Address  (5),
+	subscriberNotSC-Subscriber  (6)}
+
+SM-DeliveryFailureCause ::= SEQUENCE {
+	sm-EnumeratedDeliveryFailureCause	SM-EnumeratedDeliveryFailureCause,
+	diagnosticInfo	SignalInfo	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+AbsentSubscriberSM-Param ::= SEQUENCE {
+	absentSubscriberDiagnosticSM	AbsentSubscriberDiagnosticSM	OPTIONAL,
+	-- AbsentSubscriberDiagnosticSM can be either for non-GPRS 
+	-- or for GPRS
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	additionalAbsentSubscriberDiagnosticSM  	[0] 	AbsentSubscriberDiagnosticSM	OPTIONAL }
+	-- if received, additionalAbsentSubscriberDiagnosticSM 
+	-- is for GPRS and absentSubscriberDiagnosticSM is 
+	-- for non-GPRS
+
+AbsentSubscriberDiagnosticSM ::= INTEGER (0..255)
+	-- AbsentSubscriberDiagnosticSM values are defined in 3GPP TS 23.040
+
+SystemFailureParam ::= CHOICE {
+	networkResource	NetworkResource,
+	-- networkResource must not be used in version 3
+	extensibleSystemFailureParam	ExtensibleSystemFailureParam
+	-- extensibleSystemFailureParam must not be used in version <3
+	}
+
+ExtensibleSystemFailureParam ::= SEQUENCE {
+	networkResource	NetworkResource	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	additionalNetworkResource	[0] AdditionalNetworkResource	OPTIONAL,
+	failureCauseParam	[1] FailureCauseParam	OPTIONAL }
+
+FailureCauseParam ::= ENUMERATED {
+	limitReachedOnNumberOfConcurrentLocationRequests (0),
+	... }
+	-- if unknown value is received in FailureCauseParam it shall be ignored
+
+
+DataMissingParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+UnexpectedDataParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+FacilityNotSupParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	shapeOfLocationEstimateNotSupported [0]	NULL		OPTIONAL,
+	neededLcsCapabilityNotSupportedInServingNode [1] NULL	OPTIONAL }
+
+OR-NotAllowedParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+UnknownSubscriberParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	unknownSubscriberDiagnostic	UnknownSubscriberDiagnostic	OPTIONAL}
+
+UnknownSubscriberDiagnostic ::= ENUMERATED {
+	imsiUnknown  (0),
+	gprs-eps-SubscriptionUnknown  (1),
+	...,
+	npdbMismatch  (2)}
+	-- if unknown values are received in 	
+	-- UnknownSubscriberDiagnostic they shall be discarded
+
+NumberChangedParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+UnidentifiedSubParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+IllegalSubscriberParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+IllegalEquipmentParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+BearerServNotProvParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+TeleservNotProvParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+TracingBufferFullParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+NoRoamingNbParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+AbsentSubscriberParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	absentSubscriberReason	[0] AbsentSubscriberReason	OPTIONAL}
+
+AbsentSubscriberReason ::= ENUMERATED {
+	imsiDetach (0),
+	restrictedArea (1),
+	noPageResponse (2),
+	... ,
+	purgedMS (3)}
+-- exception handling: at reception of other values than the ones listed the 
+-- AbsentSubscriberReason shall be ignored. 
+-- The AbsentSubscriberReason: purgedMS is defined for the Super-Charger feature 
+-- (see TS 23.116). If this value is received in a Provide Roaming Number response
+-- it shall be mapped to the AbsentSubscriberReason: imsiDetach in the Send Routeing
+-- Information response
+
+BusySubscriberParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	ccbs-Possible	[0] NULL		OPTIONAL,
+	ccbs-Busy		[1] NULL		OPTIONAL}
+
+NoSubscriberReplyParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+ForwardingViolationParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+ForwardingFailedParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+ATI-NotAllowedParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+ATSI-NotAllowedParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+ATM-NotAllowedParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+IllegalSS-OperationParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+SS-NotAvailableParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+SS-SubscriptionViolationParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+InformationNotAvailableParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+SubBusyForMT-SMS-Param ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	gprsConnectionSuspended	NULL			OPTIONAL }
+	-- If GprsConnectionSuspended is not understood it shall 
+	-- be discarded
+
+MessageWaitListFullParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+ResourceLimitationParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+NoGroupCallNbParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+IncompatibleTerminalParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+ShortTermDenialParam ::= SEQUENCE {
+	...}
+
+LongTermDenialParam ::= SEQUENCE {
+	...}
+
+UnauthorizedRequestingNetwork-Param ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+UnauthorizedLCSClient-Param ::= SEQUENCE {
+	unauthorizedLCSClient-Diagnostic	[0] UnauthorizedLCSClient-Diagnostic	OPTIONAL,
+	extensionContainer	[1] ExtensionContainer			OPTIONAL,
+	... }
+
+UnauthorizedLCSClient-Diagnostic ::= ENUMERATED {
+	noAdditionalInformation (0),
+	clientNotInMSPrivacyExceptionList (1),
+	callToClientNotSetup (2),
+	privacyOverrideNotApplicable (3),
+	disallowedByLocalRegulatoryRequirements (4),
+	...,
+	unauthorizedPrivacyClass (5),
+	unauthorizedCallSessionUnrelatedExternalClient (6),
+	unauthorizedCallSessionRelatedExternalClient (7) }
+--	exception handling:
+--	any unrecognized value shall be ignored
+
+PositionMethodFailure-Param ::= SEQUENCE {
+	positionMethodFailure-Diagnostic	[0] PositionMethodFailure-Diagnostic	OPTIONAL,
+	extensionContainer	[1] ExtensionContainer			OPTIONAL,
+	... }
+
+PositionMethodFailure-Diagnostic ::= ENUMERATED {
+	congestion  (0),
+	insufficientResources  (1),
+	insufficientMeasurementData  (2),
+	inconsistentMeasurementData  (3),
+	locationProcedureNotCompleted  (4),
+	locationProcedureNotSupportedByTargetMS  (5),
+	qoSNotAttainable  (6),
+	positionMethodNotAvailableInNetwork	(7),
+	positionMethodNotAvailableInLocationArea	(8),
+	... }
+--	exception handling:
+--	any unrecognized value shall be ignored
+
+UnknownOrUnreachableLCSClient-Param ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+MM-EventNotSupported-Param ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+TargetCellOutsideGCA-Param ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+OngoingGroupCallParam ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-ExtensionDataTypes.asn b/rrlp-ephemeris/asn1/MAP-ExtensionDataTypes.asn
new file mode 100644
index 0000000..d94c057
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-ExtensionDataTypes.asn
@@ -0,0 +1,74 @@
+-- $Id: MAP-ExtensionDataTypes.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- MAP-ExtensionDataTypes.asn
+--
+-- Taken from 3GPP TS 29.002 V8.9.0 (2009-04)
+--
+-- 17.7.11 Extension data types
+--
+
+MAP-ExtensionDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+
+DEFINITIONS
+
+IMPLICIT TAGS
+
+::=
+
+BEGIN
+
+EXPORTS
+
+	PrivateExtension,
+	ExtensionContainer,
+	SLR-ArgExtensionContainer;
+
+
+-- IOC for private MAP extensions
+
+
+MAP-EXTENSION  ::= CLASS {
+	&ExtensionType				OPTIONAL,
+	&extensionId 	OBJECT IDENTIFIER }
+	-- The length of the Object Identifier shall not exceed 16 octets and the
+	-- number of components of the Object Identifier shall not exceed 16
+
+-- data types
+
+ExtensionContainer ::= SEQUENCE {
+	privateExtensionList	[0]PrivateExtensionList	OPTIONAL, 
+	pcs-Extensions	[1]PCS-Extensions	OPTIONAL,
+	...}
+
+SLR-ArgExtensionContainer ::= SEQUENCE {
+	privateExtensionList	[0]PrivateExtensionList	OPTIONAL, 
+	slr-Arg-PCS-Extensions	[1]SLR-Arg-PCS-Extensions	OPTIONAL,
+	...}
+
+PrivateExtensionList ::= SEQUENCE SIZE (1..maxNumOfPrivateExtensions) OF
+				PrivateExtension
+
+PrivateExtension ::= SEQUENCE {
+	extId		MAP-EXTENSION.&extensionId
+				({ExtensionSet}),
+	extType		MAP-EXTENSION.&ExtensionType
+				({ExtensionSet}{@extId})	OPTIONAL}
+
+maxNumOfPrivateExtensions  INTEGER ::= 10
+
+ExtensionSet		MAP-EXTENSION ::=
+		{...
+		 -- ExtensionSet is the set of all defined private extensions
+	}
+	-- Unsupported private extensions shall be discarded if received.
+
+PCS-Extensions ::= SEQUENCE {
+	...}
+
+SLR-Arg-PCS-Extensions ::= SEQUENCE {
+	...,
+	na-ESRK-Request	[0]	NULL		OPTIONAL }
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-LCS-DataTypes.asn b/rrlp-ephemeris/asn1/MAP-LCS-DataTypes.asn
new file mode 100644
index 0000000..2434b89
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-LCS-DataTypes.asn
@@ -0,0 +1,657 @@
+-- $Id: MAP-LCS-DataTypes.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- MAP-LCS-DataTypes.asn
+--
+-- Taken from 3GPP TS 29.002  V8.9.0 (2009-04)
+--
+-- 17.7.13 Location service data types
+--
+
+MAP-LCS-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-LCS-DataTypes (25) version11 (11)}
+
+DEFINITIONS
+IMPLICIT TAGS
+::=
+BEGIN
+
+EXPORTS
+	RoutingInfoForLCS-Arg,
+	RoutingInfoForLCS-Res,
+	ProvideSubscriberLocation-Arg,
+	ProvideSubscriberLocation-Res,
+	SubscriberLocationReport-Arg,
+	SubscriberLocationReport-Res,
+LocationType, 
+DeferredLocationEventType,
+LCSClientName,
+LCS-QoS,
+Horizontal-Accuracy,
+ResponseTime,
+Ext-GeographicalInformation, 
+VelocityEstimate,
+SupportedGADShapes,
+Add-GeographicalInformation,
+LCSRequestorID, 
+LCS-ReferenceNumber,
+LCSCodeword,
+AreaEventInfo,
+ReportingPLMNList,
+PeriodicLDRInfo,
+SequenceNumber
+;
+
+IMPORTS
+	AddressString,
+	ISDN-AddressString,
+	IMEI,
+	IMSI,
+	LMSI,
+	SubscriberIdentity,
+	AgeOfLocationInformation,
+	LCSClientExternalID,
+	LCSClientInternalID,
+LCSServiceTypeID,
+CellGlobalIdOrServiceAreaIdOrLAI,
+PLMN-Id
+FROM MAP-CommonDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-CommonDataTypes (18) version11 (11)}
+
+	ExtensionContainer,
+	SLR-ArgExtensionContainer
+FROM MAP-ExtensionDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+
+	USSD-DataCodingScheme,
+USSD-String
+FROM MAP-SS-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0) gsm-Network (1) modules (3)
+   map-SS-DataTypes (14) version11 (11)}
+
+	APN,
+	GSN-Address,
+	SupportedLCS-CapabilitySets
+FROM MAP-MS-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-MS-DataTypes (11) version11 (11)}
+
+	Additional-Number
+FROM MAP-SM-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SM-DataTypes (16) version11 (11)}
+;
+
+
+RoutingInfoForLCS-Arg ::= SEQUENCE {
+	mlcNumber		[0] ISDN-AddressString,
+	targetMS		[1] SubscriberIdentity,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...}
+
+RoutingInfoForLCS-Res ::= SEQUENCE {
+	targetMS		[0] SubscriberIdentity,
+	lcsLocationInfo	[1] LCSLocationInfo,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...,
+	v-gmlc-Address	[3]	GSN-Address	OPTIONAL,
+	h-gmlc-Address	[4]	GSN-Address	OPTIONAL,
+	ppr-Address	[5]	GSN-Address	OPTIONAL,
+	additional-v-gmlc-Address	[6]	GSN-Address	OPTIONAL }
+
+LCSLocationInfo ::= SEQUENCE {
+	networkNode-Number	ISDN-AddressString,
+	-- NetworkNode-number can be either msc-number or sgsn-number
+	lmsi			[0] LMSI		OPTIONAL,
+	extensionContainer	[1] ExtensionContainer	OPTIONAL,
+	... ,
+	gprsNodeIndicator 	[2] NULL		OPTIONAL,
+	-- gprsNodeIndicator is set only if the SGSN number is sent as the Network Node Number
+	additional-Number	[3] Additional-Number	OPTIONAL,
+	supportedLCS-CapabilitySets	[4]	SupportedLCS-CapabilitySets	OPTIONAL,
+	additional-LCS-CapabilitySets	[5]	SupportedLCS-CapabilitySets	OPTIONAL
+	}
+
+ProvideSubscriberLocation-Arg ::= SEQUENCE {
+	locationType	LocationType,
+	mlc-Number	ISDN-AddressString,
+	lcs-ClientID	[0] LCS-ClientID	OPTIONAL,
+	privacyOverride	[1] NULL		OPTIONAL,
+	imsi			[2] IMSI		OPTIONAL,
+	msisdn		[3] ISDN-AddressString	OPTIONAL,
+	lmsi			[4] LMSI		OPTIONAL,
+	imei			[5] IMEI		OPTIONAL,
+	lcs-Priority	[6] LCS-Priority	OPTIONAL,
+	lcs-QoS		[7] LCS-QoS	OPTIONAL,
+	extensionContainer	[8] ExtensionContainer	OPTIONAL,
+	... ,
+	supportedGADShapes	[9]	SupportedGADShapes	OPTIONAL,
+	lcs-ReferenceNumber	[10]	LCS-ReferenceNumber	OPTIONAL,
+	lcsServiceTypeID	[11]	LCSServiceTypeID	OPTIONAL,
+	lcsCodeword	[12]	LCSCodeword	OPTIONAL,
+	lcs-PrivacyCheck	[13]	LCS-PrivacyCheck	OPTIONAL,
+	areaEventInfo	[14]	AreaEventInfo	OPTIONAL,
+	h-gmlc-Address	[15]	GSN-Address	OPTIONAL,
+	mo-lrShortCircuitIndicator	[16] NULL		OPTIONAL,
+	periodicLDRInfo	[17] PeriodicLDRInfo	OPTIONAL,
+	reportingPLMNList	[18] ReportingPLMNList	OPTIONAL }
+
+	-- one of imsi or msisdn is mandatory
+	-- If a location estimate type indicates activate deferred location or cancel deferred 
+	-- location, a lcs-Reference number shall be included.
+
+LocationType ::= SEQUENCE {
+	locationEstimateType	[0] LocationEstimateType,
+	...,
+	deferredLocationEventType	[1] DeferredLocationEventType	OPTIONAL }
+
+LocationEstimateType ::= ENUMERATED {
+	currentLocation	(0),
+	currentOrLastKnownLocation	(1),
+	initialLocation	(2),
+	...,
+	activateDeferredLocation	(3),
+	cancelDeferredLocation	(4) ,
+	notificationVerificationOnly	(5) }
+--	exception handling:
+--	a ProvideSubscriberLocation-Arg containing an unrecognized LocationEstimateType
+--	shall be rejected by the receiver with a return error cause of unexpected data value
+
+DeferredLocationEventType ::= BIT STRING {
+	msAvailable	(0) ,
+	enteringIntoArea	(1),
+	leavingFromArea	(2),
+	beingInsideArea	(3) ,
+	periodicLDR	(4)  } (SIZE (1..16)) 
+-- beingInsideArea is always treated as oneTimeEvent regardless of the possible value
+-- of occurrenceInfo inside areaEventInfo.
+-- exception handling:
+-- a ProvideSubscriberLocation-Arg containing other values than listed above in 
+-- DeferredLocationEventType shall be rejected by the receiver with a return error cause of 
+-- unexpected data value.
+
+LCS-ClientID ::= SEQUENCE {
+	lcsClientType	[0] LCSClientType,
+	lcsClientExternalID	[1] LCSClientExternalID	OPTIONAL,
+	lcsClientDialedByMS	[2] AddressString	OPTIONAL,
+	lcsClientInternalID	[3] LCSClientInternalID	OPTIONAL,
+	lcsClientName	[4] LCSClientName	OPTIONAL,
+	...,
+	lcsAPN		[5] APN		OPTIONAL,
+	lcsRequestorID	[6] LCSRequestorID	OPTIONAL }
+
+LCSClientType ::= ENUMERATED {
+	emergencyServices	(0),
+	valueAddedServices	(1),
+	plmnOperatorServices	(2),
+	lawfulInterceptServices	(3),
+	... }
+	--	exception handling:
+	--	unrecognized values may be ignored if the LCS client uses the privacy override
+	--	otherwise, an unrecognized value shall be treated as unexpected data by a receiver
+	--	a return error shall then be returned if received in a MAP invoke 
+
+LCSClientName ::= SEQUENCE {
+	dataCodingScheme	[0] USSD-DataCodingScheme,
+	nameString	[2] NameString,
+	...,
+	lcs-FormatIndicator	[3] LCS-FormatIndicator	OPTIONAL }
+
+-- The USSD-DataCodingScheme shall indicate use of the default alphabet through the
+-- following encoding
+--	bit	7 6 5 4 3 2 1 0
+--		0 0 0 0 1 1 1 1
+
+NameString ::= USSD-String (SIZE (1..maxNameStringLength))
+
+maxNameStringLength  INTEGER ::= 63
+
+LCSRequestorID ::= SEQUENCE {
+	dataCodingScheme	[0] USSD-DataCodingScheme,
+	requestorIDString	[1] RequestorIDString,
+	...,
+	lcs-FormatIndicator	[2] LCS-FormatIndicator	OPTIONAL }
+
+RequestorIDString ::= USSD-String (SIZE (1..maxRequestorIDStringLength))
+
+maxRequestorIDStringLength  INTEGER ::= 63
+
+LCS-FormatIndicator ::= ENUMERATED {
+	logicalName	(0),
+	e-mailAddress	(1),
+	msisdn		(2),
+	url			(3),
+	sipUrl		(4),
+	... }
+
+LCS-Priority ::= OCTET STRING (SIZE (1))
+	-- 0 = highest priority
+	-- 1 = normal priority
+	-- all other values treated as 1 
+
+LCS-QoS ::= SEQUENCE {
+	horizontal-accuracy	[0] Horizontal-Accuracy	OPTIONAL,
+	verticalCoordinateRequest	[1] NULL		OPTIONAL,
+	vertical-accuracy	[2] Vertical-Accuracy	OPTIONAL,	responseTime	[3] ResponseTime	OPTIONAL,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	...,
+	velocityRequest	[5] NULL		OPTIONAL
+}
+
+Horizontal-Accuracy ::= OCTET STRING (SIZE (1))
+	-- bit 8 = 0
+	-- bits 7-1 = 7 bit Uncertainty Code defined in 3GPP TS 23.032. The horizontal location 
+	-- error should be less than the error indicated by the uncertainty code with 67%
+	-- confidence.
+
+Vertical-Accuracy ::= OCTET STRING (SIZE (1))
+	-- bit 8 = 0
+	-- bits 7-1 = 7 bit Vertical Uncertainty Code defined in 3GPP TS 23.032. 
+	-- The vertical location error should be less than the error indicated 
+	-- by the uncertainty code with 67% confidence.
+
+ResponseTime ::= SEQUENCE {
+	responseTimeCategory	ResponseTimeCategory,
+	...}
+--	note: an expandable SEQUENCE simplifies later addition of a numeric response time.
+
+ResponseTimeCategory ::= ENUMERATED {
+	lowdelay  (0),
+	delaytolerant  (1),
+	... }
+--	exception handling:
+--	an unrecognized value shall be treated the same as value 1 (delaytolerant)
+
+SupportedGADShapes ::= BIT STRING {
+	ellipsoidPoint  (0),
+	ellipsoidPointWithUncertaintyCircle (1),
+	ellipsoidPointWithUncertaintyEllipse (2),
+	polygon (3),
+	ellipsoidPointWithAltitude (4),
+	ellipsoidPointWithAltitudeAndUncertaintyElipsoid (5),
+	ellipsoidArc  (6) } (SIZE (7..16))
+-- A node shall mark in the BIT STRING all Shapes defined in 3GPP TS 23.032 it supports.
+-- exception handling: bits 7 to 15 shall be ignored if received.
+
+LCS-ReferenceNumber::= OCTET STRING (SIZE(1))
+
+LCSCodeword ::= SEQUENCE {
+	dataCodingScheme	[0] USSD-DataCodingScheme,
+	lcsCodewordString	[1] LCSCodewordString,
+	...}
+
+LCSCodewordString ::= USSD-String (SIZE (1..maxLCSCodewordStringLength))
+
+maxLCSCodewordStringLength  INTEGER ::= 20
+
+LCS-PrivacyCheck ::= SEQUENCE {
+	callSessionUnrelated	[0] PrivacyCheckRelatedAction,
+	callSessionRelated	[1] PrivacyCheckRelatedAction	OPTIONAL,
+	...}
+
+PrivacyCheckRelatedAction ::= ENUMERATED {
+	allowedWithoutNotification (0),
+	allowedWithNotification (1),
+	allowedIfNoResponse (2),
+	restrictedIfNoResponse (3),
+	notAllowed (4),
+	...}
+--	exception handling:
+--	a ProvideSubscriberLocation-Arg containing an unrecognized PrivacyCheckRelatedAction
+--	shall be rejected by the receiver with a return error cause of unexpected data value
+
+AreaEventInfo ::= SEQUENCE {
+	areaDefinition	[0]	AreaDefinition,
+	occurrenceInfo	[1]	OccurrenceInfo	OPTIONAL,
+	intervalTime	[2]	IntervalTime	OPTIONAL,
+	...}
+
+AreaDefinition ::= SEQUENCE {
+	areaList		[0]	AreaList,
+	...}
+
+AreaList ::= SEQUENCE SIZE (1..maxNumOfAreas) OF Area
+
+maxNumOfAreas  INTEGER ::= 10
+
+Area ::= SEQUENCE {
+	areaType		[0]	AreaType,
+	areaIdentification	[1]	AreaIdentification,
+	...}
+
+AreaType ::= ENUMERATED {
+	countryCode	(0),
+	plmnId		(1),
+	locationAreaId	(2),
+	routingAreaId	(3),
+	cellGlobalId	(4),
+	...,
+	utranCellId	(5) }
+
+AreaIdentification ::= OCTET STRING (SIZE (2..7))
+	-- The internal structure is defined as follows:
+	-- octet 1 bits 4321	Mobile Country Code 1st digit
+	--         bits 8765	Mobile Country Code 2nd digit
+	-- octet 2 bits 4321	Mobile Country Code 3rd digit
+	--         bits 8765	Mobile Network Code 3rd digit if 3 digit MNC included
+	--			or filler (1111)
+	-- octet 3 bits 4321	Mobile Network Code 1st digit
+	--         bits 8765	Mobile Network Code 2nd digit
+	-- octets 4 and 5	Location Area Code (LAC) for Local Area Id,
+	--			Routing Area Id and Cell Global Id
+	-- octet 6	Routing Area Code (RAC) for Routing Area Id
+	-- octets 6 and 7	Cell Identity (CI) for Cell Global Id
+	-- octets 4 until 7	Utran Cell Identity (UC-Id) for Utran Cell Id
+
+OccurrenceInfo ::= ENUMERATED {
+	oneTimeEvent	(0),
+	multipleTimeEvent	(1),
+	...}
+
+IntervalTime ::= INTEGER (1..32767)
+	-- minimum interval time between area reports in seconds
+
+PeriodicLDRInfo ::= SEQUENCE {
+	reportingAmount		ReportingAmount,
+	reportingInterval	ReportingInterval,
+	...}
+-- reportingInterval x reportingAmount shall not exceed 8639999 (99 days, 23 hours,
+-- 59 minutes and 59 seconds) for compatibility with OMA MLP and RLP
+
+ReportingAmount ::= INTEGER (1..maxReportingAmount)
+
+maxReportingAmount INTEGER ::= 8639999
+
+ReportingInterval ::= INTEGER (1..maxReportingInterval)
+-- ReportingInterval is in seconds
+
+maxReportingInterval INTEGER ::= 8639999
+
+ReportingPLMNList::= SEQUENCE {
+	plmn-ListPrioritized			[0] NULL					OPTIONAL,
+	plmn-List 						[1] PLMNList,
+	...}
+
+PLMNList::= SEQUENCE SIZE (1..maxNumOfReportingPLMN) OF
+				ReportingPLMN
+
+maxNumOfReportingPLMN INTEGER ::= 20
+
+ReportingPLMN::= SEQUENCE {
+	plmn-Id 						[0] PLMN-Id,
+	ran-Technology 					[1] RAN-Technology			OPTIONAL,
+	ran-PeriodicLocationSupport		[2] NULL					OPTIONAL,
+	...}
+
+RAN-Technology ::= ENUMERATED {
+	gsm			(0),
+	umts		(1),
+	...}
+
+ProvideSubscriberLocation-Res ::= SEQUENCE {
+	locationEstimate	Ext-GeographicalInformation,
+	ageOfLocationEstimate	[0] AgeOfLocationInformation	OPTIONAL,
+	extensionContainer	[1] ExtensionContainer	OPTIONAL,
+	... ,
+	add-LocationEstimate	[2] Add-GeographicalInformation 	OPTIONAL,
+	deferredmt-lrResponseIndicator	[3] NULL		OPTIONAL,
+	geranPositioningData	[4] PositioningDataInformation	OPTIONAL,
+	utranPositioningData	[5] UtranPositioningDataInfo	OPTIONAL,
+	cellIdOrSai	[6] CellGlobalIdOrServiceAreaIdOrLAI	OPTIONAL,
+	sai-Present	[7] NULL		OPTIONAL,
+	accuracyFulfilmentIndicator	[8] AccuracyFulfilmentIndicator		OPTIONAL,
+	velocityEstimate	[9] VelocityEstimate	OPTIONAL,
+	mo-lrShortCircuitIndicator	[10] NULL		OPTIONAL }
+
+--	if deferredmt-lrResponseIndicator is set, locationEstimate is ignored.
+
+-- the add-LocationEstimate parameter shall not be sent to a node that did not indicate the
+-- geographic shapes supported in the ProvideSubscriberLocation-Arg
+-- The locationEstimate and the add-locationEstimate parameters shall not be sent if
+-- the supportedGADShapes parameter has been received in ProvideSubscriberLocation-Arg
+-- and the shape encoded in locationEstimate or add-LocationEstimate is not marked
+-- as supported in supportedGADShapes. In such a case ProvideSubscriberLocation
+-- shall be rejected with error FacilityNotSupported with additional indication
+-- shapeOfLocationEstimateNotSupported.
+-- sai-Present indicates that the cellIdOrSai parameter contains a Service Area Identity.
+
+AccuracyFulfilmentIndicator ::= ENUMERATED {
+	requestedAccuracyFulfilled  (0),
+	requestedAccuracyNotFulfilled  (1),
+	...	}
+
+Ext-GeographicalInformation ::= OCTET STRING (SIZE (1..maxExt-GeographicalInformation))
+	-- Refers to geographical Information defined in 3GPP TS 23.032.
+	-- This is composed of 1 or more octets with an internal structure according to
+	-- 3GPP TS 23.032
+	-- Octet 1: Type of shape, only the following shapes in 3GPP TS 23.032 are allowed:
+	--		(a) Ellipsoid point with uncertainty circle
+	--		(b) Ellipsoid point with uncertainty ellipse
+	--		(c) Ellipsoid point with altitude and uncertainty ellipsoid
+	--		(d) Ellipsoid Arc
+	--		(e) Ellipsoid Point
+	-- Any other value in octet 1 shall be treated as invalid
+	-- Octets 2 to 8 for case (a) – Ellipsoid point with uncertainty circle
+	--		Degrees of Latitude				3 octets
+	--		Degrees of Longitude				3 octets
+	--		Uncertainty code				1 octet
+	-- Octets 2 to 11 for case (b) – Ellipsoid point with uncertainty ellipse:
+	--		Degrees of Latitude				3 octets
+	--		Degrees of Longitude				3 octets
+	--		Uncertainty semi-major axis				1 octet
+	--		Uncertainty semi-minor axis				1 octet
+	--		Angle of major axis				1 octet
+	--		Confidence				1 octet
+	-- Octets 2 to 14 for case (c) – Ellipsoid point with altitude and uncertainty ellipsoid
+	--		Degrees of Latitude				3 octets
+	--		Degrees of Longitude				3 octets
+	--		Altitude				2 octets
+	--		Uncertainty semi-major axis				1 octet
+	--		Uncertainty semi-minor axis				1 octet
+	--		Angle of major axis				1 octet
+	--		Uncertainty altitude				1 octet
+	--		Confidence				1 octet
+	-- Octets 2 to 13 for case (d) – Ellipsoid Arc
+	--		Degrees of Latitude				3 octets
+	--		Degrees of Longitude				3 octets
+	--		Inner radius				2 octets
+	--		Uncertainty radius				1 octet
+	--		Offset angle				1 octet
+	--		Included angle				1 octet
+	--		Confidence				1 octet
+	-- Octets 2 to 7 for case (e) – Ellipsoid Point
+	--		Degrees of Latitude				3 octets
+	--		Degrees of Longitude				3 octets
+
+	--
+	-- An Ext-GeographicalInformation parameter comprising more than one octet and
+	-- containing any other shape or an incorrect number of octets or coding according
+	-- to 3GPP TS 23.032 shall be treated as invalid data by a receiver.
+	--
+	-- An Ext-GeographicalInformation parameter comprising one octet shall be discarded
+	-- by the receiver if an Add-GeographicalInformation parameter is received 
+	-- in the same message.
+	--
+	-- An Ext-GeographicalInformation parameter comprising one octet shall be treated as
+	-- invalid data by the receiver if an Add-GeographicalInformation parameter is not
+	-- received in the same message.
+
+maxExt-GeographicalInformation  INTEGER ::= 20
+	-- the maximum length allows for further shapes in 3GPP TS 23.032 to be included in later 
+	-- versions of 3GPP TS 29.002
+
+VelocityEstimate ::= OCTET STRING (SIZE (4..7))
+	-- Refers to Velocity description defined in 3GPP TS 23.032.
+	-- This is composed of 4 or more octets with an internal structure according to
+	-- 3GPP TS 23.032
+	-- Octet 1: Type of velocity, only the following types in 3GPP TS 23.032 are allowed:
+	--		(a) Horizontal Velocity
+	--		(b) Horizontal with Vertical Velocity
+	--		(c) Horizontal Velocity with Uncertainty
+	--		(d) Horizontal with Vertical Velocity and Uncertainty
+	-- For types Horizontal with Vertical Velocity and Horizontal with Vertical Velocity
+	-- and Uncertainty, the direction of the Vertical Speed is also included in Octet 1
+	-- Any other value in octet 1 shall be treated as invalid
+	-- Octets 2 to 4 for case (a) Horizontal velocity:
+	--		Bearing				1 octet
+	--		Horizontal Speed				2 octets
+	-- Octets 2 to 5 for case (b) – Horizontal with Vertical Velocity:
+	--		Bearing				1 octet
+	--		Horizontal Speed				2 octets
+	--		Vertical Speed				1 octet
+	-- Octets 2 to 5 for case (c) – Horizontal velocity with Uncertainty:
+	--		Bearing				1 octet
+	--		Horizontal Speed				2 octets
+	--		Uncertainty Speed				1 octet
+	-- Octets 2 to 7 for case (d) – Horizontal with Vertical Velocity and Uncertainty:
+	--		Bearing				1 octet
+	--		Horizontal Speed				2 octets
+	--		Vertical Speed				1 octet
+	--		Horizontal Uncertainty Speed			1 octet
+	--		Vertical Uncertainty Speed				1 octet
+
+PositioningDataInformation ::= OCTET STRING (SIZE (2..maxPositioningDataInformation))
+	-- Refers to the Positioning Data defined in 3GPP TS 49.031.
+	-- This is composed of 2 or more octets with an internal structure according to
+	-- 3GPP TS 49.031. 
+
+maxPositioningDataInformation INTEGER ::= 10
+	-- 
+
+UtranPositioningDataInfo ::= OCTET STRING (SIZE (3..maxUtranPositioningDataInfo))
+	-- Refers to the Position Data defined in 3GPP TS 25.413.
+	-- This is composed of the positioningDataDiscriminator and the positioningDataSet
+	-- included in positionData as defined in 3GPP TS 25.413.
+
+maxUtranPositioningDataInfo INTEGER ::= 11
+	-- 
+
+Add-GeographicalInformation ::= OCTET STRING (SIZE (1..maxAdd-GeographicalInformation))
+	-- Refers to geographical Information defined in 3GPP TS 23.032.
+	-- This is composed of 1 or more octets with an internal structure according to 
+	-- 3GPP TS 23.032
+	-- Octet 1: Type of shape, all the shapes defined in 3GPP TS 23.032 are allowed:
+	-- Octets 2 to n (where n is the total number of octets necessary to encode the shape
+	-- according to 3GPP TS 23.032) are used to encode the shape itself in accordance with the
+	-- encoding defined in 3GPP TS 23.032
+	--
+	-- An Add-GeographicalInformation parameter, whether valid or invalid, received 
+	-- together with a valid Ext-GeographicalInformation parameter in the same message 
+	-- shall be discarded.
+	--
+	-- An Add-GeographicalInformation parameter containing any shape not defined in 
+	-- 3GPP TS 23.032 or an incorrect number of octets or coding according to 
+	-- 3GPP TS 23.032 shall be treated as invalid data by a receiver if not received 
+	-- together with a valid Ext-GeographicalInformation parameter in the same message.
+
+maxAdd-GeographicalInformation  INTEGER ::= 91
+	-- the maximum length allows support for all the shapes currently defined in 3GPP TS 23.032
+
+SubscriberLocationReport-Arg ::= SEQUENCE {
+	lcs-Event		LCS-Event,
+	lcs-ClientID	LCS-ClientID, 
+	lcsLocationInfo	LCSLocationInfo,
+	msisdn		[0] ISDN-AddressString	OPTIONAL,
+	imsi			[1] IMSI		OPTIONAL,
+	imei			[2] IMEI		OPTIONAL,
+	na-ESRD		[3] ISDN-AddressString	OPTIONAL,
+	na-ESRK		[4] ISDN-AddressString	OPTIONAL,
+	locationEstimate	[5] Ext-GeographicalInformation	OPTIONAL,
+	ageOfLocationEstimate	[6] AgeOfLocationInformation	OPTIONAL,
+	slr-ArgExtensionContainer	[7] SLR-ArgExtensionContainer	OPTIONAL,
+	... ,
+	add-LocationEstimate	[8] Add-GeographicalInformation	OPTIONAL,
+	deferredmt-lrData	[9] Deferredmt-lrData	OPTIONAL, 
+	lcs-ReferenceNumber	[10] LCS-ReferenceNumber	OPTIONAL,
+	geranPositioningData	[11] PositioningDataInformation	OPTIONAL,
+	utranPositioningData	[12] UtranPositioningDataInfo	OPTIONAL,
+	cellIdOrSai	[13]	CellGlobalIdOrServiceAreaIdOrLAI	OPTIONAL,
+	h-gmlc-Address	[14]	GSN-Address	OPTIONAL,
+	lcsServiceTypeID	[15]	LCSServiceTypeID	OPTIONAL,
+	sai-Present	[17] NULL		OPTIONAL,
+	pseudonymIndicator	[18] NULL		OPTIONAL,
+	accuracyFulfilmentIndicator	[19] AccuracyFulfilmentIndicator	OPTIONAL,
+	velocityEstimate	[20] VelocityEstimate	OPTIONAL,
+	sequenceNumber	[21] SequenceNumber	OPTIONAL,
+	periodicLDRInfo	[22] PeriodicLDRInfo	OPTIONAL,
+	mo-lrShortCircuitIndicator	[23] NULL		OPTIONAL }
+
+	-- one of msisdn or imsi is mandatory
+	-- a location estimate that is valid for the locationEstimate parameter should 
+	-- be transferred in this parameter in preference to the add-LocationEstimate.
+	-- the deferredmt-lrData parameter shall be included if and only if the lcs-Event
+	-- indicates a deferredmt-lrResponse.
+	-- if the lcs-Event indicates a deferredmt-lrResponse then the locationEstimate 
+	-- and the add-locationEstimate parameters shall not be sent if the 
+	-- supportedGADShapes parameter had been received in ProvideSubscriberLocation-Arg
+	-- and the shape encoded in locationEstimate or add-LocationEstimate was not marked
+	-- as supported in supportedGADShapes. In such a case terminationCause 
+	-- in deferredmt-lrData shall be present with value 
+	-- shapeOfLocationEstimateNotSupported. 
+	-- If a lcs event indicates deferred mt-lr response, the lcs-Reference number shall be 
+	-- included. 
+	-- sai-Present indicates that the cellIdOrSai parameter contains a Service Area Identity.
+
+Deferredmt-lrData ::= SEQUENCE {
+	deferredLocationEventType	DeferredLocationEventType,
+	terminationCause	[0] TerminationCause	OPTIONAL,
+	lcsLocationInfo	[1] LCSLocationInfo	OPTIONAL,
+	...}
+	-- lcsLocationInfo may be included only if a terminationCause is present 
+	-- indicating mt-lrRestart.
+
+LCS-Event ::= ENUMERATED {
+	emergencyCallOrigination  (0),
+	emergencyCallRelease  (1), 
+	mo-lr  (2),
+	...,
+	deferredmt-lrResponse  (3) ,
+	deferredmo-lrTTTPInitiation  (4)  }
+	--	deferredmt-lrResponse is applicable to the delivery of a location estimate 
+	--	for an LDR initiated earlier by either the network (via an MT-LR activate deferred 
+	--	location) or the UE (via a deferred MO-LR TTTP initiation)
+	--	exception handling:
+	--	a SubscriberLocationReport-Arg containing an unrecognized LCS-Event
+	--	shall be rejected by a receiver with a return error cause of unexpected data value
+
+TerminationCause ::= ENUMERATED {
+	normal  (0),
+	errorundefined  (1),
+	internalTimeout  (2),
+	congestion  (3),
+	mt-lrRestart  (4),
+	privacyViolation  (5),
+	...,
+	shapeOfLocationEstimateNotSupported (6) ,
+	subscriberTermination (7),
+	uETermination (8),
+	networkTermination (9)  } 
+-- mt-lrRestart shall be used to trigger the GMLC to restart the location procedure, 
+-- either because the sending node knows that the terminal has moved under coverage 
+-- of another MSC or SGSN (e.g. Send Identification received), or because the subscriber
+-- has been deregistered due to a Cancel Location received from HLR.
+--
+-- exception handling
+-- an unrecognized value shall be treated the same as value 1 (errorundefined) 
+
+SequenceNumber ::= INTEGER (1..maxReportingAmount)
+
+SubscriberLocationReport-Res ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL, 
+	..., 
+	na-ESRK		[0] ISDN-AddressString	OPTIONAL,
+	na-ESRD		[1] ISDN-AddressString	OPTIONAL,
+	h-gmlc-Address	[2]	GSN-Address	OPTIONAL,
+	mo-lrShortCircuitIndicator	[3] NULL		OPTIONAL,
+	reportingPLMNList	[4] ReportingPLMNList	OPTIONAL,
+	lcs-ReferenceNumber	[5]	LCS-ReferenceNumber	OPTIONAL }
+
+-- na-ESRK and na-ESRD are mutually exclusive
+--
+-- exception handling
+-- receipt of both na-ESRK and na-ESRD shall be treated the same as a return error
+
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-MS-DataTypes.asn b/rrlp-ephemeris/asn1/MAP-MS-DataTypes.asn
new file mode 100644
index 0000000..9c12a02
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-MS-DataTypes.asn
@@ -0,0 +1,2780 @@
+-- $Id: MAP-MS-DataTypes.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- 3GPP TS 29.002 V8.9.0 (2009-04) 
+-- 17.7.1	Mobile Service data types
+
+MAP-MS-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-MS-DataTypes (11) version11 (11)}
+
+DEFINITIONS
+
+IMPLICIT TAGS
+
+::=
+
+BEGIN
+
+EXPORTS
+
+	-- location registration types
+	UpdateLocationArg,
+	UpdateLocationRes,
+	CancelLocationArg,
+	CancelLocationRes, 
+	PurgeMS-Arg, 
+	PurgeMS-Res,
+	SendIdentificationArg,
+	SendIdentificationRes, 
+	UpdateGprsLocationArg,
+	UpdateGprsLocationRes,
+	IST-SupportIndicator, 
+	SupportedLCS-CapabilitySets,
+
+	-- gprs location registration types
+	GSN-Address,
+
+	-- handover types
+	ForwardAccessSignalling-Arg,
+	PrepareHO-Arg,
+	PrepareHO-Res,
+	PrepareSubsequentHO-Arg, 
+	PrepareSubsequentHO-Res,
+	ProcessAccessSignalling-Arg,
+	SendEndSignal-Arg,
+	SendEndSignal-Res,
+
+	-- authentication management types
+	SendAuthenticationInfoArg,
+	SendAuthenticationInfoRes, 
+	AuthenticationFailureReportArg,
+AuthenticationFailureReportRes,
+
+	-- security management types
+	Kc, 
+	Cksn,
+
+	-- equipment management types
+	CheckIMEI-Arg,
+	CheckIMEI-Res,
+
+	-- subscriber management types
+	InsertSubscriberDataArg,
+	InsertSubscriberDataRes, 
+	LSAIdentity,
+	DeleteSubscriberDataArg,
+	DeleteSubscriberDataRes,
+	Ext-QoS-Subscribed,
+	Ext2-QoS-Subscribed, 
+	Ext3-QoS-Subscribed,
+	SubscriberData,
+	ODB-Data,
+	SubscriberStatus,
+	ZoneCodeList,
+	maxNumOfZoneCodes, 
+	O-CSI, 
+D-CSI,
+	O-BcsmCamelTDPCriteriaList, 
+	T-BCSM-CAMEL-TDP-CriteriaList,
+	SS-CSI,
+	ServiceKey,
+	DefaultCallHandling,
+	CamelCapabilityHandling,
+	BasicServiceCriteria,
+	SupportedCamelPhases,
+	OfferedCamel4CSIs,
+	OfferedCamel4Functionalities,
+	maxNumOfCamelTDPData,
+	CUG-Index, 
+	CUG-Info,
+	CUG-Interlock,
+	InterCUG-Restrictions,
+	IntraCUG-Options,
+	NotificationToMSUser, 
+	QoS-Subscribed,
+IST-AlertTimerValue,
+	T-CSI,
+	T-BcsmTriggerDetectionPoint,
+APN,
+AdditionalInfo,
+
+	-- fault recovery types
+	ResetArg,
+	RestoreDataArg,
+	RestoreDataRes,
+
+-- provide subscriber info types 
+GeographicalInformation, 
+MS-Classmark2,
+GPRSMSClass,
+
+	-- subscriber information enquiry types
+	ProvideSubscriberInfoArg,
+	ProvideSubscriberInfoRes,
+	SubscriberInfo,
+	LocationInformation,
+	LocationInformationGPRS,
+	RAIdentity,
+	SubscriberState,
+	GPRSChargingID, 
+MNPInfoRes,
+	RouteingNumber,
+
+	-- any time information enquiry types
+	AnyTimeInterrogationArg,
+	AnyTimeInterrogationRes,
+
+	-- any time information handling types
+	AnyTimeSubscriptionInterrogationArg,
+	AnyTimeSubscriptionInterrogationRes,
+	AnyTimeModificationArg,
+	AnyTimeModificationRes,
+
+	-- subscriber data modification notification types
+	NoteSubscriberDataModifiedArg,
+	NoteSubscriberDataModifiedRes,
+
+	-- gprs location information retrieval types
+	SendRoutingInfoForGprsArg,
+	SendRoutingInfoForGprsRes,
+
+	-- failure reporting types
+	FailureReportArg,
+	FailureReportRes,
+
+	-- gprs notification types
+	NoteMsPresentForGprsArg,
+	NoteMsPresentForGprsRes,
+
+	-- Mobility Management types
+NoteMM-EventArg,
+	NoteMM-EventRes,
+	NumberPortabilityStatus,
+	PagingArea,
+
+	-- VGCS / VBS types types
+GroupId, 
+Long-GroupId,
+AdditionalSubscriptions
+
+;
+
+IMPORTS
+	maxNumOfSS,
+	SS-SubscriptionOption,
+	SS-List,
+	SS-ForBS-Code,
+	Password
+FROM MAP-SS-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SS-DataTypes (14) version11 (11)}
+
+	SS-Code
+FROM MAP-SS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SS-Code (15) version11 (11)}
+
+	Ext-BearerServiceCode
+FROM MAP-BS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-BS-Code (20) version11 (11)}
+
+	Ext-TeleserviceCode
+FROM MAP-TS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-TS-Code (19) version11 (11)}
+
+	AddressString,
+ISDN-AddressString, 
+	ISDN-SubaddressString, 
+	FTN-AddressString,
+	AccessNetworkSignalInfo,
+	IMSI, 
+	IMEI,
+	TMSI,
+	HLR-List,
+	LMSI,
+	Identity,
+	GlobalCellId,
+	CellGlobalIdOrServiceAreaIdOrLAI,
+	Ext-BasicServiceCode,
+	NAEA-PreferredCI,
+	EMLPP-Info, 
+	MC-SS-Info,
+	SubscriberIdentity,
+	AgeOfLocationInformation,
+	LCSClientExternalID,
+	LCSClientInternalID,
+	Ext-SS-Status,
+	LCSServiceTypeID,
+	ASCI-CallReference,
+	TBCD-STRING,
+	LAIFixedLength,
+	PLMN-Id,
+EMLPP-Priority
+FROM MAP-CommonDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-CommonDataTypes (18) version11 (11)}
+
+	ExtensionContainer
+FROM MAP-ExtensionDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+
+	AbsentSubscriberDiagnosticSM
+FROM MAP-ER-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ER-DataTypes (17) version11 (11)}
+
+	TracePropagationList
+FROM MAP-OM-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-OM-DataTypes (12) version11 (11)}
+
+;
+
+-- location registration types
+
+UpdateLocationArg ::= SEQUENCE {
+	imsi			IMSI,
+	msc-Number	[1] ISDN-AddressString,
+	vlr-Number	ISDN-AddressString,
+	lmsi			[10] LMSI		OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	vlr-Capability	[6] VLR-Capability	OPTIONAL,
+	informPreviousNetworkEntity	[11]	NULL		OPTIONAL,
+	cs-LCS-NotSupportedByUE	[12]	NULL		OPTIONAL,
+	v-gmlc-Address	[2]	GSN-Address	OPTIONAL,
+	add-info		[13] ADD-Info	OPTIONAL,
+	pagingArea	[14] PagingArea	OPTIONAL,
+	skipSubscriberDataUpdate	[15] NULL		OPTIONAL 
+	-- The skipSubscriberDataUpdate parameter in the UpdateLocationArg and the ADD-Info
+	-- structures carry the same semantic.
+	 }
+
+VLR-Capability ::= SEQUENCE{
+	supportedCamelPhases  	[0] SupportedCamelPhases	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	solsaSupportIndicator	[2] NULL		OPTIONAL,
+	istSupportIndicator	[1] IST-SupportIndicator	OPTIONAL,
+	superChargerSupportedInServingNetworkEntity	[3] SuperChargerInfo	OPTIONAL,
+	longFTN-Supported	[4]	NULL		OPTIONAL,
+	supportedLCS-CapabilitySets	[5]	SupportedLCS-CapabilitySets	OPTIONAL,
+	offeredCamel4CSIs	[6] OfferedCamel4CSIs	OPTIONAL,
+	supportedRAT-TypesIndicator	[7]	SupportedRAT-Types	OPTIONAL,
+	longGroupID-Supported	[8]	NULL		OPTIONAL }
+
+SupportedRAT-Types::= BIT STRING {
+	utran  (0),
+	geran  (1),
+	gan    (2),
+	i-hspa-evolution (3),
+	e-utran	(4)} (SIZE (2..8))
+	-- exception handling: bits 5 to 7 shall be ignored if received and not understood
+	 
+
+
+SuperChargerInfo ::= CHOICE {
+	sendSubscriberData	[0] NULL,
+	subscriberDataStored	[1] AgeIndicator }
+
+AgeIndicator ::= OCTET STRING (SIZE (1..6))
+	-- The internal structure of this parameter is implementation specific.
+
+IST-SupportIndicator ::=  ENUMERATED {
+	basicISTSupported	(0),
+	istCommandSupported	(1),
+	...}
+-- exception handling:
+-- reception of values > 1 shall be mapped to ' istCommandSupported '
+
+SupportedLCS-CapabilitySets ::= BIT STRING {
+	lcsCapabilitySet1 (0),
+	lcsCapabilitySet2 (1),
+	lcsCapabilitySet3 (2),
+	lcsCapabilitySet4 (3) ,
+	lcsCapabilitySet5 (4) } (SIZE (2..16)) 
+-- Core network signalling capability set1 indicates LCS Release98 or Release99 version.
+-- Core network signalling capability set2 indicates LCS Release4.
+-- Core network signalling capability set3 indicates LCS Release5.
+-- Core network signalling capability set4 indicates LCS Release6.
+-- Core network signalling capability set5 indicates LCS Release7 or later version.
+-- A node shall mark in the BIT STRING all LCS capability sets it supports. 
+-- If no bit is set then the sending node does not support LCS.
+-- If the parameter is not sent by an VLR then the VLR may support at most capability set1.
+-- If the parameter is not sent by an SGSN then no support for LCS is assumed.
+-- An SGSN is not allowed to indicate support of capability set1.
+-- Other bits than listed above shall be discarded.
+
+UpdateLocationRes ::= SEQUENCE {
+	hlr-Number	ISDN-AddressString,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	add-Capability	NULL			OPTIONAL,
+	pagingArea-Capability	[0]NULL			OPTIONAL }
+
+ADD-Info ::= SEQUENCE {
+	imeisv		[0] IMEI,
+	skipSubscriberDataUpdate	[1] NULL		OPTIONAL,
+	-- The skipSubscriberDataUpdate parameter in the UpdateLocationArg and the ADD-Info
+	-- structures carry the same semantic.
+	...}
+
+
+PagingArea ::= SEQUENCE SIZE (1..5) OF LocationArea 
+
+
+LocationArea ::= CHOICE {
+	laiFixedLength	[0] LAIFixedLength,
+	lac			[1] LAC}
+
+
+LAC ::= OCTET STRING (SIZE (2))
+	-- Refers to Location Area Code of the Location Area Identification defined in 
+     -- 3GPP TS 23.003 [17].
+	-- Location Area Code according to 3GPP TS 24.008 [35]
+
+CancelLocationArg ::= [3] SEQUENCE {
+	identity		Identity,
+	cancellationType	CancellationType	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	typeOfUpdate	[0] TypeOfUpdate	OPTIONAL }
+
+TypeOfUpdate ::= ENUMERATED {
+	sgsn-change (0),
+	mme-change (1),
+	...}
+	-- TypeOfUpdate shall be absent if CancellationType is different from updateProcedure
+
+CancellationType ::= ENUMERATED {
+	updateProcedure	(0),
+	subscriptionWithdraw	(1),
+	...}
+	-- The HLR shall not send values other than listed above
+
+CancelLocationRes ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+PurgeMS-Arg ::= [3] SEQUENCE {
+	imsi			IMSI,
+	vlr-Number	[0] ISDN-AddressString	OPTIONAL,
+	sgsn-Number	[1]	ISDN-AddressString	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+PurgeMS-Res ::= SEQUENCE {
+	freezeTMSI	[0]	NULL		OPTIONAL,
+	freezeP-TMSI	[1]	NULL		OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	freezeM-TMSI	[2]	NULL		OPTIONAL }
+
+SendIdentificationArg ::= SEQUENCE {
+	tmsi			TMSI,
+	numberOfRequestedVectors	NumberOfRequestedVectors 	OPTIONAL,
+	-- within a dialogue numberOfRequestedVectors shall be present in 
+	-- the first service request and shall not be present in subsequent service requests. 
+	-- If received in a subsequent service request it shall be discarded. 
+	segmentationProhibited	NULL			OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	msc-Number	ISDN-AddressString 	OPTIONAL,
+	previous-LAI	[0] LAIFixedLength	OPTIONAL,
+	hopCounter	[1] HopCounter	OPTIONAL }
+
+HopCounter ::= INTEGER (0..3)
+
+SendIdentificationRes ::= [3] SEQUENCE {
+	imsi			IMSI			OPTIONAL,
+	-- IMSI shall be present in the first (or only) service response of a dialogue.
+	-- If multiple service requests are present in a dialogue then IMSI
+	-- shall not be present in any service response other than the first one.
+	authenticationSetList	AuthenticationSetList	OPTIONAL,
+	currentSecurityContext	[2]CurrentSecurityContext	OPTIONAL,
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	...}
+
+-- authentication management types
+
+AuthenticationSetList ::= CHOICE {
+	tripletList	[0] TripletList,
+	quintupletList	[1] QuintupletList }
+
+TripletList ::= SEQUENCE SIZE (1..5) OF
+				AuthenticationTriplet
+
+QuintupletList ::= SEQUENCE SIZE (1..5) OF
+				AuthenticationQuintuplet
+
+AuthenticationTriplet ::= SEQUENCE {
+	rand			RAND,
+	sres			SRES,
+	kc			Kc,
+	...}
+
+AuthenticationQuintuplet ::= SEQUENCE {
+	rand			RAND,
+	xres			XRES,
+	ck			CK,
+	ik			IK,
+	autn			AUTN,
+	...}
+
+CurrentSecurityContext ::= CHOICE {
+	gsm-SecurityContextData	[0] GSM-SecurityContextData,
+	umts-SecurityContextData	[1] UMTS-SecurityContextData }
+
+GSM-SecurityContextData ::= SEQUENCE {
+	kc			Kc,
+	cksn			Cksn,
+	... }
+
+UMTS-SecurityContextData ::= SEQUENCE {
+	ck			CK,
+	ik			IK,
+	ksi			KSI,
+	... }
+
+RAND ::= OCTET STRING (SIZE (16))
+
+SRES ::= OCTET STRING (SIZE (4))
+
+Kc ::= OCTET STRING (SIZE (8))
+
+XRES ::= OCTET STRING (SIZE (4..16))
+
+CK ::= OCTET STRING (SIZE (16))
+
+IK ::= OCTET STRING (SIZE (16))
+
+AUTN ::= OCTET STRING (SIZE (16))
+
+AUTS ::= OCTET STRING (SIZE (14))
+
+Cksn ::= OCTET STRING (SIZE (1))
+	-- The internal structure is defined in 3GPP TS 24.008
+
+KSI ::= OCTET STRING (SIZE (1))
+	-- The internal structure is defined in 3GPP TS 24.008
+
+AuthenticationFailureReportArg ::= SEQUENCE {
+	imsi			IMSI,
+	failureCause	FailureCause,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	re-attempt	BOOLEAN		OPTIONAL,
+	accessType	AccessType	OPTIONAL,
+	rand			RAND			OPTIONAL,
+	vlr-Number	[0] ISDN-AddressString	OPTIONAL,
+	sgsn-Number	[1] ISDN-AddressString	OPTIONAL }
+
+AccessType ::= ENUMERATED {
+	call (0),
+	emergencyCall (1),
+	locationUpdating (2),
+	supplementaryService (3),
+	shortMessage (4),
+	gprsAttach (5),
+	routingAreaUpdating (6),
+	serviceRequest (7),
+	pdpContextActivation (8),
+	pdpContextDeactivation (9),
+	...,
+	gprsDetach (10)}
+	-- exception handling:
+	-- received values greater than 10 shall be ignored.
+
+AuthenticationFailureReportRes ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+FailureCause ::= ENUMERATED {
+	wrongUserResponse  (0),
+	wrongNetworkSignature  (1)}
+
+-- gprs location registration types
+
+UpdateGprsLocationArg ::= SEQUENCE {
+	imsi			IMSI,
+	sgsn-Number	ISDN-AddressString,	
+	sgsn-Address	GSN-Address,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	sgsn-Capability	[0] SGSN-Capability	OPTIONAL,
+	informPreviousNetworkEntity	[1]	NULL		OPTIONAL,
+	ps-LCS-NotSupportedByUE	[2]	NULL		OPTIONAL,
+	v-gmlc-Address	[3]	GSN-Address	OPTIONAL,
+	add-info		[4]  ADD-Info	OPTIONAL,
+	eps-info		[5]	EPS-Info	OPTIONAL,
+	servingNodeTypeIndicator	[6]	NULL		OPTIONAL,
+	skipSubscriberDataUpdate	[7] NULL		OPTIONAL,
+	usedRAT-Type	[8] Used-RAT-Type	OPTIONAL 
+	 }
+
+Used-RAT-Type::= ENUMERATED {
+	utran  (0),
+	geran  (1),
+	gan    (2),
+	i-hspa-evolution (3),
+	e-utran	(4),
+	...}
+
+EPS-Info ::= CHOICE{
+	pdn-gw-update	[0] PDN-GW-Update,
+	isr-Information	[1] ISR-Information }
+
+PDN-GW-Update ::= SEQUENCE{
+	apn			[0] APN		OPTIONAL,
+	pdn-gw-Identity	[1] PDN-GW-Identity	OPTIONAL,
+	contextId		[2] ContextId                     OPTIONAL,
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	... }
+
+ISR-Information::= BIT STRING {
+	updateMME  (0),
+	cancelSGSN  (1)} (SIZE (2..8))
+	-- exception handling: reception of unknown bit assignments in the
+	-- ISR-Information data type shall be discarded by the receiver 
+
+SGSN-Capability ::= SEQUENCE{
+	solsaSupportIndicator	NULL			OPTIONAL,
+	extensionContainer	[1] ExtensionContainer	OPTIONAL,
+	... ,
+	superChargerSupportedInServingNetworkEntity	[2] SuperChargerInfo	OPTIONAL ,
+	gprsEnhancementsSupportIndicator 	[3] NULL		OPTIONAL,
+	supportedCamelPhases  	[4] SupportedCamelPhases	OPTIONAL,
+	supportedLCS-CapabilitySets	[5]  SupportedLCS-CapabilitySets	OPTIONAL,
+	offeredCamel4CSIs	[6] OfferedCamel4CSIs	OPTIONAL,
+	smsCallBarringSupportIndicator	[7]	NULL		OPTIONAL,	supportedRAT-TypesIndicator	[8]	SupportedRAT-Types	OPTIONAL,
+	supportedFeatures	[9] SupportedFeatures	OPTIONAL }
+
+SupportedFeatures::= BIT STRING {
+	odb-all (0),
+	odb-HPLMN-APN (1),
+	odb-VPLMN-APN (2),
+	regSub (3)} (SIZE (4..8))
+
+GSN-Address ::= OCTET STRING (SIZE (5..17))
+	-- Octets are coded according to TS 3GPP TS 23.003 [17]
+
+UpdateGprsLocationRes ::= SEQUENCE {
+	hlr-Number	ISDN-AddressString,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	add-Capability	NULL			OPTIONAL,
+	sgsn-mmeSeparationSupported	[0] NULL		OPTIONAL }
+
+-- handover types
+
+ForwardAccessSignalling-Arg ::= [3] SEQUENCE {
+	an-APDU		AccessNetworkSignalInfo,
+	integrityProtectionInfo	[0] IntegrityProtectionInformation		OPTIONAL,
+	encryptionInfo	[1] EncryptionInformation		OPTIONAL,
+	keyStatus		[2]	KeyStatus	OPTIONAL,
+	allowedGSM-Algorithms	[4]	AllowedGSM-Algorithms	OPTIONAL,
+	allowedUMTS-Algorithms	[5]	AllowedUMTS-Algorithms	OPTIONAL,
+	radioResourceInformation	[6] RadioResourceInformation	OPTIONAL,
+	extensionContainer	[3]	ExtensionContainer 	OPTIONAL,
+	...,
+	radioResourceList	[7]	RadioResourceList	OPTIONAL,
+	bssmap-ServiceHandover	[9]	BSSMAP-ServiceHandover	OPTIONAL,
+	ranap-ServiceHandover	[8]	RANAP-ServiceHandover	OPTIONAL,
+	bssmap-ServiceHandoverList	[10]	BSSMAP-ServiceHandoverList	OPTIONAL,
+	currentlyUsedCodec	[11] Codec	OPTIONAL,
+	iuSupportedCodecsList	[12] SupportedCodecsList	OPTIONAL,
+	rab-ConfigurationIndicator	[13] NULL		OPTIONAL,
+	iuSelectedCodec	[14]	Codec	OPTIONAL,
+	alternativeChannelType	[15]	RadioResourceInformation	OPTIONAL,
+	tracePropagationList	[17]	TracePropagationList	OPTIONAL }
+
+AllowedGSM-Algorithms ::= OCTET STRING (SIZE (1))
+	-- internal structure is coded as Algorithm identifier octet from
+	-- Permitted Algorithms defined in 3GPP TS 48.008
+	-- A node shall mark all GSM algorithms that are allowed in MSC-B
+
+AllowedUMTS-Algorithms ::= SEQUENCE {
+	integrityProtectionAlgorithms	[0] 	PermittedIntegrityProtectionAlgorithms	OPTIONAL,
+	encryptionAlgorithms	[1] 	PermittedEncryptionAlgorithms		OPTIONAL,
+	extensionContainer	[2]	ExtensionContainer	OPTIONAL,
+	...}
+
+PermittedIntegrityProtectionAlgorithms ::=
+		OCTET STRING (SIZE (1..maxPermittedIntegrityProtectionAlgorithmsLength))
+	-- Octets contain a complete PermittedIntegrityProtectionAlgorithms data type 
+	-- as defined in 3GPP TS 25.413, encoded according to the encoding scheme 
+	-- mandated by 3GPP TS 25.413. 
+	-- Padding bits are included, if needed, in the least significant bits of the 
+	-- last octet of the octet string. 
+
+
+maxPermittedIntegrityProtectionAlgorithmsLength INTEGER ::= 9
+
+PermittedEncryptionAlgorithms ::=
+		OCTET STRING (SIZE (1..maxPermittedEncryptionAlgorithmsLength))
+	-- Octets contain a complete PermittedEncryptionAlgorithms data type 
+	-- as defined in 3GPP TS 25.413, encoded according to the encoding scheme 
+	-- mandated by 3GPP TS 25.413
+	-- Padding bits are included, if needed, in the least significant bits of the 
+	-- last octet of the octet string. 
+
+
+maxPermittedEncryptionAlgorithmsLength INTEGER ::= 9
+
+KeyStatus ::= ENUMERATED {
+	old  (0),
+	new  (1),
+	...}
+	-- exception handling:
+	-- received values in range 2-31 shall be treated as "old"
+	-- received values greater than 31 shall be treated as "new"
+
+PrepareHO-Arg ::= [3] SEQUENCE {
+	targetCellId	[0] GlobalCellId	OPTIONAL,
+	ho-NumberNotRequired	NULL			OPTIONAL, 
+	targetRNCId	[1] RNCId		OPTIONAL,
+	an-APDU		[2] AccessNetworkSignalInfo	OPTIONAL,
+	multipleBearerRequested	[3] NULL		OPTIONAL,
+	imsi			[4] IMSI		OPTIONAL,
+	integrityProtectionInfo	[5] IntegrityProtectionInformation		OPTIONAL,
+	encryptionInfo	[6] EncryptionInformation		OPTIONAL,
+	radioResourceInformation	[7] RadioResourceInformation	OPTIONAL,
+	allowedGSM-Algorithms	[9]	AllowedGSM-Algorithms	OPTIONAL,
+	allowedUMTS-Algorithms	[10]	AllowedUMTS-Algorithms	OPTIONAL,
+	radioResourceList	[11] RadioResourceList	OPTIONAL,
+	extensionContainer	[8] ExtensionContainer	OPTIONAL,
+	... ,
+	rab-Id		[12] RAB-Id	OPTIONAL,
+	bssmap-ServiceHandover	[13]	BSSMAP-ServiceHandover	OPTIONAL,
+	ranap-ServiceHandover	[14]	RANAP-ServiceHandover	OPTIONAL, 
+	bssmap-ServiceHandoverList	[15]	BSSMAP-ServiceHandoverList	OPTIONAL,
+	asciCallReference	[20]	ASCI-CallReference	OPTIONAL,
+	geran-classmark	[16] GERAN-Classmark	OPTIONAL,
+	iuCurrentlyUsedCodec	[17] Codec	OPTIONAL,
+	iuSupportedCodecsList	[18] SupportedCodecsList	OPTIONAL,
+	rab-ConfigurationIndicator	[19] NULL		OPTIONAL,
+	uesbi-Iu		[21]	UESBI-Iu	OPTIONAL,
+	imeisv		[22]	IMEI		OPTIONAL,
+	alternativeChannelType	[23]	RadioResourceInformation	OPTIONAL,
+	tracePropagationList	[25]	TracePropagationList	OPTIONAL	 }
+
+BSSMAP-ServiceHandoverList ::= SEQUENCE SIZE (1.. maxNumOfServiceHandovers) OF
+				BSSMAP-ServiceHandoverInfo
+
+BSSMAP-ServiceHandoverInfo ::= SEQUENCE {
+	bssmap-ServiceHandover	BSSMAP-ServiceHandover,
+	rab-Id		RAB-Id,
+	-- RAB Identity is needed to relate the service handovers with the radio access bearers. 
+	...}
+
+maxNumOfServiceHandovers  INTEGER ::= 7
+
+BSSMAP-ServiceHandover ::= OCTET STRING (SIZE (1))
+	-- Octets are coded according the Service Handover information element in
+	-- 3GPP TS 48.008.
+
+RANAP-ServiceHandover ::= OCTET STRING (SIZE (1))
+	-- Octet contains a complete Service-Handover data type 
+	-- as defined in 3GPP TS 25.413, encoded according to the encoding scheme 
+	-- mandated by 3GPP TS 25.413
+	-- Padding bits are included in the least significant bits. 
+
+
+RadioResourceList ::= SEQUENCE SIZE (1.. maxNumOfRadioResources) OF
+				RadioResource
+
+RadioResource ::= SEQUENCE {
+	radioResourceInformation	RadioResourceInformation,
+	rab-Id		RAB-Id,
+	-- RAB Identity is needed to relate the radio resources with the radio access bearers. 
+	...}
+
+maxNumOfRadioResources  INTEGER ::= 7
+
+PrepareHO-Res ::= [3] SEQUENCE {
+	handoverNumber	[0] ISDN-AddressString	OPTIONAL,
+	relocationNumberList	[1]	RelocationNumberList	OPTIONAL,
+	an-APDU		[2]	AccessNetworkSignalInfo	OPTIONAL,
+	multicallBearerInfo	[3]	MulticallBearerInfo	OPTIONAL,
+	multipleBearerNotSupported	NULL			OPTIONAL,
+	selectedUMTS-Algorithms	[5]	SelectedUMTS-Algorithms	OPTIONAL,
+	chosenRadioResourceInformation	[6] ChosenRadioResourceInformation	 OPTIONAL,
+	extensionContainer	[4]	ExtensionContainer	OPTIONAL,
+	...,
+	iuSelectedCodec	[7] Codec		OPTIONAL,
+	iuAvailableCodecsList	[8] CodecList	OPTIONAL }
+
+SelectedUMTS-Algorithms ::= SEQUENCE {
+	integrityProtectionAlgorithm	[0] 	ChosenIntegrityProtectionAlgorithm	OPTIONAL,
+	encryptionAlgorithm	[1] 	ChosenEncryptionAlgorithm	OPTIONAL,
+	extensionContainer	[2]	ExtensionContainer	OPTIONAL,
+	...}
+
+ChosenIntegrityProtectionAlgorithm ::= OCTET STRING (SIZE (1))
+	-- Octet contains a complete IntegrityProtectionAlgorithm data type 
+	-- as defined in 3GPP TS 25.413, encoded according to the encoding scheme 
+	-- mandated by 3GPP TS 25.413
+	-- Padding bits are included in the least significant bits. 
+
+ChosenEncryptionAlgorithm ::= OCTET STRING (SIZE (1))
+	-- Octet contains a complete EncryptionAlgorithm data type 
+	-- as defined in 3GPP TS 25.413, encoded according to the encoding scheme 
+	-- mandated by 3GPP TS 25.413
+	-- Padding bits are included in the least significant bits. 
+
+ChosenRadioResourceInformation ::= SEQUENCE {
+	chosenChannelInfo	[0] ChosenChannelInfo	OPTIONAL,
+	chosenSpeechVersion	[1] ChosenSpeechVersion	OPTIONAL,
+	...}
+
+ChosenChannelInfo ::= OCTET STRING (SIZE (1))
+	-- Octets are coded according the Chosen Channel information element in 3GPP TS 48.008
+
+ChosenSpeechVersion ::= OCTET STRING (SIZE (1))
+	-- Octets are coded according the Speech Version (chosen) information element in 3GPP TS
+	-- 48.008 
+
+PrepareSubsequentHO-Arg ::= [3] SEQUENCE {
+	targetCellId	[0] GlobalCellId	OPTIONAL,
+	targetMSC-Number	[1] ISDN-AddressString,
+	targetRNCId	[2] RNCId		OPTIONAL,
+	an-APDU		[3]	AccessNetworkSignalInfo	OPTIONAL,
+	selectedRab-Id	[4]	RAB-Id	OPTIONAL,
+	extensionContainer	[5]	ExtensionContainer	OPTIONAL,
+	...,
+	geran-classmark	[6] GERAN-Classmark	OPTIONAL,
+	rab-ConfigurationIndicator	[7] NULL		OPTIONAL }
+
+PrepareSubsequentHO-Res ::= [3] SEQUENCE {
+	an-APDU		AccessNetworkSignalInfo,
+	extensionContainer	[0]	ExtensionContainer	OPTIONAL,
+	...}
+
+ProcessAccessSignalling-Arg ::= [3] SEQUENCE {
+	an-APDU		AccessNetworkSignalInfo,
+	selectedUMTS-Algorithms	[1]	SelectedUMTS-Algorithms	OPTIONAL,
+	selectedGSM-Algorithm	[2]	SelectedGSM-Algorithm	OPTIONAL,
+	chosenRadioResourceInformation	[3] ChosenRadioResourceInformation OPTIONAL,
+	selectedRab-Id	[4] RAB-Id	OPTIONAL,
+	extensionContainer	[0]	ExtensionContainer 	OPTIONAL,
+	...,
+	iUSelectedCodec	[5] Codec		OPTIONAL,
+	iuAvailableCodecsList	[6] CodecList	OPTIONAL }
+
+SupportedCodecsList ::= SEQUENCE {
+	utranCodecList	[0] CodecList	OPTIONAL,
+	geranCodecList	[1] CodecList	OPTIONAL,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...}
+
+CodecList ::= SEQUENCE {
+	codec1		[1] Codec,
+	codec2		[2] Codec		OPTIONAL,
+	codec3		[3] Codec		OPTIONAL,
+	codec4		[4] Codec		OPTIONAL,
+	codec5		[5] Codec		OPTIONAL,
+	codec6		[6] Codec		OPTIONAL,
+	codec7		[7] Codec		OPTIONAL,
+	codec8		[8] Codec		OPTIONAL,
+	extensionContainer	[9] ExtensionContainer	OPTIONAL,
+	...}
+	-- Codecs are sent in priority order where codec1 has highest priority
+
+Codec ::= OCTET STRING (SIZE (1..4))
+
+	-- The internal structure is defined as follows:
+	-- octet 1	Coded as Codec Identification code in 3GPP TS 26.103
+	-- octets 2,3,4	Parameters for the Codec as defined in 3GPP TS
+	--			26.103, if available, length depending on the codec
+
+GERAN-Classmark ::= OCTET STRING (SIZE (2..87))
+	-- Octets are coded according the GERAN Classmark information element in 3GPP TS 48.008
+
+SelectedGSM-Algorithm ::= OCTET STRING (SIZE (1))
+	-- internal structure is coded as Algorithm identifier octet from Chosen Encryption
+	-- Algorithm defined in 3GPP TS 48.008
+	-- A node shall mark only the selected GSM algorithm
+
+SendEndSignal-Arg ::= [3] SEQUENCE {
+	an-APDU		AccessNetworkSignalInfo,
+	extensionContainer	[0]	ExtensionContainer 	OPTIONAL,
+	...}
+
+SendEndSignal-Res ::= SEQUENCE {
+	extensionContainer	[0]	ExtensionContainer 	OPTIONAL,
+	...}
+
+RNCId ::= OCTET STRING (SIZE (7))
+	-- The internal structure is defined as follows:
+	-- octet 1 bits 4321	Mobile Country Code 1st digit
+	--         bits 8765	Mobile Country Code 2nd digit
+	-- octet 2 bits 4321	Mobile Country Code 3rd digit
+	--         bits 8765	Mobile Network Code 3rd digit
+	--			or filler (1111) for 2 digit MNCs
+	-- octet 3 bits 4321	Mobile Network Code 1st digit
+	--         bits 8765	Mobile Network Code 2nd digit
+	-- octets 4 and 5	Location Area Code according to 3GPP TS 24.008
+	-- octets 6 and 7	RNC Id value according to 3GPP TS 25.413
+
+RelocationNumberList ::= SEQUENCE SIZE (1..maxNumOfRelocationNumber) OF
+				RelocationNumber
+
+MulticallBearerInfo ::= INTEGER (1..maxNumOfRelocationNumber)
+
+RelocationNumber ::= SEQUENCE {
+	handoverNumber	ISDN-AddressString,
+	rab-Id		RAB-Id,
+	-- RAB Identity is needed to relate the calls with the radio access bearers. 
+	...}
+
+RAB-Id ::= INTEGER (1..maxNrOfRABs)
+
+maxNrOfRABs INTEGER ::= 255
+
+maxNumOfRelocationNumber  INTEGER ::= 7
+
+RadioResourceInformation ::= OCTET STRING (SIZE (3..13))
+	-- Octets are coded according the Channel Type information element in 3GPP TS 48.008
+
+IntegrityProtectionInformation ::= OCTET STRING (SIZE (18..maxNumOfIntegrityInfo))
+	-- Octets contain a complete IntegrityProtectionInformation data type 
+	-- as defined in 3GPP TS 25.413, encoded according to the encoding scheme 
+	-- mandated by 3GPP TS 25.413
+	-- Padding bits are included, if needed, in the least significant bits of the 
+	-- last octet of the octet string. 
+
+maxNumOfIntegrityInfo INTEGER ::= 100
+
+EncryptionInformation ::= OCTET STRING (SIZE (18..maxNumOfEncryptionInfo))
+	-- Octets contain a complete EncryptionInformation data type 
+	-- as defined in 3GPP TS 25.413, encoded according to the encoding scheme 
+	-- mandated by 3GPP TS 25.413
+	-- Padding bits are included, if needed, in the least significant bits of the 
+	-- last octet of the octet string. 
+
+maxNumOfEncryptionInfo INTEGER ::= 100
+
+-- authentication management types
+
+SendAuthenticationInfoArg ::= SEQUENCE {
+	imsi			[0] IMSI,
+	numberOfRequestedVectors	NumberOfRequestedVectors,
+	segmentationProhibited	NULL			OPTIONAL,
+	immediateResponsePreferred	[1] NULL			OPTIONAL,
+	re-synchronisationInfo	Re-synchronisationInfo	OPTIONAL,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...,
+	requestingNodeType	[3] RequestingNodeType	OPTIONAL,
+	requestingPLMN-Id	[4] PLMN-Id	OPTIONAL,
+	numberOfRequestedAdditional-Vectors	[5] NumberOfRequestedVectors	OPTIONAL,
+	additionalVectorsAreForEPS	[6] NULL		OPTIONAL }	
+
+
+NumberOfRequestedVectors ::= INTEGER (1..5)
+
+Re-synchronisationInfo ::= SEQUENCE {
+	rand			RAND,
+	auts			AUTS,
+	...}
+
+SendAuthenticationInfoRes ::= [3] SEQUENCE {
+	authenticationSetList	AuthenticationSetList 	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	eps-AuthenticationSetList	[2] EPS-AuthenticationSetList	OPTIONAL }
+
+EPS-AuthenticationSetList ::= SEQUENCE SIZE (1..5) OF
+				EPC-AV
+
+EPC-AV ::= SEQUENCE {
+	rand			RAND,
+	xres			XRES,
+	autn			AUTN,
+	kasme		KASME,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+KASME ::= OCTET STRING (SIZE (16))
+
+RequestingNodeType ::= ENUMERATED {
+	vlr  (0),
+	sgsn  (1),
+	...,
+	s-cscf  (2),
+	bsf  (3),
+	gan-aaa-server  (4),
+	wlan-aaa-server  (5),
+	mme		(16),
+	mme-sgsn	(17)
+	}
+	-- the values 2, 3, 4 and 5 shall not be used on the MAP-D or Gr interfaces
+	-- exception handling:
+	-- received values in the range (6-15) shall be treated as "vlr"
+	-- received values greater than 17 shall be treated as "sgsn"
+
+-- equipment management types
+
+CheckIMEI-Arg ::= SEQUENCE {
+	imei			IMEI,
+	requestedEquipmentInfo	RequestedEquipmentInfo,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+CheckIMEI-Res ::= SEQUENCE {
+	equipmentStatus	EquipmentStatus	OPTIONAL,
+	bmuef		UESBI-Iu		OPTIONAL,
+	extensionContainer	[0] ExtensionContainer	OPTIONAL,
+	...}
+
+RequestedEquipmentInfo::= BIT STRING {
+	equipmentStatus  (0),
+	bmuef  (1)} (SIZE (2..8))
+	-- exception handling: reception of unknown bit assignments in the
+	-- RequestedEquipmentInfo data type shall be discarded by the receiver 
+
+UESBI-Iu ::= SEQUENCE {
+	uesbi-IuA	[0] UESBI-IuA				OPTIONAL,
+	uesbi-IuB	[1] UESBI-IuB				OPTIONAL,
+	...}
+
+UESBI-IuA				::= BIT STRING (SIZE(1..128))
+-- See 3GPP TS 25.413
+
+UESBI-IuB				::= BIT STRING (SIZE(1..128))
+-- See 3GPP TS 25.413
+
+EquipmentStatus ::= ENUMERATED {
+	whiteListed  (0),
+	blackListed  (1),
+	greyListed  (2)}
+
+-- subscriber management types
+
+InsertSubscriberDataArg ::= SEQUENCE {
+	imsi			[0] IMSI		OPTIONAL,
+	COMPONENTS OF	SubscriberData,
+	extensionContainer	[14] ExtensionContainer	OPTIONAL,
+	... ,	
+	naea-PreferredCI	[15] NAEA-PreferredCI	OPTIONAL,
+	-- naea-PreferredCI is included at the discretion of the HLR operator.
+	gprsSubscriptionData	[16] GPRSSubscriptionData	OPTIONAL,
+	roamingRestrictedInSgsnDueToUnsupportedFeature [23] 	NULL	
+							OPTIONAL, 
+	networkAccessMode	[24] NetworkAccessMode	OPTIONAL,
+	lsaInformation	[25] LSAInformation	OPTIONAL,
+	lmu-Indicator	[21]	NULL		OPTIONAL,
+	lcsInformation	[22]	LCSInformation	OPTIONAL,
+	istAlertTimer	[26] IST-AlertTimerValue	OPTIONAL,
+	superChargerSupportedInHLR	[27] AgeIndicator	OPTIONAL,
+	mc-SS-Info	[28] MC-SS-Info	OPTIONAL,
+	cs-AllocationRetentionPriority	[29] CS-AllocationRetentionPriority		OPTIONAL,
+	sgsn-CAMEL-SubscriptionInfo	[17] SGSN-CAMEL-SubscriptionInfo	OPTIONAL,
+	chargingCharacteristics 	[18]	ChargingCharacteristics 	OPTIONAL,
+	accessRestrictionData	[19] AccessRestrictionData	OPTIONAL,
+	ics-Indicator	[20]	BOOLEAN	OPTIONAL,
+	eps-SubscriptionData	[31]	EPS-SubscriptionData	OPTIONAL,
+	csg-SubscriptionDataList	[32] CSG-SubscriptionDataList	OPTIONAL }
+	-- If the Network Access Mode parameter is sent, it shall be present only in 
+	-- the first sequence if seqmentation is used
+
+CSG-SubscriptionDataList ::= SEQUENCE SIZE (1..50) OF
+				CSG-SubscriptionData
+
+CSG-SubscriptionData ::= SEQUENCE {
+	csg-Id	 		CSG-Id,
+	expirationDate		Time		OPTIONAL,
+	extensionContainer		ExtensionContainer 	OPTIONAL,
+	...}
+
+CSG-Id ::= BIT STRING (SIZE (27))
+	-- coded according to 3GPP TS 23.003 [17].
+
+Time ::= OCTET STRING (SIZE (4))
+	-- Octets are coded according to IETF RFC 3588 [139]
+
+
+EPS-SubscriptionData ::= SEQUENCE {
+	apn-oi-Replacement	[0]	APN-OI-Replacement	OPTIONAL,
+	rfsp-id		[2]	RFSP-ID		OPTIONAL,
+	ambr			[3]	AMBR		OPTIONAL,
+	apn-ConfigurationProfile	[4]	APN-ConfigurationProfile	OPTIONAL,
+	stn-sr		[6]	ISDN-AddressString	OPTIONAL,
+	extensionContainer	[5]	ExtensionContainer	OPTIONAL,
+	... }
+
+APN-OI-Replacement ::=  OCTET STRING (SIZE (9..100))
+	-- Octets are coded as APN Operator Identifier according to TS 3GPP TS 23.003 [17] 
+
+RFSP-ID ::=  INTEGER (1..256)
+
+APN-ConfigurationProfile ::= SEQUENCE {
+	defaultContext	ContextId,
+	completeDataListIncluded	NULL			OPTIONAL,
+		-- If segmentation is used, completeDataListIncluded may only be present in the
+		-- first segment of APN-ConfigurationProfile.
+	epsDataList	[1]	EPS-DataList,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	... }
+
+EPS-DataList ::= SEQUENCE SIZE (1..maxNumOfAPN-Configurations) OF
+				APN-Configuration
+
+
+maxNumOfAPN-Configurations  INTEGER ::= 50
+
+
+APN-Configuration ::= SEQUENCE {
+	contextId		[0] ContextId,
+	servedPartyIP-Address	[1] PDP-Address	OPTIONAL,
+	apn			[2] APN,
+	eps-qos-Subscribed	[3] EPS-QoS-Subscribed,
+	pdn-gw-Identity	[4] PDN-GW-Identity	OPTIONAL,
+	pdn-gw-AllocationType	[5] PDN-GW-AllocationType	OPTIONAL,
+	vplmnAddressAllowed	[6] NULL		OPTIONAL,
+	chargingCharacteristics	[7] ChargingCharacteristics	OPTIONAL,
+	ambr			[8] AMBR		OPTIONAL,
+	specificAPNInfoList	[9] SpecificAPNInfoList	OPTIONAL,	extensionContainer	[10] ExtensionContainer	OPTIONAL,
+	... }
+
+EPS-QoS-Subscribed ::= SEQUENCE {
+	qos-Class-Identifier	[0] QoS-Class-Identifier,
+	allocation-Retention-Priority	[1] Allocation-Retention-Priority,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	... }
+
+AMBR ::= SEQUENCE {
+	max-RequestedBandwidth-UL	[0] Bandwidth,
+	max-RequestedBandwidth-DL	[1] Bandwidth,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	... }
+
+
+SpecificAPNInfoList ::= SEQUENCE SIZE (1..maxNumOfSpecificAPNInfos) OF
+				SpecificAPNInfo
+
+maxNumOfSpecificAPNInfos  INTEGER ::= 50
+
+SpecificAPNInfo ::= SEQUENCE {
+	apn			[0] APN,
+	pdn-gw-Identity	[1] PDN-GW-Identity,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	... }
+
+Bandwidth ::= INTEGER 
+	-- bits per second
+
+QoS-Class-Identifier ::= INTEGER (1..9)
+	-- values are defined in  3GPP TS 29.212
+
+
+
+Allocation-Retention-Priority ::= SEQUENCE {
+	priority-level	[0] INTEGER,
+	pre-emption-capability	[1] BOOLEAN	OPTIONAL,
+	pre-emption-vulnerability	[2] BOOLEAN	OPTIONAL,
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	... }
+
+PDN-GW-Identity ::= SEQUENCE {
+	pdn-gw-ipv4-Address	[0] PDP-Address	OPTIONAL,
+	pdn-gw-ipv6-Address	[1] PDP-Address	OPTIONAL,
+	pdn-gw-name	[2] FQDN		OPTIONAL,
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	... }
+
+FQDN ::=  OCTET STRING (SIZE (9..100))
+
+
+PDN-GW-AllocationType ::= ENUMERATED {
+	static	(0),
+	dynamic	(1)}
+
+
+AccessRestrictionData ::= BIT STRING {
+	utranNotAllowed (0),
+	geranNotAllowed (1),
+	ganNotAllowed   (2),
+	i-hspa-evolutionNotAllowed (3),
+	e-utranNotAllowed (4),
+	ho-toNon3GPP-AccessNotAllowed (5) } (SIZE (2..8))
+	-- exception handling: 
+	-- access restriction data related to an access type not supported by a node
+	-- shall be ignored
+	-- bits 6 to 7 shall be ignored if received and not understood
+	
+
+CS-AllocationRetentionPriority ::= OCTET STRING (SIZE (1))
+	-- This data type encodes each priority level defined in TS 23.107 as the binary value
+	-- of the priority level.
+
+IST-AlertTimerValue ::= INTEGER (15..255)
+
+LCSInformation ::= SEQUENCE {
+	gmlc-List	[0]	GMLC-List	OPTIONAL,
+	lcs-PrivacyExceptionList	[1]	LCS-PrivacyExceptionList	OPTIONAL,
+	molr-List		[2]	MOLR-List	OPTIONAL,
+	...,
+	add-lcs-PrivacyExceptionList	[3]	LCS-PrivacyExceptionList	OPTIONAL }
+	-- add-lcs-PrivacyExceptionList may be sent only if lcs-PrivacyExceptionList is
+	-- present and contains four instances of LCS-PrivacyClass. If the mentioned condition
+	-- is not satisfied the receiving node shall discard add-lcs-PrivacyExceptionList.
+	-- If an LCS-PrivacyClass is received both in lcs-PrivacyExceptionList and in
+	-- add-lcs-PrivacyExceptionList with the same SS-Code, then the error unexpected 
+	-- data value shall be returned.
+
+GMLC-List ::= SEQUENCE SIZE (1..maxNumOfGMLC) OF
+				ISDN-AddressString
+	-- if segmentation is used, the complete GMLC-List shall be sent in one segment
+
+maxNumOfGMLC  INTEGER ::= 5
+
+NetworkAccessMode ::= ENUMERATED {
+	packetAndCircuit	(0),
+	onlyCircuit		(1),
+	onlyPacket		(2),
+	...}
+	-- if unknown values are received in NetworkAccessMode
+	-- they shall be discarded.
+
+GPRSDataList ::= SEQUENCE SIZE (1..maxNumOfPDP-Contexts) OF
+				PDP-Context
+
+maxNumOfPDP-Contexts  INTEGER ::= 50
+
+PDP-Context ::= SEQUENCE {
+	pdp-ContextId	ContextId,
+	pdp-Type		[16] PDP-Type,
+	pdp-Address	[17] PDP-Address	OPTIONAL,
+	qos-Subscribed	[18] QoS-Subscribed,
+	vplmnAddressAllowed	[19] NULL	OPTIONAL,
+	apn			[20] APN,
+	extensionContainer	[21] ExtensionContainer	OPTIONAL,
+	... ,
+	ext-QoS-Subscribed	[0] Ext-QoS-Subscribed	OPTIONAL, 
+	pdp-ChargingCharacteristics	[1] ChargingCharacteristics	OPTIONAL,
+	ext2-QoS-Subscribed	[2] Ext2-QoS-Subscribed	OPTIONAL,
+	-- ext2-QoS-Subscribed may be present only if ext-QoS-Subscribed is present.
+	ext3-QoS-Subscribed	[3] Ext3-QoS-Subscribed	OPTIONAL
+	-- ext3-QoS-Subscribed may be present only if ext2-QoS-Subscribed is present.
+	 }
+
+ContextId ::= INTEGER (1..maxNumOfPDP-Contexts)
+
+GPRSSubscriptionData ::= SEQUENCE {
+	completeDataListIncluded	NULL			OPTIONAL,
+		-- If segmentation is used, completeDataListIncluded may only be present in the
+		-- first segment of GPRSSubscriptionData.
+	gprsDataList	[1]	GPRSDataList,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	... }
+
+SGSN-CAMEL-SubscriptionInfo ::= SEQUENCE {
+	gprs-CSI		[0]	GPRS-CSI	OPTIONAL,
+	mo-sms-CSI	[1]	SMS-CSI	OPTIONAL,
+	extensionContainer	[2]	ExtensionContainer	OPTIONAL,
+	...,
+	mt-sms-CSI	[3]	SMS-CSI	OPTIONAL,
+	mt-smsCAMELTDP-CriteriaList	[4]	MT-smsCAMELTDP-CriteriaList	OPTIONAL,
+	mg-csi		[5]	MG-CSI	OPTIONAL
+	}
+
+GPRS-CSI ::= SEQUENCE {
+	gprs-CamelTDPDataList	[0] GPRS-CamelTDPDataList	OPTIONAL,
+	camelCapabilityHandling	[1] CamelCapabilityHandling	OPTIONAL,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	notificationToCSE	[3]	NULL		OPTIONAL,
+	csi-Active	[4]	NULL		OPTIONAL,
+	...}
+--	notificationToCSE and csi-Active shall not be present when GPRS-CSI is sent to SGSN.
+--	They may only be included in ATSI/ATM ack/NSDC message. 
+--	GPRS-CamelTDPData and  camelCapabilityHandling shall be present in 
+--	the GPRS-CSI sequence.
+--	If GPRS-CSI is segmented, gprs-CamelTDPDataList and camelCapabilityHandling shall be 
+--	present in the first segment
+
+GPRS-CamelTDPDataList ::= SEQUENCE SIZE (1..maxNumOfCamelTDPData) OF
+	GPRS-CamelTDPData
+--	GPRS-CamelTDPDataList shall not contain more than one instance of
+--	GPRS-CamelTDPData containing the same value for gprs-TriggerDetectionPoint.
+
+GPRS-CamelTDPData ::= SEQUENCE {
+	gprs-TriggerDetectionPoint	[0] GPRS-TriggerDetectionPoint,
+	serviceKey	[1] ServiceKey,
+	gsmSCF-Address	[2] ISDN-AddressString,
+	defaultSessionHandling	[3] DefaultGPRS-Handling,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	...
+	}
+
+DefaultGPRS-Handling ::= ENUMERATED {
+	continueTransaction (0) ,
+	releaseTransaction (1) ,
+	...}
+-- exception handling:
+-- reception of values in range 2-31 shall be treated as "continueTransaction"
+-- reception of values greater than 31 shall be treated as "releaseTransaction"
+
+GPRS-TriggerDetectionPoint ::= ENUMERATED {
+	attach 			(1),
+	attachChangeOfPosition 		(2),
+	pdp-ContextEstablishment 		(11),
+	pdp-ContextEstablishmentAcknowledgement	(12),
+	pdp-ContextChangeOfPosition 		(14),
+	... }
+-- exception handling:
+-- For GPRS-CamelTDPData sequences containing this parameter with any
+-- other value than the ones listed the receiver shall ignore the whole 
+-- GPRS-CamelTDPDatasequence.
+
+APN ::=  OCTET STRING (SIZE (2..63))
+	-- Octets are coded according to TS 3GPP TS 23.003 [17] 
+
+PDP-Type ::= OCTET STRING (SIZE (2))
+	-- Octets are coded according to TS 3GPP TS 29.060 [105]
+
+PDP-Address ::= OCTET STRING (SIZE (1..16))
+	-- Octets are coded according to TS 3GPP TS 29.060 [105]
+
+	-- The possible size values are:
+	-- 1-7 octets  X.25 address type
+	--  4  octets  IPv4 address type
+	-- 16  octets  Ipv6 address type
+
+QoS-Subscribed ::= OCTET STRING (SIZE (3))
+	-- Octets are coded according to TS 3GPP TS 24.008 [35] Quality of Service Octets 
+	-- 3-5.
+
+Ext-QoS-Subscribed ::= OCTET STRING (SIZE (1..9))
+	-- OCTET 1: 
+	--  Allocation/Retention Priority (This octet encodes each priority level defined in
+	--     23.107 as the binary value of the priority level, declaration in 29.060)
+	-- Octets 2-9 are coded according to 3GPP TS 24.008 [35] Quality of Service Octets 
+	-- 6-13.
+
+Ext2-QoS-Subscribed ::= OCTET STRING (SIZE (1..3))
+	-- Octets 1-3 are coded according to 3GPP TS 24.008 [35] Quality of Service Octets 14-16.
+	-- If Quality of Service information is structured with 14 octet length, then
+	-- Octet 1 is coded according to 3GPP TS 24.008 [35] Quality of Service Octet 14.
+
+Ext3-QoS-Subscribed ::= OCTET STRING (SIZE (1..2))
+	-- Octets 1-2 are coded according to 3GPP TS 24.008 [35] Quality of Service Octets 17-18.
+
+ChargingCharacteristics ::= OCTET STRING (SIZE (2))
+	-- Octets are coded according to 3GPP TS 32.215.
+
+LSAOnlyAccessIndicator ::= ENUMERATED {
+	accessOutsideLSAsAllowed  (0),
+	accessOutsideLSAsRestricted (1)}
+
+LSADataList ::= SEQUENCE SIZE (1..maxNumOfLSAs) OF
+				LSAData
+
+maxNumOfLSAs  INTEGER ::= 20
+
+LSAData ::= SEQUENCE {
+	lsaIdentity	[0] LSAIdentity,
+	lsaAttributes	[1] LSAAttributes,
+	lsaActiveModeIndicator	[2] NULL		OPTIONAL,
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	...}
+
+LSAInformation ::= SEQUENCE {
+	completeDataListIncluded	NULL			OPTIONAL,
+
+		-- If segmentation is used, completeDataListIncluded may only be present in the
+		-- first segment.
+	lsaOnlyAccessIndicator	[1]	LSAOnlyAccessIndicator	OPTIONAL,
+	lsaDataList	[2]	LSADataList	OPTIONAL,
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	...}
+
+LSAIdentity ::= OCTET STRING (SIZE (3))
+	-- Octets are coded according to TS 3GPP TS 23.003 [17]
+
+LSAAttributes ::= OCTET STRING (SIZE (1))
+	-- Octets are coded according to TS 3GPP TS 48.008 [49]
+
+SubscriberData ::= SEQUENCE {
+	msisdn		[1] ISDN-AddressString	OPTIONAL,
+	category		[2] Category	OPTIONAL,
+	subscriberStatus	[3] SubscriberStatus	OPTIONAL,
+	bearerServiceList	[4] BearerServiceList	OPTIONAL,
+	-- The exception handling for reception of unsupported / not allocated
+	-- bearerServiceCodes is defined in section 8.8.1
+	teleserviceList	[6] TeleserviceList	OPTIONAL,
+	-- The exception handling for reception of unsupported / not allocated
+	-- teleserviceCodes is defined in section 8.8.1
+	provisionedSS	[7] Ext-SS-InfoList	OPTIONAL,
+	odb-Data		[8] ODB-Data	OPTIONAL,
+	roamingRestrictionDueToUnsupportedFeature  [9] NULL	OPTIONAL,
+	regionalSubscriptionData	[10] ZoneCodeList	OPTIONAL,
+	vbsSubscriptionData	[11] VBSDataList	OPTIONAL,
+	vgcsSubscriptionData	[12] VGCSDataList	OPTIONAL,
+	vlrCamelSubscriptionInfo	[13] VlrCamelSubscriptionInfo	OPTIONAL
+	}
+
+Category ::= OCTET STRING (SIZE (1))
+	-- The internal structure is defined in ITU-T Rec Q.763.
+
+SubscriberStatus ::= ENUMERATED {
+	serviceGranted  (0),
+	operatorDeterminedBarring  (1)}
+
+BearerServiceList ::= SEQUENCE SIZE (1..maxNumOfBearerServices) OF
+				Ext-BearerServiceCode
+
+maxNumOfBearerServices  INTEGER ::= 50
+
+TeleserviceList ::= SEQUENCE SIZE (1..maxNumOfTeleservices) OF
+				Ext-TeleserviceCode
+
+maxNumOfTeleservices  INTEGER ::= 20
+
+ODB-Data ::= SEQUENCE {
+	odb-GeneralData	ODB-GeneralData,
+	odb-HPLMN-Data	ODB-HPLMN-Data	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+ODB-GeneralData ::= BIT STRING {
+	allOG-CallsBarred  (0),
+	internationalOGCallsBarred  (1),
+	internationalOGCallsNotToHPLMN-CountryBarred  (2),
+	interzonalOGCallsBarred (6),
+	interzonalOGCallsNotToHPLMN-CountryBarred (7),
+	interzonalOGCallsAndInternationalOGCallsNotToHPLMN-CountryBarred (8),
+	premiumRateInformationOGCallsBarred  (3),
+	premiumRateEntertainementOGCallsBarred  (4),
+	ss-AccessBarred  (5),
+	allECT-Barred (9),
+	chargeableECT-Barred (10),
+	internationalECT-Barred (11),
+	interzonalECT-Barred (12),
+	doublyChargeableECT-Barred (13),
+	multipleECT-Barred (14),
+	allPacketOrientedServicesBarred (15),
+	roamerAccessToHPLMN-AP-Barred  (16),
+	roamerAccessToVPLMN-AP-Barred  (17),
+	roamingOutsidePLMNOG-CallsBarred  (18),
+	allIC-CallsBarred  (19),
+	roamingOutsidePLMNIC-CallsBarred  (20),
+	roamingOutsidePLMNICountryIC-CallsBarred  (21),
+	roamingOutsidePLMN-Barred  (22),
+	roamingOutsidePLMN-CountryBarred  (23),
+	registrationAllCF-Barred  (24),
+	registrationCFNotToHPLMN-Barred  (25),
+	registrationInterzonalCF-Barred  (26),
+	registrationInterzonalCFNotToHPLMN-Barred  (27),
+	registrationInternationalCF-Barred  (28)} (SIZE (15..32))
+	-- exception handling: reception of unknown bit assignments in the
+	-- ODB-GeneralData type shall be treated like unsupported ODB-GeneralData
+	-- When the ODB-GeneralData type is removed from the HLR for a given subscriber, 
+	-- in NoteSubscriberDataModified operation sent toward the gsmSCF 
+	-- all bits shall be set to "O".
+
+ODB-HPLMN-Data ::= BIT STRING {
+	plmn-SpecificBarringType1  (0),
+	plmn-SpecificBarringType2  (1),
+	plmn-SpecificBarringType3  (2),
+	plmn-SpecificBarringType4  (3)} (SIZE (4..32))
+	-- exception handling: reception of unknown bit assignments in the
+	-- ODB-HPLMN-Data type shall be treated like unsupported ODB-HPLMN-Data 
+	-- When the ODB-HPLMN-Data type is removed from the HLR for a given subscriber, 
+	-- in NoteSubscriberDataModified operation sent toward the gsmSCF
+	-- all bits shall be set to "O".
+
+Ext-SS-InfoList ::= SEQUENCE SIZE (1..maxNumOfSS) OF
+				Ext-SS-Info
+
+Ext-SS-Info ::= CHOICE {
+	forwardingInfo	[0] Ext-ForwInfo,
+	callBarringInfo	[1] Ext-CallBarInfo,
+	cug-Info		[2] CUG-Info,
+	ss-Data		[3] Ext-SS-Data,
+	emlpp-Info	[4] EMLPP-Info}
+
+Ext-ForwInfo ::= SEQUENCE {
+	ss-Code		SS-Code,
+	forwardingFeatureList	Ext-ForwFeatureList,
+	extensionContainer	[0] ExtensionContainer	OPTIONAL,
+	...}
+
+Ext-ForwFeatureList ::= SEQUENCE SIZE (1..maxNumOfExt-BasicServiceGroups) OF
+				Ext-ForwFeature
+
+Ext-ForwFeature ::= SEQUENCE {
+	basicService	Ext-BasicServiceCode	OPTIONAL,
+	ss-Status		[4] Ext-SS-Status,
+	forwardedToNumber	[5] ISDN-AddressString	OPTIONAL,
+	-- When this data type is sent from an HLR which supports CAMEL Phase 2
+	-- to a VLR that supports CAMEL Phase 2 the VLR shall not check the
+	-- format of the number
+	forwardedToSubaddress	[8] ISDN-SubaddressString	OPTIONAL,
+	forwardingOptions	[6] Ext-ForwOptions	OPTIONAL,
+	noReplyConditionTime	[7] Ext-NoRepCondTime	OPTIONAL,
+	extensionContainer	[9] ExtensionContainer	OPTIONAL,
+	...,
+	longForwardedToNumber	[10] FTN-AddressString	OPTIONAL }
+
+Ext-ForwOptions ::= OCTET STRING (SIZE (1..5))
+
+	-- OCTET 1:
+
+	--  bit 8: notification to forwarding party
+	--	0  no notification
+	--	1  notification
+
+	--  bit 7: redirecting presentation
+	--	0 no presentation  
+	--	1  presentation
+
+	--  bit 6: notification to calling party
+	--	0  no notification
+	--	1  notification
+
+	--  bit 5: 0 (unused)
+
+	--  bits 43: forwarding reason
+	--	00  ms not reachable
+	--	01  ms busy
+	--	10  no reply
+	--	11  unconditional
+
+	-- bits 21: 00 (unused)
+
+	-- OCTETS 2-5: reserved for future use. They shall be discarded if
+	-- received and not understood.
+
+Ext-NoRepCondTime ::= INTEGER (1..100)
+	-- Only values 5-30 are used.
+	-- Values in the ranges 1-4 and 31-100 are reserved for future use
+	-- If received:
+	--		values 1-4 shall be mapped on to value 5
+	--		values 31-100 shall be mapped on to value 30
+
+Ext-CallBarInfo ::= SEQUENCE {
+	ss-Code		SS-Code,
+	callBarringFeatureList	Ext-CallBarFeatureList,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+Ext-CallBarFeatureList ::= SEQUENCE SIZE (1..maxNumOfExt-BasicServiceGroups) OF
+				Ext-CallBarringFeature
+
+Ext-CallBarringFeature ::= SEQUENCE {
+	basicService	Ext-BasicServiceCode	OPTIONAL,
+	ss-Status		[4] Ext-SS-Status,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+CUG-Info ::= SEQUENCE {
+	cug-SubscriptionList	CUG-SubscriptionList,
+	cug-FeatureList	CUG-FeatureList	OPTIONAL,
+	extensionContainer	[0] ExtensionContainer	OPTIONAL,
+	...}
+
+CUG-SubscriptionList ::= SEQUENCE SIZE (0..maxNumOfCUG) OF
+				CUG-Subscription
+
+CUG-Subscription ::= SEQUENCE {
+	cug-Index	CUG-Index,
+	cug-Interlock	CUG-Interlock,
+	intraCUG-Options	IntraCUG-Options,
+	basicServiceGroupList	Ext-BasicServiceGroupList	OPTIONAL,
+	extensionContainer	[0] ExtensionContainer	OPTIONAL,
+	...}
+
+CUG-Index ::= INTEGER (0..32767)
+	-- The internal structure is defined in ETS 300 138.
+
+CUG-Interlock ::= OCTET STRING (SIZE (4))
+
+IntraCUG-Options ::= ENUMERATED {
+	noCUG-Restrictions  (0),
+	cugIC-CallBarred  (1),
+	cugOG-CallBarred  (2)}
+
+maxNumOfCUG  INTEGER ::= 10
+
+CUG-FeatureList ::= SEQUENCE SIZE (1..maxNumOfExt-BasicServiceGroups) OF
+				CUG-Feature
+
+Ext-BasicServiceGroupList ::= SEQUENCE SIZE (1..maxNumOfExt-BasicServiceGroups) OF
+				Ext-BasicServiceCode
+
+maxNumOfExt-BasicServiceGroups  INTEGER ::= 32
+
+CUG-Feature ::= SEQUENCE {
+	basicService	Ext-BasicServiceCode	OPTIONAL,
+	preferentialCUG-Indicator	CUG-Index	OPTIONAL,
+	interCUG-Restrictions	InterCUG-Restrictions,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+InterCUG-Restrictions ::= OCTET STRING (SIZE (1))
+
+	-- bits 876543: 000000 (unused)
+	-- Exception handling:
+	-- bits 876543 shall be ignored if received and not understood
+
+	-- bits 21
+	--	00  CUG only facilities
+	--	01  CUG with outgoing access
+	--	10  CUG with incoming access
+	--	11  CUG with both outgoing and incoming access
+
+Ext-SS-Data ::= SEQUENCE {
+	ss-Code		SS-Code,
+	ss-Status	[4] Ext-SS-Status,
+	ss-SubscriptionOption	SS-SubscriptionOption	OPTIONAL,
+	basicServiceGroupList	Ext-BasicServiceGroupList	OPTIONAL,
+	extensionContainer	[5] ExtensionContainer	OPTIONAL,
+	...}
+
+LCS-PrivacyExceptionList ::= SEQUENCE SIZE (1..maxNumOfPrivacyClass) OF
+				LCS-PrivacyClass
+
+maxNumOfPrivacyClass  INTEGER ::= 4
+
+LCS-PrivacyClass ::= SEQUENCE {
+	ss-Code		SS-Code,
+	ss-Status		Ext-SS-Status,
+	notificationToMSUser	[0] NotificationToMSUser	OPTIONAL,
+	-- notificationToMSUser may be sent only for SS-codes callSessionRelated
+	-- and callSessionUnrelated. If not received for SS-codes callSessionRelated
+	-- and callSessionUnrelated,
+	-- the default values according to 3GPP TS 23.271 shall be assumed.
+	externalClientList	[1] ExternalClientList	OPTIONAL,
+	-- externalClientList may be sent only for SS-code callSessionUnrelated to a
+	-- visited node that does not support LCS Release 4 or later versions.
+	-- externalClientList may be sent only for SS-codes callSessionUnrelated and
+	-- callSessionRelated to a visited node that supports LCS Release 4 or later versions.
+	plmnClientList	[2] PLMNClientList	OPTIONAL,
+	-- plmnClientList may be sent only for SS-code plmnoperator.
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	...,
+	ext-externalClientList	[4] Ext-ExternalClientList	OPTIONAL,
+	-- Ext-externalClientList may be sent only if the visited node supports LCS Release 4 or
+	-- later versions, the user did specify more than 5 clients, and White Book SCCP is used.
+	serviceTypeList	[5]	ServiceTypeList	OPTIONAL
+	-- serviceTypeList may be sent only for SS-code serviceType and if the visited node
+	-- supports LCS Release 5 or later versions.
+	-- 
+	-- if segmentation is used, the complete LCS-PrivacyClass shall be sent in one segment
+}
+
+ExternalClientList ::= SEQUENCE SIZE (0..maxNumOfExternalClient) OF
+				ExternalClient
+
+maxNumOfExternalClient  INTEGER ::= 5
+
+PLMNClientList ::= SEQUENCE SIZE (1..maxNumOfPLMNClient) OF
+				LCSClientInternalID
+
+maxNumOfPLMNClient  INTEGER ::= 5
+
+Ext-ExternalClientList ::= SEQUENCE SIZE (1..maxNumOfExt-ExternalClient) OF
+				ExternalClient
+
+maxNumOfExt-ExternalClient  INTEGER ::= 35
+
+ExternalClient ::= SEQUENCE {
+	clientIdentity	LCSClientExternalID,
+	gmlc-Restriction	[0] GMLC-Restriction	OPTIONAL,
+	notificationToMSUser	[1] NotificationToMSUser	OPTIONAL,
+	-- If notificationToMSUser is not received, the default value according to 
+	-- 3GPP TS 23.271 shall be assumed.
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	... }
+
+GMLC-Restriction ::= ENUMERATED {
+	gmlc-List		(0),
+	home-Country	(1) ,
+	... }
+-- exception handling:
+-- At reception of any other value than the ones listed the receiver shall ignore
+-- GMLC-Restriction.
+
+NotificationToMSUser ::= ENUMERATED {
+	notifyLocationAllowed	(0),
+	notifyAndVerify-LocationAllowedIfNoResponse	(1),
+	notifyAndVerify-LocationNotAllowedIfNoResponse	(2),
+	...,
+	locationNotAllowed (3) }
+-- exception handling:
+-- At reception of any other value than the ones listed the receiver shall ignore
+-- NotificationToMSUser.
+
+ServiceTypeList ::= SEQUENCE SIZE (1..maxNumOfServiceType) OF
+				ServiceType
+
+maxNumOfServiceType  INTEGER ::= 32
+
+ServiceType ::= SEQUENCE {
+	serviceTypeIdentity	LCSServiceTypeID,
+	gmlc-Restriction	[0] GMLC-Restriction	OPTIONAL,
+	notificationToMSUser	[1] NotificationToMSUser	OPTIONAL,
+	-- If notificationToMSUser is not received, the default value according to 
+	-- 3GPP TS 23.271 shall be assumed.
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	... }
+
+MOLR-List ::= SEQUENCE SIZE (1..maxNumOfMOLR-Class) OF
+				MOLR-Class
+
+maxNumOfMOLR-Class  INTEGER ::= 3
+
+MOLR-Class ::= SEQUENCE {
+	ss-Code		SS-Code,
+	ss-Status		Ext-SS-Status,
+	extensionContainer	[0] ExtensionContainer	OPTIONAL,
+	...}
+
+ZoneCodeList ::= SEQUENCE SIZE (1..maxNumOfZoneCodes)
+				OF ZoneCode
+
+ZoneCode ::= OCTET STRING (SIZE (2))
+	-- internal structure is defined in TS 3GPP TS 23.003 [17]
+
+maxNumOfZoneCodes  INTEGER ::= 10
+
+InsertSubscriberDataRes ::= SEQUENCE {
+	teleserviceList	[1] TeleserviceList	OPTIONAL,
+	bearerServiceList	[2] BearerServiceList	OPTIONAL,
+	ss-List		[3] SS-List	OPTIONAL,
+	odb-GeneralData	[4] ODB-GeneralData	OPTIONAL,
+	regionalSubscriptionResponse	[5] RegionalSubscriptionResponse	OPTIONAL,
+	supportedCamelPhases	[6] SupportedCamelPhases	OPTIONAL,
+	extensionContainer	[7] ExtensionContainer	OPTIONAL,
+	... ,
+	offeredCamel4CSIs	[8] OfferedCamel4CSIs	OPTIONAL,
+	supportedFeatures	[9] SupportedFeatures	OPTIONAL }
+
+RegionalSubscriptionResponse ::= ENUMERATED {
+	networkNode-AreaRestricted	(0),
+	tooManyZoneCodes	(1),
+	zoneCodesConflict	(2),
+	regionalSubscNotSupported	(3)}
+
+DeleteSubscriberDataArg ::= SEQUENCE {
+	imsi			[0] IMSI,
+	basicServiceList	[1] BasicServiceList	OPTIONAL,
+	-- The exception handling for reception of unsupported/not allocated
+	-- basicServiceCodes is defined in section 6.8.2
+	ss-List		[2] SS-List	OPTIONAL,
+	roamingRestrictionDueToUnsupportedFeature [4] NULL	OPTIONAL,
+	regionalSubscriptionIdentifier	[5] ZoneCode	OPTIONAL,
+	vbsGroupIndication	[7] NULL		OPTIONAL,
+	vgcsGroupIndication	[8] NULL	 	OPTIONAL,
+	camelSubscriptionInfoWithdraw	[9] NULL	 	OPTIONAL,
+	extensionContainer	[6] ExtensionContainer 	OPTIONAL,
+	...,
+	gprsSubscriptionDataWithdraw	[10] GPRSSubscriptionDataWithdraw	OPTIONAL,
+	roamingRestrictedInSgsnDueToUnsuppportedFeature [11] NULL	OPTIONAL,
+	lsaInformationWithdraw	[12] LSAInformationWithdraw	OPTIONAL,
+	gmlc-ListWithdraw 	[13]	NULL		OPTIONAL,
+	istInformationWithdraw	[14] NULL		OPTIONAL,
+	specificCSI-Withdraw	[15] SpecificCSI-Withdraw	OPTIONAL,
+	chargingCharacteristicsWithdraw	[16] NULL		OPTIONAL,
+	stn-srWithdraw	[17] NULL		OPTIONAL,
+	epsSubscriptionDataWithdraw	[18] EPS-SubscriptionDataWithdraw	OPTIONAL,
+	apn-oi-replacementWithdraw	[19] NULL		OPTIONAL,
+	csg-SubscriptionDeleted	[20]	NULL		OPTIONAL }
+
+SpecificCSI-Withdraw ::= BIT STRING {
+	o-csi (0),
+	ss-csi (1),
+	tif-csi (2),
+	d-csi (3),
+	vt-csi (4),
+	mo-sms-csi (5),
+	m-csi (6),
+	gprs-csi (7),
+	t-csi (8),
+	mt-sms-csi (9),
+	mg-csi (10),
+	o-IM-CSI (11), 
+	d-IM-CSI (12),
+	vt-IM-CSI (13) } (SIZE(8..32)) 
+-- exception handling:
+-- bits 11 to 31 shall be ignored if received by a non-IP Multimedia Core Network entity.
+-- bits 0-10 and 14-31 shall be ignored if received by an IP Multimedia Core Network entity.
+-- bits 11-13 are only applicable in an IP Multimedia Core Network.
+-- Bit 8 and bits 11-13 are only applicable for the NoteSubscriberDataModified operation.
+
+GPRSSubscriptionDataWithdraw ::= CHOICE {
+	allGPRSData	NULL,
+	contextIdList	ContextIdList}
+
+EPS-SubscriptionDataWithdraw ::= CHOICE {
+	allEPS-Data	NULL,
+	contextIdList	ContextIdList}
+
+ContextIdList ::= SEQUENCE SIZE (1..maxNumOfPDP-Contexts) OF
+				ContextId
+
+LSAInformationWithdraw ::= CHOICE {
+	allLSAData	NULL,
+	lsaIdentityList	LSAIdentityList }
+
+LSAIdentityList ::= SEQUENCE SIZE (1..maxNumOfLSAs) OF
+				LSAIdentity
+
+BasicServiceList ::= SEQUENCE SIZE (1..maxNumOfBasicServices) OF
+				Ext-BasicServiceCode
+
+maxNumOfBasicServices  INTEGER ::= 70
+
+DeleteSubscriberDataRes ::= SEQUENCE {
+	regionalSubscriptionResponse	[0] RegionalSubscriptionResponse	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+VlrCamelSubscriptionInfo ::= SEQUENCE {
+	o-CSI		[0] O-CSI		OPTIONAL,
+	extensionContainer	[1] ExtensionContainer	OPTIONAL,
+	...,
+	ss-CSI		[2] SS-CSI	OPTIONAL,
+	o-BcsmCamelTDP-CriteriaList	[4] O-BcsmCamelTDPCriteriaList	OPTIONAL,
+	tif-CSI		[3] NULL	OPTIONAL,
+	m-CSI		[5] M-CSI	OPTIONAL,
+	mo-sms-CSI	[6] SMS-CSI	OPTIONAL,
+	vt-CSI		[7] T-CSI	OPTIONAL,
+	t-BCSM-CAMEL-TDP-CriteriaList	[8] T-BCSM-CAMEL-TDP-CriteriaList	OPTIONAL,
+	d-CSI		[9] D-CSI	OPTIONAL,
+	mt-sms-CSI	[10] SMS-CSI	OPTIONAL,
+	mt-smsCAMELTDP-CriteriaList	[11]	MT-smsCAMELTDP-CriteriaList	OPTIONAL
+	}
+
+MT-smsCAMELTDP-CriteriaList ::= SEQUENCE SIZE (1.. maxNumOfCamelTDPData) OF
+	MT-smsCAMELTDP-Criteria
+
+MT-smsCAMELTDP-Criteria ::= SEQUENCE {
+	sms-TriggerDetectionPoint	SMS-TriggerDetectionPoint,
+	tpdu-TypeCriterion	[0]	TPDU-TypeCriterion		OPTIONAL,
+	... }
+
+TPDU-TypeCriterion ::= SEQUENCE SIZE (1..maxNumOfTPDUTypes) OF
+	MT-SMS-TPDU-Type
+
+
+maxNumOfTPDUTypes INTEGER ::= 5
+
+MT-SMS-TPDU-Type ::= ENUMERATED {
+	sms-DELIVER 	(0),
+	sms-SUBMIT-REPORT 	(1),
+	sms-STATUS-REPORT 	(2),
+	... }
+
+--	exception handling:
+--	For TPDU-TypeCriterion sequences containing this parameter with any
+--	other value than the ones listed above the receiver shall ignore 
+--	the whole TPDU-TypeCriterion sequence.
+--	In CAMEL phase 4, sms-SUBMIT-REPORT shall not be used and a received TPDU-TypeCriterion
+--	sequence containing sms-SUBMIT-REPORT shall be wholly ignored.
+
+D-CSI ::= SEQUENCE {
+	dp-AnalysedInfoCriteriaList	[0] DP-AnalysedInfoCriteriaList	OPTIONAL,
+	camelCapabilityHandling	[1] CamelCapabilityHandling	OPTIONAL,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	notificationToCSE	[3]	NULL		OPTIONAL,
+	csi-Active	[4]	NULL		OPTIONAL,
+	...} 
+--	notificationToCSE and csi-Active shall not be present when D-CSI is sent to VLR/GMSC.
+--	They may only be included in ATSI/ATM ack/NSDC message.
+--	DP-AnalysedInfoCriteria and  camelCapabilityHandling shall be present in 
+--	the D-CSI sequence.
+--	If D-CSI is segmented, then the first segment shall contain dp-AnalysedInfoCriteriaList
+--	and camelCapabilityHandling. Subsequent segments shall not contain
+--	camelCapabilityHandling, but may contain dp-AnalysedInfoCriteriaList.
+
+DP-AnalysedInfoCriteriaList  ::= SEQUENCE SIZE (1..maxNumOfDP-AnalysedInfoCriteria) OF
+				DP-AnalysedInfoCriterium
+
+maxNumOfDP-AnalysedInfoCriteria INTEGER ::= 10
+
+DP-AnalysedInfoCriterium ::= SEQUENCE {
+	dialledNumber	ISDN-AddressString,
+	serviceKey	ServiceKey,
+	gsmSCF-Address	ISDN-AddressString,
+	defaultCallHandling	DefaultCallHandling,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+SS-CSI ::= SEQUENCE {
+	ss-CamelData	SS-CamelData,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	notificationToCSE	[0]	NULL		OPTIONAL,
+	csi-Active	[1]	NULL		OPTIONAL
+--	notificationToCSE and csi-Active shall not be present when SS-CSI is sent to VLR.
+--	They may only be included in ATSI/ATM ack/NSDC message.
+}
+
+SS-CamelData  ::= SEQUENCE {
+	ss-EventList	SS-EventList,
+	gsmSCF-Address	ISDN-AddressString,
+	extensionContainer	[0] ExtensionContainer	OPTIONAL, 
+	...}
+
+SS-EventList  ::= SEQUENCE SIZE (1..maxNumOfCamelSSEvents) OF SS-Code
+	-- Actions for the following SS-Code values are defined in CAMEL Phase 3:
+	-- ect		SS-Code ::= '00110001'B
+	-- multiPTY	SS-Code ::= '01010001'B
+	-- cd		SS-Code ::= '00100100'B
+	-- ccbs		SS-Code ::= '01000100'B
+	-- all other SS codes shall be ignored
+	-- When SS-CSI is sent to the VLR, it shall not contain a marking for ccbs.
+	-- If the VLR receives SS-CSI containing a marking for ccbs, the VLR shall discard the
+	-- ccbs marking in SS-CSI.
+
+maxNumOfCamelSSEvents INTEGER ::= 10
+
+O-CSI ::= SEQUENCE {
+	o-BcsmCamelTDPDataList	O-BcsmCamelTDPDataList,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	camelCapabilityHandling	[0] CamelCapabilityHandling	OPTIONAL,
+	notificationToCSE	[1]	NULL		OPTIONAL,
+	csiActive		[2]	NULL		OPTIONAL}
+--	notificationtoCSE and csiActive shall not be present when O-CSI is sent to VLR/GMSC.
+--	They may only be included in ATSI/ATM ack/NSDC message.
+--	O-CSI shall not be segmented.
+
+O-BcsmCamelTDPDataList ::= SEQUENCE SIZE (1..maxNumOfCamelTDPData) OF
+	O-BcsmCamelTDPData
+	-- O-BcsmCamelTDPDataList shall not contain more than one instance of
+	-- O-BcsmCamelTDPData containing the same value for o-BcsmTriggerDetectionPoint.
+	-- For CAMEL Phase 2, this means that only one instance of O-BcsmCamelTDPData is allowed
+	-- with o-BcsmTriggerDetectionPoint being equal to DP2.
+
+maxNumOfCamelTDPData  INTEGER ::= 10
+
+O-BcsmCamelTDPData ::= SEQUENCE {
+	o-BcsmTriggerDetectionPoint	O-BcsmTriggerDetectionPoint,
+	serviceKey	ServiceKey,
+	gsmSCF-Address	[0] ISDN-AddressString,
+	defaultCallHandling	[1] DefaultCallHandling,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...
+	}
+
+ServiceKey ::= INTEGER (0..2147483647)
+
+O-BcsmTriggerDetectionPoint ::= ENUMERATED {
+	collectedInfo (2),
+	...,
+	routeSelectFailure (4) }
+	-- exception handling:
+	-- For O-BcsmCamelTDPData sequences containing this parameter with any
+	-- other value than the ones listed the receiver shall ignore the whole 
+	-- O-BcsmCamelTDPDatasequence. 
+	-- For O-BcsmCamelTDP-Criteria sequences containing this parameter with any
+	-- other value than the ones listed the receiver shall ignore the whole
+	-- O-BcsmCamelTDP-Criteria sequence.
+
+O-BcsmCamelTDPCriteriaList ::= SEQUENCE SIZE (1..maxNumOfCamelTDPData) OF
+	O-BcsmCamelTDP-Criteria 
+
+T-BCSM-CAMEL-TDP-CriteriaList ::= SEQUENCE SIZE (1..maxNumOfCamelTDPData) OF
+	T-BCSM-CAMEL-TDP-Criteria 
+
+O-BcsmCamelTDP-Criteria ::= SEQUENCE {
+	o-BcsmTriggerDetectionPoint	O-BcsmTriggerDetectionPoint,	
+	destinationNumberCriteria 	[0] DestinationNumberCriteria	OPTIONAL,
+	basicServiceCriteria	[1] BasicServiceCriteria	OPTIONAL,
+	callTypeCriteria	[2] CallTypeCriteria	OPTIONAL,
+	...,
+	o-CauseValueCriteria	[3] O-CauseValueCriteria	OPTIONAL,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL }
+
+T-BCSM-CAMEL-TDP-Criteria ::= SEQUENCE {
+	t-BCSM-TriggerDetectionPoint	T-BcsmTriggerDetectionPoint,	
+	basicServiceCriteria	[0] BasicServiceCriteria	OPTIONAL,
+	t-CauseValueCriteria	[1] T-CauseValueCriteria	OPTIONAL,
+	... }
+
+DestinationNumberCriteria  ::= SEQUENCE {
+	matchType		[0] MatchType,
+	destinationNumberList 	[1] DestinationNumberList	OPTIONAL,
+	destinationNumberLengthList	[2] DestinationNumberLengthList	OPTIONAL,
+	-- one or both of destinationNumberList and destinationNumberLengthList 
+	-- shall be present
+	...}
+
+DestinationNumberList  ::= SEQUENCE SIZE	(1..maxNumOfCamelDestinationNumbers) OF
+				ISDN-AddressString
+	-- The receiving entity shall not check the format of a number in
+	-- the dialled number list
+
+DestinationNumberLengthList  ::= SEQUENCE SIZE (1..maxNumOfCamelDestinationNumberLengths) OF 
+					INTEGER(1..maxNumOfISDN-AddressDigits)
+
+BasicServiceCriteria   ::= SEQUENCE SIZE(1..maxNumOfCamelBasicServiceCriteria) OF
+	Ext-BasicServiceCode
+
+maxNumOfISDN-AddressDigits  INTEGER ::= 15
+
+maxNumOfCamelDestinationNumbers  INTEGER ::= 10
+
+maxNumOfCamelDestinationNumberLengths  INTEGER ::= 3
+
+maxNumOfCamelBasicServiceCriteria  INTEGER ::= 5
+
+CallTypeCriteria       ::= ENUMERATED {
+	forwarded 	(0),
+	notForwarded	(1)}
+
+MatchType       ::= ENUMERATED {
+	inhibiting 	(0),
+	enabling		(1)}
+
+O-CauseValueCriteria   ::= SEQUENCE SIZE(1..maxNumOfCAMEL-O-CauseValueCriteria) OF
+	CauseValue
+
+T-CauseValueCriteria   ::= SEQUENCE SIZE(1..maxNumOfCAMEL-T-CauseValueCriteria) OF
+	CauseValue
+
+maxNumOfCAMEL-O-CauseValueCriteria  INTEGER ::= 5
+
+maxNumOfCAMEL-T-CauseValueCriteria  INTEGER ::= 5
+
+CauseValue ::= OCTET STRING (SIZE(1))
+-- Type extracted from Cause parameter in ITU-T Recommendation Q.763.
+-- For the use of cause value refer to ITU-T Recommendation Q.850.
+
+DefaultCallHandling ::= ENUMERATED {
+	continueCall (0) ,
+	releaseCall (1) ,
+	...}
+	-- exception handling:
+	-- reception of values in range 2-31 shall be treated as "continueCall"
+	-- reception of values greater than 31 shall be treated as "releaseCall"
+
+CamelCapabilityHandling ::= INTEGER(1..16) 
+	-- value 1 = CAMEL phase 1,
+	-- value 2 = CAMEL phase 2,
+	-- value 3 = CAMEL Phase 3,
+	-- value 4 = CAMEL phase 4:
+	-- reception of values greater than 4 shall be treated as CAMEL phase 4.
+
+SupportedCamelPhases ::= BIT STRING {
+	phase1 (0),
+	phase2 (1),
+	phase3 (2),
+	phase4 (3)} (SIZE (1..16)) 
+-- A node shall mark in the BIT STRING all CAMEL Phases it supports.
+-- Other values than listed above shall be discarded.
+
+OfferedCamel4CSIs ::= BIT STRING { 	
+	o-csi		(0),
+	d-csi		(1),
+	vt-csi		(2),
+	t-csi		(3),
+	mt-sms-csi	(4),
+	mg-csi		(5),
+	psi-enhancements	(6) 
+} (SIZE (7..16))
+-- A node supporting Camel phase 4 shall mark in the BIT STRING all Camel4 CSIs 
+-- it offers.
+-- Other values than listed above shall be discarded.
+
+OfferedCamel4Functionalities ::= BIT STRING { 	
+	initiateCallAttempt	(0),
+	splitLeg		(1),
+	moveLeg		(2),
+	disconnectLeg	(3),
+	entityReleased	(4),
+	dfc-WithArgument	(5),
+	playTone		(6),
+	dtmf-MidCall	(7),
+	chargingIndicator	(8),
+	alertingDP	(9),
+	locationAtAlerting	(10),
+	changeOfPositionDP	(11),
+	or-Interactions	(12),
+	warningToneEnhancements	(13),
+	cf-Enhancements	(14),
+	subscribedEnhancedDialledServices 	(15),
+	servingNetworkEnhancedDialledServices (16),
+	criteriaForChangeOfPositionDP	(17),
+	serviceChangeDP	(18),
+	collectInformation	(19)
+} (SIZE (15..64))
+-- A node supporting Camel phase 4 shall mark in the BIT STRING all CAMEL4 
+-- functionalities it offers.
+-- Other values than listed above shall be discarded.
+
+SMS-CSI ::= SEQUENCE {
+	sms-CAMEL-TDP-DataList	[0] SMS-CAMEL-TDP-DataList	OPTIONAL,
+	camelCapabilityHandling	[1] CamelCapabilityHandling	OPTIONAL,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	notificationToCSE	[3] NULL		OPTIONAL,
+	csi-Active	[4] NULL		OPTIONAL,
+	...}
+--	notificationToCSE and csi-Active shall not be present
+--	when MO-SMS-CSI or MT-SMS-CSI is sent to VLR or SGSN.
+--	They may only be included in ATSI/ATM ack/NSDC message.
+--	SMS-CAMEL-TDP-Data and  camelCapabilityHandling shall be present in 
+--	the SMS-CSI sequence.
+--	If SMS-CSI is segmented, sms-CAMEL-TDP-DataList and camelCapabilityHandling shall be 
+--	present in the first segment
+
+SMS-CAMEL-TDP-DataList ::= SEQUENCE SIZE (1..maxNumOfCamelTDPData) OF
+	SMS-CAMEL-TDP-Data
+--	SMS-CAMEL-TDP-DataList shall not contain more than one instance of
+--	SMS-CAMEL-TDP-Data containing the same value for sms-TriggerDetectionPoint.
+
+SMS-CAMEL-TDP-Data ::= SEQUENCE {
+	sms-TriggerDetectionPoint	[0] SMS-TriggerDetectionPoint,
+	serviceKey	[1] ServiceKey,
+	gsmSCF-Address	[2] ISDN-AddressString,
+	defaultSMS-Handling	[3] DefaultSMS-Handling,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	...
+	}
+
+SMS-TriggerDetectionPoint ::= ENUMERATED {
+	sms-CollectedInfo (1),
+	...,
+	sms-DeliveryRequest (2)
+	}
+--	exception handling:
+--	For SMS-CAMEL-TDP-Data and MT-smsCAMELTDP-Criteria sequences containing this 
+--	parameter with any other value than the ones listed the receiver shall ignore
+--	the whole sequence.
+--
+--	If this parameter is received with any other value than sms-CollectedInfo
+--	in an SMS-CAMEL-TDP-Data sequence contained in mo-sms-CSI, then the receiver shall
+--	ignore the whole SMS-CAMEL-TDP-Data sequence.
+--
+--	If this parameter is received with any other value than sms-DeliveryRequest
+--	in an SMS-CAMEL-TDP-Data sequence contained in mt-sms-CSI then the receiver shall
+--	ignore the whole SMS-CAMEL-TDP-Data sequence.
+--
+--	If this parameter is received with any other value than sms-DeliveryRequest
+--	in an MT-smsCAMELTDP-Criteria sequence then the receiver shall
+--	ignore the whole MT-smsCAMELTDP-Criteria sequence.
+
+DefaultSMS-Handling ::= ENUMERATED {
+	continueTransaction (0) ,
+	releaseTransaction (1) ,
+	...}
+--	exception handling:
+--	reception of values in range 2-31 shall be treated as "continueTransaction"
+--	reception of values greater than 31 shall be treated as "releaseTransaction"
+
+M-CSI ::= SEQUENCE {
+	mobilityTriggers	MobilityTriggers,
+	serviceKey	ServiceKey,
+	gsmSCF-Address	[0]	ISDN-AddressString,
+	extensionContainer	[1]	ExtensionContainer	OPTIONAL,
+	notificationToCSE	[2] NULL		OPTIONAL,
+	csi-Active	[3] NULL		OPTIONAL,
+	...}
+--	notificationToCSE and csi-Active shall not be present when M-CSI is sent to VLR.
+--	They may only be included in ATSI/ATM ack/NSDC message.
+
+MG-CSI ::= SEQUENCE {
+	mobilityTriggers	MobilityTriggers,
+	serviceKey	ServiceKey,
+	gsmSCF-Address	[0]	ISDN-AddressString,
+	extensionContainer	[1]	ExtensionContainer	OPTIONAL,
+	notificationToCSE	[2] NULL		OPTIONAL,
+	csi-Active	[3] NULL		OPTIONAL,
+	...}
+--	notificationToCSE and csi-Active shall not be present when MG-CSI is sent to SGSN.
+--	They may only be included in ATSI/ATM ack/NSDC message.
+
+MobilityTriggers  ::= SEQUENCE SIZE (1..maxNumOfMobilityTriggers) OF
+	MM-Code
+
+maxNumOfMobilityTriggers INTEGER ::= 10
+
+MM-Code ::= OCTET STRING (SIZE (1))
+--	This type is used to indicate a Mobility Management event.
+--	Actions for the following MM-Code values are defined in CAMEL Phase 4:
+--
+--	CS domain MM events:
+--	Location-update-in-same-VLR	MM-Code ::= '00000000'B
+--	Location-update-to-other-VLR	MM-Code ::= '00000001'B
+--	IMSI-Attach	MM-Code ::= '00000010'B
+--	MS-initiated-IMSI-Detach	MM-Code ::= '00000011'B
+--	Network-initiated-IMSI-Detach	MM-Code ::= '00000100'B
+--
+--	PS domain MM events:
+--	Routeing-Area-update-in-same-SGSN	MM-Code ::= '10000000'B
+--	Routeing-Area-update-to-other-SGSN-update-from-new-SGSN
+--				MM-Code ::= '10000001'B
+--	Routeing-Area-update-to-other-SGSN-disconnect-by-detach
+--				MM-Code ::= '10000010'B
+--	GPRS-Attach	MM-Code ::= '10000011'B
+--	MS-initiated-GPRS-Detach	MM-Code ::= '10000100'B
+--	Network-initiated-GPRS-Detach	MM-Code ::= '10000101'B 
+--	Network-initiated-transfer-to-MS-not-reachable-for-paging
+--				MM-Code ::= '10000110'B
+--
+--	If the MSC receives any other MM-code than the ones listed above for the
+--	CS domain, then the MSC shall ignore that MM-code.
+--	If the SGSN receives any other MM-code than the ones listed above for the
+--	PS domain, then the SGSN shall ignore that MM-code.
+
+T-CSI ::= SEQUENCE {
+	t-BcsmCamelTDPDataList	T-BcsmCamelTDPDataList,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	camelCapabilityHandling	[0] CamelCapabilityHandling	OPTIONAL,
+	notificationToCSE	[1] NULL		OPTIONAL,
+	csi-Active	[2] NULL		OPTIONAL}
+--	notificationToCSE and csi-Active shall not be present when VT-CSI/T-CSI is sent
+--	to VLR/GMSC.
+--	They may only be included in ATSI/ATM ack/NSDC message.
+--	T-CSI shall not be segmented.
+
+T-BcsmCamelTDPDataList ::= SEQUENCE SIZE (1..maxNumOfCamelTDPData) OF
+	T-BcsmCamelTDPData
+	--- T-BcsmCamelTDPDataList shall not contain more than one instance of
+	--- T-BcsmCamelTDPData containing the same value for t-BcsmTriggerDetectionPoint.
+	--- For CAMEL Phase 2, this means that only one instance of T-BcsmCamelTDPData is allowed
+	--- with t-BcsmTriggerDetectionPoint being equal to DP12. 
+	--- For CAMEL Phase 3, more TDPÂ’s are allowed.
+
+T-BcsmCamelTDPData ::= SEQUENCE {
+	t-BcsmTriggerDetectionPoint	T-BcsmTriggerDetectionPoint,
+	serviceKey	ServiceKey,
+	gsmSCF-Address	[0] ISDN-AddressString,
+	defaultCallHandling	[1] DefaultCallHandling,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...}
+
+T-BcsmTriggerDetectionPoint ::= ENUMERATED {
+	termAttemptAuthorized (12),
+	... ,
+	tBusy (13),
+	tNoAnswer (14)}
+	-- exception handling:
+	-- For T-BcsmCamelTDPData sequences containing this parameter with any other
+	-- value than the ones listed above, the receiver shall ignore the whole
+	-- T-BcsmCamelTDPData sequence.
+
+-- gprs location information retrieval types
+
+SendRoutingInfoForGprsArg ::= SEQUENCE {
+	imsi				[0] IMSI,
+	ggsn-Address		[1] GSN-Address	OPTIONAL, 
+	ggsn-Number		[2]	ISDN-AddressString,
+	extensionContainer		[3] ExtensionContainer	OPTIONAL,
+	...}
+
+SendRoutingInfoForGprsRes ::= SEQUENCE {
+	sgsn-Address		[0] GSN-Address,
+	ggsn-Address		[1]	GSN-Address	OPTIONAL,
+	mobileNotReachableReason		[2]	AbsentSubscriberDiagnosticSM		OPTIONAL,
+	extensionContainer		[3] ExtensionContainer	OPTIONAL,
+	...}
+
+-- failure report types
+
+FailureReportArg ::= SEQUENCE {
+	imsi				[0] IMSI,
+	ggsn-Number		[1] ISDN-AddressString	,
+	ggsn-Address		[2] GSN-Address	OPTIONAL,
+	extensionContainer		[3] ExtensionContainer	OPTIONAL,
+	...}
+
+FailureReportRes ::= SEQUENCE {
+	ggsn-Address		[0] GSN-Address	OPTIONAL,
+	extensionContainer		[1] ExtensionContainer	OPTIONAL,
+	...}
+
+-- gprs notification types
+
+NoteMsPresentForGprsArg ::= SEQUENCE {
+	imsi				[0] IMSI,
+	sgsn-Address		[1] GSN-Address,
+	ggsn-Address		[2] GSN-Address	OPTIONAL,
+	extensionContainer		[3] ExtensionContainer	OPTIONAL,
+	...}
+
+NoteMsPresentForGprsRes ::= SEQUENCE {
+	extensionContainer		[0] ExtensionContainer	OPTIONAL,
+	...}
+
+-- fault recovery types
+
+ResetArg ::= SEQUENCE {
+	hlr-Number	ISDN-AddressString,
+	hlr-List		HLR-List		OPTIONAL,
+	...}
+
+RestoreDataArg ::= SEQUENCE {
+	imsi			IMSI,
+	lmsi			LMSI			OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	vlr-Capability	[6] VLR-Capability	OPTIONAL }
+
+RestoreDataRes ::= SEQUENCE {
+	hlr-Number	ISDN-AddressString,
+	msNotReachable	NULL			OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+-- VBS/VGCS types
+VBSDataList ::= SEQUENCE SIZE (1..maxNumOfVBSGroupIds) OF
+				VoiceBroadcastData
+
+VGCSDataList ::= SEQUENCE SIZE (1..maxNumOfVGCSGroupIds) OF
+				VoiceGroupCallData
+
+maxNumOfVBSGroupIds  INTEGER ::= 50
+
+maxNumOfVGCSGroupIds  INTEGER ::= 50
+
+VoiceGroupCallData  ::= SEQUENCE {
+	groupId		GroupId, 
+	-- groupId shall be filled with six TBCD fillers (1111)if the longGroupId is present  
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	additionalSubscriptions	AdditionalSubscriptions	OPTIONAL,
+	additionalInfo	[0] AdditionalInfo	OPTIONAL,
+	longGroupId	[1] Long-GroupId	OPTIONAL }
+
+	-- VoiceGroupCallData containing a longGroupId shall not be sent to VLRs that did not
+	-- indicate support of long Group IDs within the Update Location or Restore Data 
+	-- request message
+
+AdditionalInfo ::= BIT STRING (SIZE (1..136))
+--	 Refers to Additional Info as specified in 3GPP TS 43.068 
+
+AdditionalSubscriptions ::= BIT STRING {
+	privilegedUplinkRequest (0),
+	emergencyUplinkRequest (1),
+	emergencyReset (2)} (SIZE (3..8))
+-- Other bits than listed above shall be discarded.
+
+VoiceBroadcastData ::= SEQUENCE {
+	groupid		GroupId, 
+	-- groupId shall be filled with six TBCD fillers (1111)if the longGroupId is present
+	broadcastInitEntitlement	NULL			OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	longGroupId	[0] Long-GroupId	OPTIONAL }
+	
+-- VoiceBroadcastData containing a longGroupId shall not be sent to VLRs that did not
+-- indicate support of long Group IDs within the Update Location or Restore Data 
+	-- request message
+
+GroupId  ::= TBCD-STRING (SIZE (3))
+	-- When Group-Id is less than six characters in length, the TBCD filler (1111)
+	-- is used to fill unused half octets.
+	-- Refers to the Group Identification as specified in 3GPP TS 23.003 
+	-- and 3GPP TS 43.068/ 43.069
+
+Long-GroupId  ::= TBCD-STRING (SIZE (4))
+	-- When Long-Group-Id is less than eight characters in length, the TBCD filler (1111)
+	-- is used to fill unused half octets.
+	-- Refers to the Group Identification as specified in 3GPP TS 23.003 
+	-- and 3GPP TS 43.068/ 43.069
+
+
+-- provide subscriber info types
+
+ProvideSubscriberInfoArg ::= SEQUENCE {
+	imsi		[0] IMSI,
+	lmsi		[1] LMSI	OPTIONAL,
+	requestedInfo	[2] RequestedInfo,
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	...,
+	callPriority	[4]	EMLPP-Priority	OPTIONAL
+	}
+
+ProvideSubscriberInfoRes ::= SEQUENCE {
+	subscriberInfo	SubscriberInfo,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+SubscriberInfo ::= SEQUENCE {
+	locationInformation	[0] LocationInformation	OPTIONAL,
+	subscriberState	[1] SubscriberState	OPTIONAL,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	... ,
+	locationInformationGPRS	[3] LocationInformationGPRS	OPTIONAL,
+	ps-SubscriberState	[4] PS-SubscriberState	OPTIONAL,
+	imei			[5] IMEI		OPTIONAL,
+	ms-Classmark2	[6] MS-Classmark2	OPTIONAL,
+	gprs-MS-Class	[7] GPRSMSClass	OPTIONAL,
+	mnpInfoRes	[8] MNPInfoRes	OPTIONAL }
+
+--	If the HLR receives locationInformation, subscriberState or ms-Classmark2 from an SGSN
+--	it shall discard them.
+--	If the HLR receives locationInformationGPRS, ps-SubscriberState or gprs-MS-Class from
+--	a VLR it shall discard them.
+--	If the HLR receives parameters which it has not requested, it shall discard them.
+
+MNPInfoRes ::= SEQUENCE {
+	routeingNumber	[0] RouteingNumber 	OPTIONAL,
+	imsi			[1] IMSI		OPTIONAL,
+	msisdn		[2] ISDN-AddressString 	OPTIONAL,
+	numberPortabilityStatus	[3] NumberPortabilityStatus	OPTIONAL,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	... }
+--	The IMSI parameter contains a generic IMSI, i.e. it is not tied necessarily to the 
+--	Subscriber. MCC and MNC values in this IMSI shall point to the Subscription Network of 
+--	the Subscriber. See 3GPP TS 23.066 [108].
+
+RouteingNumber ::= TBCD-STRING (SIZE (1..5))
+
+
+NumberPortabilityStatus ::= ENUMERATED {
+	notKnownToBePorted	(0),
+	ownNumberPortedOut	(1),
+	foreignNumberPortedToForeignNetwork	(2),
+	...,
+	ownNumberNotPortedOut	(4),
+	foreignNumberPortedIn	(5)
+	}
+	--	exception handling: 
+	--  reception of other values than the ones listed the receiver shall ignore the 
+	--  whole NumberPortabilityStatus;
+	--  ownNumberNotPortedOut or foreignNumberPortedIn may only be included in Any Time 
+	--  Interrogation message.
+
+MS-Classmark2 ::= OCTET STRING (SIZE (3))
+	-- This parameter carries the value part of the MS Classmark 2 IE defined in 
+	-- 3GPP TS 24.008 [35].
+
+GPRSMSClass ::= SEQUENCE {
+	mSNetworkCapability	[0] MSNetworkCapability,
+	mSRadioAccessCapability	[1] MSRadioAccessCapability	OPTIONAL
+	}
+
+MSNetworkCapability ::= OCTET STRING (SIZE (1..8))
+	-- This parameter carries the value part of the MS Network Capability IE defined in 
+	-- 3GPP TS 24.008 [35].
+	
+MSRadioAccessCapability ::= OCTET STRING (SIZE (1..50))
+	-- This parameter carries the value part of the MS Radio Access Capability IE defined in
+	-- 3GPP TS 24.008 [35].
+
+RequestedInfo ::= SEQUENCE {
+	locationInformation	[0] NULL		OPTIONAL,
+	subscriberState	[1] NULL		OPTIONAL,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	..., 
+	currentLocation	[3] NULL		OPTIONAL,
+	requestedDomain	[4] DomainType	OPTIONAL,
+	imei			[6] NULL		OPTIONAL,
+	ms-classmark	[5] NULL		OPTIONAL,
+	mnpRequestedInfo	[7] NULL 		OPTIONAL }
+
+--	currentLocation shall be absent if locationInformation is absent
+
+DomainType ::=  ENUMERATED {
+	cs-Domain		(0),
+	ps-Domain		(1),
+	...}
+-- exception handling:
+-- reception of values > 1 shall be mapped to 'cs-Domain'
+
+LocationInformation ::= SEQUENCE {
+	ageOfLocationInformation	AgeOfLocationInformation	OPTIONAL,
+	geographicalInformation	[0] GeographicalInformation	OPTIONAL,
+	vlr-number	[1] ISDN-AddressString	OPTIONAL,
+	locationNumber	[2] LocationNumber	OPTIONAL,
+	cellGlobalIdOrServiceAreaIdOrLAI	[3] CellGlobalIdOrServiceAreaIdOrLAI	OPTIONAL,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	... ,
+	selectedLSA-Id	[5] LSAIdentity	OPTIONAL,
+	msc-Number	[6] ISDN-AddressString	OPTIONAL,
+	geodeticInformation	[7] GeodeticInformation	OPTIONAL, 
+	currentLocationRetrieved	[8] NULL		OPTIONAL,
+	sai-Present	[9] NULL		OPTIONAL }
+-- sai-Present indicates that the cellGlobalIdOrServiceAreaIdOrLAI parameter contains
+-- a Service Area Identity.
+-- currentLocationRetrieved shall be present 
+-- if the location information were retrieved after a successfull paging.
+
+LocationInformationGPRS ::= SEQUENCE {
+	cellGlobalIdOrServiceAreaIdOrLAI	[0] CellGlobalIdOrServiceAreaIdOrLAI OPTIONAL,
+	routeingAreaIdentity	[1] RAIdentity	OPTIONAL,
+	geographicalInformation	[2] GeographicalInformation	OPTIONAL,
+	sgsn-Number	[3] ISDN-AddressString	OPTIONAL,
+	selectedLSAIdentity	[4] LSAIdentity	OPTIONAL,
+	extensionContainer	[5] ExtensionContainer	OPTIONAL,
+	...,
+	sai-Present	[6] NULL		OPTIONAL,
+	geodeticInformation	[7] GeodeticInformation	OPTIONAL,
+	currentLocationRetrieved	[8] NULL		OPTIONAL,
+	ageOfLocationInformation	[9] AgeOfLocationInformation	OPTIONAL }
+-- sai-Present indicates that the cellGlobalIdOrServiceAreaIdOrLAI parameter contains
+-- a Service Area Identity.
+-- currentLocationRetrieved shall be present if the location information
+-- was retrieved after successful paging.
+
+RAIdentity ::= OCTET STRING (SIZE (6))
+-- Routing Area Identity is coded in accordance with 3GPP TS 29.060 [105].
+-- It shall contain the value part defined in 3GPP TS 29.060 only. I.e. the 3GPP TS 29.060
+-- type identifier octet shall not be included.
+
+
+GeographicalInformation ::= OCTET STRING (SIZE (8))
+--	Refers to geographical Information defined in 3GPP TS 23.032.
+--	Only the description of an ellipsoid point with uncertainty circle
+--	as specified in 3GPP TS 23.032 is allowed to be used
+--	The internal structure according to 3GPP TS 23.032 is as follows:
+--		Type of shape (ellipsoid point with uncertainty circle)	1 octet
+--		Degrees of Latitude				3 octets
+--		Degrees of Longitude				3 octets
+--		Uncertainty code				1 octet
+
+GeodeticInformation ::= OCTET STRING (SIZE (10))
+--	Refers to Calling Geodetic Location defined in Q.763 (1999).
+--	Only the description of an ellipsoid point with uncertainty circle
+--	as specified in Q.763 (1999) is allowed to be used
+--	The internal structure according to Q.763 (1999) is as follows:
+--		Screening and presentation indicators		1 octet
+--		Type of shape (ellipsoid point with uncertainty circle)	1 octet
+--		Degrees of Latitude				3 octets
+--		Degrees of Longitude				3 octets
+--		Uncertainty code				1 octet
+--		Confidence				1 octet
+
+LocationNumber ::= OCTET STRING (SIZE (2..10))
+	-- the internal structure is defined in ITU-T Rec Q.763
+
+SubscriberState ::= CHOICE {
+	assumedIdle	[0] NULL,
+	camelBusy		[1] NULL,
+	netDetNotReachable	NotReachableReason,
+	notProvidedFromVLR	[2] NULL}
+
+PS-SubscriberState ::= CHOICE {
+	notProvidedFromSGSN	[0] NULL,
+	ps-Detached	[1] NULL,
+	ps-AttachedNotReachableForPaging	[2] NULL,
+	ps-AttachedReachableForPaging	[3] NULL,
+	ps-PDP-ActiveNotReachableForPaging	[4] PDP-ContextInfoList,
+	ps-PDP-ActiveReachableForPaging	[5] PDP-ContextInfoList,
+	netDetNotReachable	NotReachableReason }
+
+PDP-ContextInfoList ::= SEQUENCE SIZE (1..maxNumOfPDP-Contexts) OF
+				PDP-ContextInfo
+
+PDP-ContextInfo ::= SEQUENCE {
+	pdp-ContextIdentifier	[0] ContextId,
+	pdp-ContextActive	[1] NULL		OPTIONAL,
+	pdp-Type		[2] PDP-Type,
+	pdp-Address	[3] PDP-Address	OPTIONAL,
+	apn-Subscribed	[4] APN		OPTIONAL,
+	apn-InUse		[5] APN		OPTIONAL,
+	nsapi		[6] NSAPI		OPTIONAL,
+	transactionId	[7] TransactionId	OPTIONAL,
+	teid-ForGnAndGp	[8] TEID		OPTIONAL,
+	teid-ForIu	[9] TEID		OPTIONAL,
+	ggsn-Address	[10] GSN-Address 	OPTIONAL,
+	qos-Subscribed	[11] Ext-QoS-Subscribed	OPTIONAL,
+	qos-Requested	[12] Ext-QoS-Subscribed	OPTIONAL,
+	qos-Negotiated	[13] Ext-QoS-Subscribed	OPTIONAL,
+	chargingId	[14] GPRSChargingID	OPTIONAL,
+	chargingCharacteristics	[15] ChargingCharacteristics	OPTIONAL,
+	rnc-Address	[16] GSN-Address	OPTIONAL,
+	extensionContainer	[17] ExtensionContainer	OPTIONAL,
+	...,
+	qos2-Subscribed	[18] Ext2-QoS-Subscribed	OPTIONAL,
+	-- qos2-Subscribed may be present only if qos-Subscribed is present.
+	qos2-Requested	[19] Ext2-QoS-Subscribed	OPTIONAL,
+	-- qos2-Requested may be present only if qos-Requested is present.
+	qos2-Negotiated	[20] Ext2-QoS-Subscribed	OPTIONAL,
+	-- qos2-Negotiated may be present only if qos-Negotiated is present.
+		qos3-Subscribed	[21] Ext3-QoS-Subscribed	OPTIONAL,
+	-- qos3-Subscribed may be present only if qos2-Subscribed is present.
+	qos3-Requested	[22] Ext3-QoS-Subscribed	OPTIONAL,
+	-- qos3-Requested may be present only if qos2-Requested is present.
+	qos3-Negotiated	[23] Ext3-QoS-Subscribed	OPTIONAL
+	-- qos3-Negotiated may be present only if qos2-Negotiated is present.
+}
+
+NSAPI ::= INTEGER (0..15)
+--	This type is used to indicate the Network layer Service Access Point
+
+TransactionId ::= OCTET STRING (SIZE (1..2))
+--	This type carries the value part of the transaction identifier which is used in the 
+--	session management messages on the access interface. The encoding is defined in 
+--	3GPP TS 24.008
+
+TEID ::= OCTET STRING (SIZE (4))
+--	This type carries the value part of the Tunnel Endpoint Identifier which is used to 
+--	distinguish between different tunnels between the same pair of entities which communicate 
+--	using the GPRS Tunnelling Protocol The encoding is defined in 3GPP TS 29.060.
+
+GPRSChargingID ::= OCTET STRING (SIZE (4))
+--	The Charging ID is a unique four octet value generated by the GGSN when 
+--	a PDP Context is activated. A Charging ID is generated for each activated context.
+--	The encoding is defined in 3GPP TS 29.060.
+
+NotReachableReason ::= ENUMERATED {
+	msPurged (0),
+	imsiDetached (1),
+	restrictedArea (2),
+	notRegistered (3)}
+
+-- any time interrogation info types
+
+AnyTimeInterrogationArg ::= SEQUENCE {
+	subscriberIdentity	[0] SubscriberIdentity,
+	requestedInfo	[1] RequestedInfo,
+	gsmSCF-Address	[3] ISDN-AddressString,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...}
+
+AnyTimeInterrogationRes ::= SEQUENCE {
+	subscriberInfo	SubscriberInfo,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+-- any time information handling types
+
+AnyTimeSubscriptionInterrogationArg ::= SEQUENCE {
+	subscriberIdentity	[0] SubscriberIdentity,
+	requestedSubscriptionInfo	[1] RequestedSubscriptionInfo,
+	gsmSCF-Address	[2] ISDN-AddressString,
+	extensionContainer	[3] ExtensionContainer	OPTIONAL,
+	longFTN-Supported	[4]	NULL		OPTIONAL,
+	...}
+
+AnyTimeSubscriptionInterrogationRes ::= SEQUENCE {
+	callForwardingData	[1] CallForwardingData	OPTIONAL,
+	callBarringData	[2] CallBarringData	OPTIONAL,
+	odb-Info		[3] ODB-Info	OPTIONAL,
+	camel-SubscriptionInfo	[4] CAMEL-SubscriptionInfo	OPTIONAL,
+	supportedVLR-CAMEL-Phases	[5] SupportedCamelPhases	OPTIONAL,
+	supportedSGSN-CAMEL-Phases	[6] SupportedCamelPhases	OPTIONAL,
+	extensionContainer	[7] ExtensionContainer	OPTIONAL,
+	... ,
+	offeredCamel4CSIsInVLR	[8] OfferedCamel4CSIs	OPTIONAL,
+	offeredCamel4CSIsInSGSN	[9] OfferedCamel4CSIs	OPTIONAL,
+	msisdn-BS-List	[10] MSISDN-BS-List	OPTIONAL }
+
+RequestedSubscriptionInfo ::= SEQUENCE {
+	requestedSS-Info	[1] SS-ForBS-Code	OPTIONAL,
+	odb			[2] NULL		OPTIONAL,
+	requestedCAMEL-SubscriptionInfo	[3] RequestedCAMEL-SubscriptionInfo		OPTIONAL,
+	supportedVLR-CAMEL-Phases	[4] NULL		OPTIONAL,
+	supportedSGSN-CAMEL-Phases	[5] NULL		OPTIONAL,
+	extensionContainer	[6] ExtensionContainer	OPTIONAL,
+	...,
+	additionalRequestedCAMEL-SubscriptionInfo
+				[7] AdditionalRequestedCAMEL-SubscriptionInfo
+							OPTIONAL,
+	msisdn-BS-List	[8] NULL		OPTIONAL }
+
+MSISDN-BS-List ::= SEQUENCE SIZE (1..maxNumOfMSISDN) OF
+				MSISDN-BS
+
+maxNumOfMSISDN  INTEGER ::= 50
+
+
+MSISDN-BS ::= SEQUENCE {
+	msisdn			ISDN-AddressString,	
+	basicServiceList	[0]	BasicServiceList	OPTIONAL,
+	extensionContainer	[1]	ExtensionContainer	OPTIONAL,
+	...}
+
+RequestedCAMEL-SubscriptionInfo ::= ENUMERATED {
+	o-CSI		(0),
+	t-CSI		(1),
+	vt-CSI		(2),
+	tif-CSI		(3),
+	gprs-CSI		(4),
+	mo-sms-CSI	(5),
+	ss-CSI		(6),
+	m-CSI		(7),
+	d-csi		(8)}
+
+AdditionalRequestedCAMEL-SubscriptionInfo ::= ENUMERATED {
+	mt-sms-CSI	(0),
+	mg-csi		(1),
+	o-IM-CSI 		(2),
+	d-IM-CSI		(3),
+	vt-IM-CSI	 	(4),
+	...}
+--	exception handling: unknown values shall be discarded by the receiver.
+
+CallForwardingData ::= SEQUENCE {
+	forwardingFeatureList	Ext-ForwFeatureList,
+	notificationToCSE	NULL			OPTIONAL,
+	extensionContainer	[0] ExtensionContainer	OPTIONAL,
+	...}
+
+CallBarringData ::= SEQUENCE {
+	callBarringFeatureList	Ext-CallBarFeatureList,
+	password		Password		OPTIONAL,
+	wrongPasswordAttemptsCounter	WrongPasswordAttemptsCounter	OPTIONAL,
+	notificationToCSE	NULL			OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+WrongPasswordAttemptsCounter ::= INTEGER (0..4)
+
+ODB-Info ::= SEQUENCE {
+	odb-Data		ODB-Data,
+	notificationToCSE	NULL			OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+CAMEL-SubscriptionInfo ::= SEQUENCE {
+	o-CSI		[0]	O-CSI	OPTIONAL,
+	o-BcsmCamelTDP-CriteriaList	[1]	O-BcsmCamelTDPCriteriaList	OPTIONAL, 
+	d-CSI		[2]	D-CSI	OPTIONAL,
+	t-CSI		[3]	T-CSI	OPTIONAL,
+	t-BCSM-CAMEL-TDP-CriteriaList	[4]	T-BCSM-CAMEL-TDP-CriteriaList	OPTIONAL,
+	vt-CSI		[5]	T-CSI	OPTIONAL,
+	vt-BCSM-CAMEL-TDP-CriteriaList	[6]	T-BCSM-CAMEL-TDP-CriteriaList	OPTIONAL,
+	tif-CSI		[7]	NULL		OPTIONAL,
+	tif-CSI-NotificationToCSE	[8]	NULL		OPTIONAL,
+	gprs-CSI		[9]	GPRS-CSI	OPTIONAL,
+	mo-sms-CSI	[10]	SMS-CSI	OPTIONAL,
+	ss-CSI		[11]	SS-CSI	OPTIONAL,
+	m-CSI		[12]	M-CSI	OPTIONAL,
+	extensionContainer	[13]	ExtensionContainer	OPTIONAL,
+	...,
+	specificCSIDeletedList	[14]	SpecificCSI-Withdraw	OPTIONAL,
+	mt-sms-CSI	[15]	SMS-CSI	OPTIONAL,
+	mt-smsCAMELTDP-CriteriaList	[16]	MT-smsCAMELTDP-CriteriaList	OPTIONAL,
+	mg-csi		[17]	MG-CSI	OPTIONAL,
+	o-IM-CSI		[18] O-CSI	OPTIONAL,
+	o-IM-BcsmCamelTDP-CriteriaList	[19] O-BcsmCamelTDPCriteriaList	OPTIONAL,
+	d-IM-CSI		[20] D-CSI	OPTIONAL,
+	vt-IM-CSI		[21] T-CSI	OPTIONAL,
+	vt-IM-BCSM-CAMEL-TDP-CriteriaList	[22]	T-BCSM-CAMEL-TDP-CriteriaList	OPTIONAL
+	}
+
+AnyTimeModificationArg ::= SEQUENCE {
+	subscriberIdentity	[0]	SubscriberIdentity,
+	gsmSCF-Address	[1]	ISDN-AddressString,
+	modificationRequestFor-CF-Info	[2]	ModificationRequestFor-CF-Info OPTIONAL,
+	modificationRequestFor-CB-Info	[3]	ModificationRequestFor-CB-Info OPTIONAL,
+	modificationRequestFor-CSI	[4]	ModificationRequestFor-CSI	OPTIONAL,
+	extensionContainer	[5]	ExtensionContainer	OPTIONAL,
+	longFTN-Supported	[6]	NULL		OPTIONAL,
+	...,
+	modificationRequestFor-ODB-data	[7]	ModificationRequestFor-ODB-data OPTIONAL,
+	modificationRequestFor-IP-SM-GW-Data	[8]	ModificationRequestFor-IP-SM-GW-Data OPTIONAL }
+
+AnyTimeModificationRes ::= SEQUENCE {
+	ss-InfoFor-CSE	[0]	Ext-SS-InfoFor-CSE	OPTIONAL,
+	camel-SubscriptionInfo	[1]	CAMEL-SubscriptionInfo	OPTIONAL,
+	extensionContainer	[2]	ExtensionContainer	OPTIONAL,
+	...,
+	odb-Info		[3]	ODB-Info	OPTIONAL }
+
+ModificationRequestFor-CF-Info ::= SEQUENCE {
+	ss-Code		[0]	SS-Code,
+	basicService	[1]	Ext-BasicServiceCode	OPTIONAL,
+	ss-Status		[2]	Ext-SS-Status	OPTIONAL,
+	forwardedToNumber	[3]	AddressString	OPTIONAL,
+	forwardedToSubaddress	[4]	ISDN-SubaddressString	OPTIONAL,
+	noReplyConditionTime	[5]	Ext-NoRepCondTime	OPTIONAL,
+	modifyNotificationToCSE	[6]	ModificationInstruction	OPTIONAL,
+	extensionContainer	[7]	ExtensionContainer	OPTIONAL,
+	...}
+
+ModificationRequestFor-CB-Info ::= SEQUENCE {
+	ss-Code		[0]	SS-Code,
+	basicService	[1]	Ext-BasicServiceCode	OPTIONAL,
+	ss-Status		[2]	Ext-SS-Status	OPTIONAL,
+	password		[3]	Password	OPTIONAL,
+	wrongPasswordAttemptsCounter	[4]	WrongPasswordAttemptsCounter	OPTIONAL,
+	modifyNotificationToCSE	[5]	ModificationInstruction	OPTIONAL,
+	extensionContainer	[6]	ExtensionContainer	OPTIONAL,
+	...}
+
+ModificationRequestFor-ODB-data ::= SEQUENCE {
+	odb-data		[0]	ODB-Data	OPTIONAL,
+	modifyNotificationToCSE	[1]	ModificationInstruction	OPTIONAL,
+	extensionContainer	[2]	ExtensionContainer	OPTIONAL,
+	...}
+
+ModificationRequestFor-CSI ::= SEQUENCE {
+	requestedCamel-SubscriptionInfo	[0]	RequestedCAMEL-SubscriptionInfo,
+	modifyNotificationToCSE	[1]	ModificationInstruction	OPTIONAL,
+	modifyCSI-State	[2]	ModificationInstruction	OPTIONAL,
+	extensionContainer	[3]	ExtensionContainer	OPTIONAL,
+	...,
+	additionalRequestedCAMEL-SubscriptionInfo
+				[4] AdditionalRequestedCAMEL-SubscriptionInfo
+							OPTIONAL }
+-- requestedCamel-SubscriptionInfo shall be discarded if
+-- additionalRequestedCAMEL-SubscriptionInfo is received
+
+ModificationRequestFor-IP-SM-GW-Data ::= SEQUENCE {
+	modifyRegistrationStatus	[0]	ModificationInstruction	OPTIONAL,
+	extensionContainer	[1]	ExtensionContainer	OPTIONAL,
+	...}
+
+ModificationInstruction ::= ENUMERATED {
+	deactivate	(0),
+	activate		(1)}
+
+-- subscriber data modification notification types
+
+NoteSubscriberDataModifiedArg ::= SEQUENCE {
+	imsi			IMSI,
+	msisdn		ISDN-AddressString,
+	forwardingInfoFor-CSE	[0] Ext-ForwardingInfoFor-CSE	OPTIONAL,
+	callBarringInfoFor-CSE	[1] Ext-CallBarringInfoFor-CSE	OPTIONAL,
+	odb-Info		[2] ODB-Info	OPTIONAL,
+	camel-SubscriptionInfo	[3] CAMEL-SubscriptionInfo	OPTIONAL,
+	allInformationSent	[4] NULL		OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+NoteSubscriberDataModifiedRes ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+-- mobility management event notificatioon info types
+
+NoteMM-EventArg::= SEQUENCE {
+	serviceKey	ServiceKey,
+	eventMet		[0]	MM-Code,
+	imsi			[1]	IMSI,
+	msisdn		[2]	ISDN-AddressString,
+	locationInformation	[3]	LocationInformation	OPTIONAL,
+	supportedCAMELPhases	[5]	SupportedCamelPhases	OPTIONAL,
+	extensionContainer	[6]	ExtensionContainer	OPTIONAL,
+	...,
+	locationInformationGPRS	[7]	LocationInformationGPRS	OPTIONAL,
+	offeredCamel4Functionalities	[8] OfferedCamel4Functionalities	OPTIONAL
+}
+
+NoteMM-EventRes ::= SEQUENCE {
+	extensionContainer	ExtensionContainer 	OPTIONAL,
+	...}
+
+Ext-SS-InfoFor-CSE ::= CHOICE {
+	forwardingInfoFor-CSE	[0] Ext-ForwardingInfoFor-CSE,
+	callBarringInfoFor-CSE	[1] Ext-CallBarringInfoFor-CSE
+	}
+
+Ext-ForwardingInfoFor-CSE ::= SEQUENCE {
+	ss-Code		[0]	SS-Code,
+	forwardingFeatureList	[1]	Ext-ForwFeatureList,
+	notificationToCSE	[2]	NULL		OPTIONAL,
+	extensionContainer	[3]	ExtensionContainer	OPTIONAL,
+	...}
+
+Ext-CallBarringInfoFor-CSE ::= SEQUENCE {
+	ss-Code		[0]	SS-Code,
+	callBarringFeatureList	[1]	Ext-CallBarFeatureList,
+	password		[2]	Password	OPTIONAL,
+	wrongPasswordAttemptsCounter	[3]	WrongPasswordAttemptsCounter	OPTIONAL,
+	notificationToCSE	[4]	NULL		OPTIONAL,
+	extensionContainer	[5]	ExtensionContainer 	OPTIONAL,
+	...}
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-OM-DataTypes.asn b/rrlp-ephemeris/asn1/MAP-OM-DataTypes.asn
new file mode 100644
index 0000000..024dd6f
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-OM-DataTypes.asn
@@ -0,0 +1,216 @@
+-- $Id: MAP-OM-DataTypes.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- 17.7.2	Operation and maintenance data types
+-- 3GPP TS 29.002 V8.9.0 (2009-04)
+ 
+MAP-OM-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-OM-DataTypes (12) version11 (11)}
+
+DEFINITIONS
+
+IMPLICIT TAGS
+
+::=
+
+BEGIN
+
+EXPORTS
+	ActivateTraceModeArg,
+	ActivateTraceModeRes,
+	DeactivateTraceModeArg,
+	DeactivateTraceModeRes,
+	TracePropagationList
+;
+
+IMPORTS
+	AddressString,
+	IMSI
+FROM MAP-CommonDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-CommonDataTypes (18) version11 (11)}
+
+	ExtensionContainer
+FROM MAP-ExtensionDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+
+;
+
+ActivateTraceModeArg ::= SEQUENCE {
+	imsi			[0] IMSI		OPTIONAL,
+	traceReference	[1] TraceReference,
+	traceType		[2] TraceType,
+	omc-Id		[3] AddressString	OPTIONAL,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	...,
+	traceReference2	[5] TraceReference2	OPTIONAL,
+	traceDepthList	[6] TraceDepthList	OPTIONAL,
+	traceNE-TypeList	[7] TraceNE-TypeList	OPTIONAL,
+	traceInterfaceList	[8] TraceInterfaceList	OPTIONAL,
+	traceEventList	[9] TraceEventList	OPTIONAL
+	}
+
+TraceReference ::= OCTET STRING (SIZE (1..2))
+
+TraceReference2 ::= OCTET STRING (SIZE (3))
+
+TraceRecordingSessionReference ::= OCTET STRING (SIZE (2))
+
+TraceType ::= INTEGER
+	(0..255)
+	-- Trace types are fully defined in  3GPP TS 52.008. [61]
+
+TraceDepthList ::= SEQUENCE {
+	msc-s-TraceDepth	[0] TraceDepth	OPTIONAL,
+	mgw-TraceDepth	[1] TraceDepth	OPTIONAL,
+	sgsn-TraceDepth	[2] TraceDepth	OPTIONAL,
+	ggsn-TraceDepth	[3] TraceDepth	OPTIONAL,
+	rnc-TraceDepth	[4] TraceDepth	OPTIONAL,
+	bmsc-TraceDepth	[5] TraceDepth	OPTIONAL,
+	...}
+
+TraceDepth ::= ENUMERATED {
+	minimum (0),
+	medium (1),
+	maximum (2),
+	...}
+-- The value medium is applicable only for RNC. For other network elements, if value medium
+-- is received, value minimum shall be applied.
+
+TraceNE-TypeList ::= BIT STRING {
+	msc-s (0),
+	mgw (1),
+	sgsn (2),
+	ggsn (3),
+	rnc (4),
+	bm-sc (5)} (SIZE (6..16))
+-- Other bits than listed above shall be discarded.
+
+TraceInterfaceList ::= SEQUENCE {
+	msc-s-List	[0] MSC-S-InterfaceList	OPTIONAL,
+	mgw-List		[1] MGW-InterfaceList	OPTIONAL,
+	sgsn-List		[2] SGSN-InterfaceList	OPTIONAL,
+	ggsn-List		[3] GGSN-InterfaceList	OPTIONAL,
+	rnc-List		[4] RNC-InterfaceList	OPTIONAL,
+	bmsc-List		[5] BMSC-InterfaceList	OPTIONAL,
+	...}
+
+MSC-S-InterfaceList ::= BIT STRING {
+	a (0),
+	iu (1),
+	mc (2),
+	map-g (3),
+	map-b (4),
+	map-e (5),
+	map-f (6),
+	cap (7),
+	map-d (8),
+	map-c (9)} (SIZE (10..16))
+-- Other bits than listed above shall be discarded.
+
+MGW-InterfaceList ::= BIT STRING {
+	mc (0),
+	nb-up (1),
+	iu-up (2)} (SIZE (3..8))
+-- Other bits than listed above shall be discarded.
+
+SGSN-InterfaceList ::= BIT STRING {
+	gb (0),
+	iu (1),
+	gn (2),
+	map-gr (3),
+	map-gd (4),
+	map-gf (5),
+	gs (6),
+	ge (7)} (SIZE (8..16))
+-- Other bits than listed above shall be discarded.
+
+GGSN-InterfaceList ::= BIT STRING {
+	gn (0),
+	gi (1),
+	gmb (2)} (SIZE (3..8))
+-- Other bits than listed above shall be discarded.
+
+RNC-InterfaceList ::= BIT STRING {
+	iu (0),
+	iur (1),
+	iub (2),
+	uu (3)} (SIZE (4..8))
+-- Other bits than listed above shall be discarded.
+
+BMSC-InterfaceList ::= BIT STRING {
+	gmb (0)} (SIZE (1..8))
+-- Other bits than listed above shall be discarded.
+
+TraceEventList ::= SEQUENCE {
+	msc-s-List	[0] MSC-S-EventList	OPTIONAL,
+	mgw-List		[1] MGW-EventList	OPTIONAL,
+	sgsn-List		[2] SGSN-EventList	OPTIONAL,
+	ggsn-List		[3] GGSN-EventList	OPTIONAL,
+	bmsc-List		[4] BMSC-EventList	OPTIONAL,
+	...}
+
+MSC-S-EventList ::= BIT STRING {
+	mo-mtCall (0),
+	mo-mt-sms (1),
+	lu-imsiAttach-imsiDetach (2),
+	handovers (3),
+	ss (4)} (SIZE (5..16))
+-- Other bits than listed above shall be discarded.
+
+MGW-EventList ::= BIT STRING {
+	context (0)} (SIZE (1..8))
+-- Other bits than listed above shall be discarded.
+
+SGSN-EventList ::= BIT STRING {
+	pdpContext (0),
+	mo-mt-sms (1),
+	rau-gprsAttach-gprsDetach (2),
+	mbmsContext (3)} (SIZE (4..16))
+-- Other bits than listed above shall be discarded.
+
+GGSN-EventList ::= BIT STRING {
+	pdpContext (0),
+	mbmsContext (1)} (SIZE (2..8))
+-- Other bits than listed above shall be discarded.
+
+BMSC-EventList ::= BIT STRING {
+	mbmsMulticastServiceActivation (0)} (SIZE (1..8))
+-- Other bits than listed above shall be discarded.
+
+
+TracePropagationList ::= SEQUENCE {
+	traceReference	[0] TraceReference	OPTIONAL,
+	traceType		[1] TraceType	OPTIONAL,
+	traceReference2	[2] TraceReference2	OPTIONAL,
+	traceRecordingSessionReference	[3] TraceRecordingSessionReference OPTIONAL,
+	rnc-TraceDepth	[4] TraceDepth	OPTIONAL,
+	rnc-InterfaceList	[5] RNC-InterfaceList	OPTIONAL,
+	msc-s-TraceDepth	[6] TraceDepth	OPTIONAL,
+	msc-s-InterfaceList	[7] MSC-S-InterfaceList	OPTIONAL,
+	msc-s-EventList	[8] MSC-S-EventList	OPTIONAL,
+	mgw-TraceDepth	[9] TraceDepth	OPTIONAL,
+	mgw-InterfaceList	[10] MGW-InterfaceList	OPTIONAL,
+	mgw-EventList	[11] MGW-EventList	OPTIONAL,
+	...}
+
+ActivateTraceModeRes ::= SEQUENCE {
+	extensionContainer	[0] ExtensionContainer	OPTIONAL,
+	...,
+	traceSupportIndicator	[1]	NULL		OPTIONAL
+	}
+
+DeactivateTraceModeArg ::= SEQUENCE {
+	imsi			[0] IMSI		OPTIONAL,
+	traceReference	[1] TraceReference,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...,
+	traceReference2	[3] TraceReference2	OPTIONAL
+	}
+
+DeactivateTraceModeRes ::= SEQUENCE {
+	extensionContainer	[0] ExtensionContainer	OPTIONAL,
+	...}
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-SM-DataTypes.asn b/rrlp-ephemeris/asn1/MAP-SM-DataTypes.asn
new file mode 100644
index 0000000..0ef941f
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-SM-DataTypes.asn
@@ -0,0 +1,270 @@
+-- $Id: MAP-SM-DataTypes.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- 3GPP TS 29.002  V8.9.0 (2009-04)  
+-- 17.7.6	Short message data types
+
+MAP-SM-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SM-DataTypes (16) version11 (11)}
+
+DEFINITIONS
+
+IMPLICIT TAGS
+
+::=
+
+BEGIN
+
+EXPORTS
+	RoutingInfoForSM-Arg,
+	RoutingInfoForSM-Res,
+	MO-ForwardSM-Arg,
+	MO-ForwardSM-Res,
+	MT-ForwardSM-Arg,
+	MT-ForwardSM-Res,
+	ReportSM-DeliveryStatusArg,
+	ReportSM-DeliveryStatusRes,
+	AlertServiceCentreArg,
+	InformServiceCentreArg,
+	ReadyForSM-Arg, 
+	ReadyForSM-Res,
+	SM-DeliveryOutcome,
+	AlertReason,
+	Additional-Number,
+	MT-ForwardSM-VGCS-Arg,
+	MT-ForwardSM-VGCS-Res
+;
+
+IMPORTS
+	AddressString,
+	ISDN-AddressString,
+	SignalInfo,
+	IMSI,
+	LMSI,
+	ASCI-CallReference
+
+FROM MAP-CommonDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-CommonDataTypes (18) version11 (11)}
+
+	AbsentSubscriberDiagnosticSM
+FROM MAP-ER-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ER-DataTypes (17) version11 (11)}
+
+	ExtensionContainer
+FROM MAP-ExtensionDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+;
+
+
+RoutingInfoForSM-Arg ::= SEQUENCE {
+	msisdn		[0] ISDN-AddressString,
+	sm-RP-PRI		[1] BOOLEAN,
+	serviceCentreAddress	[2] AddressString,
+	extensionContainer	[6] ExtensionContainer	OPTIONAL,
+	... ,
+	gprsSupportIndicator	[7]	NULL		OPTIONAL,
+	-- gprsSupportIndicator is set only if the SMS-GMSC supports
+	-- receiving of two numbers from the HLR
+	sm-RP-MTI		[8] SM-RP-MTI	OPTIONAL,
+	sm-RP-SMEA	[9] SM-RP-SMEA	OPTIONAL,
+	sm-deliveryNotIntended	[10] SM-DeliveryNotIntended	OPTIONAL }
+
+SM-DeliveryNotIntended ::= ENUMERATED {
+	onlyIMSI-requested  (0),
+	onlyMCC-MNC-requested  (1),
+	...}
+
+SM-RP-MTI ::= INTEGER (0..10)
+	-- 0 SMS Deliver 
+	-- 1 SMS Status Report
+	-- other values are reserved for future use and shall be discarded if
+	-- received
+
+SM-RP-SMEA ::= OCTET STRING (SIZE (1..12))
+	-- this parameter contains an address field which is encoded 
+	-- as defined in 3GPP TS 23.040. An address field contains 3 elements :
+	-- 		address-length
+	-- 		type-of-address
+	-- 		address-value
+
+RoutingInfoForSM-Res ::= SEQUENCE {
+	imsi			IMSI,
+	locationInfoWithLMSI	[0] LocationInfoWithLMSI,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	...}
+
+LocationInfoWithLMSI ::= SEQUENCE {
+	networkNode-Number	[1] ISDN-AddressString,
+	lmsi			LMSI			OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	gprsNodeIndicator	[5]	NULL		OPTIONAL,
+	-- gprsNodeIndicator is set only if the SGSN number is sent as the 
+	-- Network Node Number
+	additional-Number	[6] Additional-Number	OPTIONAL 
+	-- NetworkNode-number can be either msc-number or sgsn-number or IP-SM-GW
+	-- number or SMS Router number
+	}
+
+Additional-Number ::= CHOICE {
+	msc-Number	[0] ISDN-AddressString,
+	sgsn-Number	[1] ISDN-AddressString}
+	-- additional-number can be either msc-number or sgsn-number
+	-- if received networkNode-number is msc-number then the 	
+	-- additional number is sgsn-number 
+	-- if received networkNode-number is sgsn-number then the 
+	-- additional number is msc-number 
+
+MO-ForwardSM-Arg ::= SEQUENCE {
+	sm-RP-DA		SM-RP-DA,
+	sm-RP-OA		SM-RP-OA,
+	sm-RP-UI		SignalInfo,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	imsi			IMSI 		OPTIONAL }
+
+MO-ForwardSM-Res ::= SEQUENCE {
+	sm-RP-UI		SignalInfo 	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+MT-ForwardSM-Arg ::= SEQUENCE {
+	sm-RP-DA		SM-RP-DA,
+	sm-RP-OA		SM-RP-OA,
+	sm-RP-UI		SignalInfo,
+	moreMessagesToSend	NULL			OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+MT-ForwardSM-Res ::= SEQUENCE {
+	sm-RP-UI		SignalInfo	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+SM-RP-DA ::= CHOICE {
+	imsi			[0] IMSI,
+	lmsi			[1] LMSI,
+	serviceCentreAddressDA	[4] AddressString,
+	noSM-RP-DA	[5] NULL}
+
+SM-RP-OA ::= CHOICE {
+	msisdn		[2] ISDN-AddressString,
+	serviceCentreAddressOA	[4] AddressString,
+	noSM-RP-OA	[5] NULL}
+
+ReportSM-DeliveryStatusArg ::= SEQUENCE {
+	msisdn		ISDN-AddressString,
+	serviceCentreAddress	AddressString,
+	sm-DeliveryOutcome	SM-DeliveryOutcome,
+	absentSubscriberDiagnosticSM	[0] AbsentSubscriberDiagnosticSM
+							OPTIONAL,
+	extensionContainer	[1] ExtensionContainer	OPTIONAL,
+	...,
+	gprsSupportIndicator	[2]	NULL		OPTIONAL,
+	-- gprsSupportIndicator is set only if the SMS-GMSC supports 
+	-- handling of two delivery outcomes
+	deliveryOutcomeIndicator	[3] 	NULL		OPTIONAL,
+	-- DeliveryOutcomeIndicator is set when the SM-DeliveryOutcome
+	-- is for GPRS
+	additionalSM-DeliveryOutcome	[4] 	SM-DeliveryOutcome 	OPTIONAL,
+	-- If received, additionalSM-DeliveryOutcome is for GPRS
+	-- If DeliveryOutcomeIndicator is set, then AdditionalSM-DeliveryOutcome shall be absent
+	additionalAbsentSubscriberDiagnosticSM	[5] 	AbsentSubscriberDiagnosticSM OPTIONAL,
+	-- If received additionalAbsentSubscriberDiagnosticSM is for GPRS
+	-- If DeliveryOutcomeIndicator is set, then AdditionalAbsentSubscriberDiagnosticSM 
+	-- shall be absent
+	ip-sm-gw-Indicator	[6] 	NULL		OPTIONAL,
+	-- the ip-sm-gw indicator indicates by its presence that sm-deliveryOutcome
+	-- is for delivery via IMS
+	-- If present, deliveryOutcomeIndicator shall be absent.
+	ip-sm-gw-sm-deliveryOutcome	[7] 	SM-DeliveryOutcome	OPTIONAL, 
+	-- If received ip-sm-gw-sm-deliveryOutcome is for delivery via IMS
+	-- If ip-sm-gw-Indicator is set, then ip-sm-gw-sm-deliveryOutcome shall be absent
+	ip-sm-gw-absentSubscriberDiagnosticSM	[8]	AbsentSubscriberDiagnosticSM	OPTIONAL
+	-- If received ip-sm-gw-sm-absentSubscriberDiagnosticSM is for delivery via IMS
+	-- If ip-sm-gw-Indicator is set, then ip-sm-gw-sm-absentSubscriberDiagnosticSM 
+	-- shall be absent
+}
+
+SM-DeliveryOutcome ::= ENUMERATED {
+	memoryCapacityExceeded  (0),
+	absentSubscriber  (1),
+	successfulTransfer  (2)}
+
+ReportSM-DeliveryStatusRes ::= SEQUENCE {
+	storedMSISDN	ISDN-AddressString	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+AlertServiceCentreArg ::= SEQUENCE {
+	msisdn		ISDN-AddressString,
+	serviceCentreAddress	AddressString,
+	...}
+
+InformServiceCentreArg ::= SEQUENCE {
+	storedMSISDN	ISDN-AddressString	OPTIONAL,
+	mw-Status	MW-Status	OPTIONAL,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	... ,
+	absentSubscriberDiagnosticSM	AbsentSubscriberDiagnosticSM	OPTIONAL,
+	additionalAbsentSubscriberDiagnosticSM	[0]	AbsentSubscriberDiagnosticSM	OPTIONAL }
+	-- additionalAbsentSubscriberDiagnosticSM may be present only if 
+	-- absentSubscriberDiagnosticSM is present.
+	-- if included, additionalAbsentSubscriberDiagnosticSM is for GPRS and
+	-- absentSubscriberDiagnosticSM is for non-GPRS
+
+MW-Status ::= BIT STRING {
+	sc-AddressNotIncluded  (0),
+	mnrf-Set  (1),
+	mcef-Set  (2) ,
+	mnrg-Set	  (3)} (SIZE (6..16))
+	-- exception handling:
+	-- bits 4 to 15 shall be ignored if received and not understood
+
+ReadyForSM-Arg ::= SEQUENCE {
+	imsi			[0] IMSI,
+	alertReason	AlertReason,
+	alertReasonIndicator	NULL			OPTIONAL,
+	-- alertReasonIndicator is set only when the alertReason 
+	-- sent to HLR is for GPRS
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...,
+	additionalAlertReasonIndicator	[1] NULL		OPTIONAL
+	-- additionalAlertReasonIndicator is set only when the alertReason
+	-- sent to HLR is for IP-SM-GW
+	}
+
+ReadyForSM-Res ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+AlertReason ::= ENUMERATED {
+	ms-Present  (0),
+	memoryAvailable  (1)}
+
+MT-ForwardSM-VGCS-Arg ::= SEQUENCE {
+	asciCallReference	ASCI-CallReference,
+	sm-RP-OA		SM-RP-OA,
+	sm-RP-UI		SignalInfo,
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...}
+
+MT-ForwardSM-VGCS-Res ::= SEQUENCE {
+	sm-RP-UI		[0] SignalInfo	OPTIONAL,
+	dispatcherList	[1] DispatcherList	OPTIONAL,
+	ongoingCall	NULL			OPTIONAL,
+	extensionContainer	[2] ExtensionContainer	OPTIONAL,
+	...}
+
+DispatcherList ::= 
+	SEQUENCE SIZE (1..maxNumOfDispatchers) OF
+				ISDN-AddressString
+
+maxNumOfDispatchers  INTEGER ::= 5
+
+
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-SS-Code.asn b/rrlp-ephemeris/asn1/MAP-SS-Code.asn
new file mode 100644
index 0000000..163f2dc
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-SS-Code.asn
@@ -0,0 +1,190 @@
+-- $Id: MAP-SS-Code.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- 3GPP TS 29.002 V8.9.0 (2009-04)
+-- 17.7.5	Supplementary service codes
+
+MAP-SS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SS-Code (15) version11 (11)}
+
+DEFINITIONS
+
+::=
+
+BEGIN
+
+SS-Code ::= OCTET STRING (SIZE (1))
+	-- This type is used to represent the code identifying a single
+	-- supplementary service, a group of supplementary services, or
+	-- all supplementary services. The services and abbreviations
+	-- used are defined in TS 3GPP TS 22.004 [5]. The internal structure is
+	-- defined as follows:
+	--
+	-- bits 87654321: group (bits 8765), and specific service
+	-- (bits 4321)
+
+allSS			SS-Code ::= '00000000'B
+	-- reserved for possible future use
+	-- all SS
+
+allLineIdentificationSS	SS-Code ::= '00010000'B
+	-- reserved for possible future use
+	-- all line identification SS
+clip				SS-Code ::= '00010001'B
+	-- calling line identification presentation
+clir				SS-Code ::= '00010010'B
+	-- calling line identification restriction
+colp				SS-Code ::= '00010011'B
+	-- connected line identification presentation
+colr				SS-Code ::= '00010100'B
+	-- connected line identification restriction
+mci				SS-Code ::= '00010101'B
+	-- reserved for possible future use
+	-- malicious call identification
+
+allNameIdentificationSS	SS-Code ::= '00011000'B
+	-- all name identification SS
+cnap				SS-Code ::= '00011001'B
+	-- calling name presentation
+
+	-- SS-Codes '00011010'B to '00011111'B are reserved for future 
+	-- NameIdentification Supplementary Service use.
+
+allForwardingSS	SS-Code ::= '00100000'B
+	-- all forwarding SS
+cfu				SS-Code ::= '00100001'B
+	-- call forwarding unconditional
+allCondForwardingSS	SS-Code ::= '00101000'B
+	-- all conditional forwarding SS
+cfb				SS-Code ::= '00101001'B
+	-- call forwarding on mobile subscriber busy
+cfnry			SS-Code ::= '00101010'B
+	-- call forwarding on no reply
+cfnrc			SS-Code ::= '00101011'B
+	-- call forwarding on mobile subscriber not reachable 
+cd				SS-Code ::= '00100100'B
+	-- call deflection
+
+allCallOfferingSS	SS-Code ::= '00110000'B
+	-- reserved for possible future use
+	-- all call offering SS includes also all forwarding SS
+ect				SS-Code ::= '00110001'B
+		-- explicit call transfer
+mah				SS-Code ::= '00110010'B
+	-- reserved for possible future use
+	-- mobile access hunting
+
+allCallCompletionSS	SS-Code ::= '01000000'B
+	-- reserved for possible future use
+	-- all Call completion SS
+cw				SS-Code ::= '01000001'B
+	-- call waiting
+hold				SS-Code ::= '01000010'B
+	-- call hold
+ccbs-A			SS-Code ::= '01000011'B
+	-- completion of call to busy subscribers, originating side
+	-- this SS-Code is used only in InsertSubscriberData, DeleteSubscriberData 
+	-- and InterrogateSS
+ccbs-B			SS-Code ::= '01000100'B
+	-- completion of call to busy subscribers, destination side
+	-- this SS-Code is used only in InsertSubscriberData and DeleteSubscriberData
+mc				SS-Code ::= '01000101'B
+	-- multicall
+
+allMultiPartySS	SS-Code ::= '01010000'B
+	-- reserved for possible future use
+	-- all multiparty SS
+multiPTY			SS-Code ::= '01010001'B
+	-- multiparty
+
+allCommunityOfInterest-SS	SS-Code ::= '01100000'B
+	-- reserved for possible future use
+	-- all community of interest SS
+cug				SS-Code ::= '01100001'B
+	-- closed user group
+
+allChargingSS		SS-Code ::= '01110000'B
+	-- reserved for possible future use
+	-- all charging SS
+aoci				SS-Code ::= '01110001'B
+	-- advice of charge information
+aocc				SS-Code ::= '01110010'B
+	-- advice of charge charging
+
+allAdditionalInfoTransferSS	SS-Code ::= '10000000'B
+	-- reserved for possible future use
+	-- all additional information transfer SS
+uus1				SS-Code ::= '10000001'B
+	-- UUS1 user-to-user signalling 
+uus2				SS-Code ::= '10000010'B
+	-- UUS2 user-to-user signalling
+uus3				SS-Code ::= '10000011'B
+	-- UUS3 user-to-user signalling
+
+allBarringSS		SS-Code ::= '10010000'B
+	-- all barring SS
+barringOfOutgoingCalls	SS-Code ::= '10010001'B
+	-- barring of outgoing calls
+baoc				SS-Code ::= '10010010'B
+	-- barring of all outgoing calls
+boic				SS-Code ::= '10010011'B
+	-- barring of outgoing international calls
+boicExHC			SS-Code ::= '10010100'B
+	-- barring of outgoing international calls except those directed
+	-- to the home PLMN Country
+barringOfIncomingCalls	SS-Code ::= '10011001'B
+	-- barring of incoming calls
+baic				SS-Code ::= '10011010'B
+	-- barring of all incoming calls
+bicRoam			SS-Code ::= '10011011'B
+	-- barring of incoming calls when roaming outside home PLMN
+	-- Country
+
+allPLMN-specificSS	SS-Code ::= '11110000'B
+plmn-specificSS-1	SS-Code ::= '11110001'B
+plmn-specificSS-2	SS-Code ::= '11110010'B
+plmn-specificSS-3	SS-Code ::= '11110011'B
+plmn-specificSS-4	SS-Code ::= '11110100'B
+plmn-specificSS-5	SS-Code ::= '11110101'B
+plmn-specificSS-6	SS-Code ::= '11110110'B
+plmn-specificSS-7	SS-Code ::= '11110111'B
+plmn-specificSS-8	SS-Code ::= '11111000'B
+plmn-specificSS-9	SS-Code ::= '11111001'B
+plmn-specificSS-A	SS-Code ::= '11111010'B
+plmn-specificSS-B	SS-Code ::= '11111011'B
+plmn-specificSS-C	SS-Code ::= '11111100'B
+plmn-specificSS-D	SS-Code ::= '11111101'B
+plmn-specificSS-E	SS-Code ::= '11111110'B
+plmn-specificSS-F	SS-Code ::= '11111111'B
+
+allCallPrioritySS	SS-Code ::= '10100000'B
+	-- reserved for possible future use
+	-- all call priority SS
+emlpp			SS-Code ::= '10100001'B
+	-- enhanced Multilevel Precedence Pre-emption (EMLPP) service
+
+allLCSPrivacyException	SS-Code ::= '10110000'B
+	-- all LCS Privacy Exception Classes
+universal			SS-Code ::= '10110001'B
+	-- allow location by any LCS client
+callSessionRelated	SS-Code ::= '10110010'B
+	-- allow location by any value added LCS client to which a call/session 
+	-- is established from the target MS
+callSessionUnrelated	SS-Code ::= '10110011'B
+	-- allow location by designated external value added LCS clients
+plmnoperator		SS-Code ::= '10110100'B
+	-- allow location by designated PLMN operator LCS clients 
+serviceType		SS-Code ::= '10110101'B
+	-- allow location by LCS clients of a designated LCS service type
+
+allMOLR-SS		SS-Code ::= '11000000'B
+	-- all Mobile Originating Location Request Classes
+basicSelfLocation	SS-Code ::= '11000001'B
+	-- allow an MS to request its own location
+autonomousSelfLocation	SS-Code ::= '11000010'B
+	-- allow an MS to perform self location without interaction
+	-- with the PLMN for a predetermined period of time
+transferToThirdParty	SS-Code ::= '11000011'B
+	-- allow an MS to request transfer of its location to another LCS client
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-SS-DataTypes.asn b/rrlp-ephemeris/asn1/MAP-SS-DataTypes.asn
new file mode 100644
index 0000000..253f7f0
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-SS-DataTypes.asn
@@ -0,0 +1,342 @@
+-- $Id: MAP-SS-DataTypes.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- 3GPP TS 29.002 V8.9.0 (2009-04)  
+-- 17.7.4	Supplementary service data types
+ 
+MAP-SS-DataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SS-DataTypes (14) version11 (11)}
+
+DEFINITIONS
+
+IMPLICIT TAGS
+
+::=
+
+BEGIN
+
+EXPORTS
+	RegisterSS-Arg,
+	SS-Info,
+	SS-Status,
+	SS-SubscriptionOption,
+	SS-ForBS-Code,
+	InterrogateSS-Res,
+	USSD-Arg,
+	USSD-Res, 
+	USSD-DataCodingScheme,
+	USSD-String,
+	Password,
+	GuidanceInfo,
+	SS-List,
+	SS-InfoList,
+	OverrideCategory,
+	CliRestrictionOption,
+	NoReplyConditionTime,
+	ForwardingOptions,
+	maxNumOfSS,
+	SS-Data,
+	SS-InvocationNotificationArg,
+	SS-InvocationNotificationRes,
+	CCBS-Feature,
+	RegisterCC-EntryArg,
+	RegisterCC-EntryRes,
+	EraseCC-EntryArg,
+	EraseCC-EntryRes
+;
+
+IMPORTS
+	AddressString,
+	ISDN-AddressString,
+	ISDN-SubaddressString,
+	FTN-AddressString,
+	IMSI,
+	BasicServiceCode,
+	AlertingPattern,
+	EMLPP-Priority, 
+	MaxMC-Bearers,
+	MC-Bearers,
+	ExternalSignalInfo
+FROM MAP-CommonDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-CommonDataTypes (18) version11 (11)}
+
+	ExtensionContainer
+FROM MAP-ExtensionDataTypes {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+
+	SS-Code
+FROM MAP-SS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-SS-Code (15) version11 (11)}
+;
+
+
+RegisterSS-Arg ::= SEQUENCE {
+	ss-Code		SS-Code,
+	basicService	BasicServiceCode	OPTIONAL,
+	forwardedToNumber	[4] AddressString	OPTIONAL,
+	forwardedToSubaddress	[6] ISDN-SubaddressString	OPTIONAL,
+	noReplyConditionTime	[5] NoReplyConditionTime	OPTIONAL,
+	...,
+	defaultPriority	[7] EMLPP-Priority	OPTIONAL,
+	nbrUser		[8] MC-Bearers	OPTIONAL,
+	longFTN-Supported	[9]	NULL		OPTIONAL }
+
+NoReplyConditionTime ::= INTEGER (5..30)
+
+SS-Info ::= CHOICE {
+	forwardingInfo	[0] ForwardingInfo,
+	callBarringInfo	[1] CallBarringInfo,
+	ss-Data		[3] SS-Data}
+
+ForwardingInfo ::= SEQUENCE {
+	ss-Code		SS-Code		OPTIONAL,
+	forwardingFeatureList	ForwardingFeatureList,
+	...}
+
+ForwardingFeatureList ::= 
+	SEQUENCE SIZE (1..maxNumOfBasicServiceGroups) OF
+				ForwardingFeature
+
+ForwardingFeature ::= SEQUENCE {
+	basicService	BasicServiceCode	OPTIONAL,
+	ss-Status		[4] SS-Status	OPTIONAL,
+	forwardedToNumber	[5] ISDN-AddressString	OPTIONAL,
+	forwardedToSubaddress	[8] ISDN-SubaddressString	OPTIONAL,
+	forwardingOptions	[6] ForwardingOptions	OPTIONAL,
+	noReplyConditionTime	[7] NoReplyConditionTime	OPTIONAL,
+	...,
+	longForwardedToNumber	[9] FTN-AddressString	OPTIONAL }
+
+SS-Status ::= OCTET STRING (SIZE (1))
+
+	-- bits 8765: 0000 (unused)
+	-- bits 4321: Used to convey the "P bit","R bit","A bit" and "Q bit",
+	--		    representing supplementary service state information
+	--		    as defined in TS 3GPP TS 23.011 [22]
+
+	-- bit 4: "Q bit"
+
+	-- bit 3: "P bit"
+
+	-- bit 2: "R bit"
+
+	-- bit 1: "A bit"
+
+ForwardingOptions ::= OCTET STRING (SIZE (1))
+
+	-- bit 8: notification to forwarding party
+	--	0  no notification
+	--	1  notification
+
+	-- bit 7: redirecting presentation
+	--	0 no presentation  
+	--	1  presentation
+
+	-- bit 6: notification to calling party
+	--	0  no notification
+	--	1  notification
+
+	-- bit 5: 0 (unused)
+
+	-- bits 43: forwarding reason
+	--	00  ms not reachable
+	--	01  ms busy
+	--	10  no reply
+	--	11  unconditional when used in a SRI Result, 
+	--	    or call deflection when used in a RCH Argument
+	-- bits 21: 00 (unused)
+
+CallBarringInfo ::= SEQUENCE {
+	ss-Code		SS-Code		OPTIONAL,
+	callBarringFeatureList	CallBarringFeatureList,
+	...}
+
+CallBarringFeatureList ::= SEQUENCE SIZE (1..maxNumOfBasicServiceGroups) OF
+				CallBarringFeature
+
+CallBarringFeature ::= SEQUENCE {
+	basicService	BasicServiceCode	OPTIONAL,
+	ss-Status	[4] SS-Status	OPTIONAL,
+	...}
+
+SS-Data ::= SEQUENCE {
+	ss-Code		SS-Code		OPTIONAL,
+	ss-Status		[4] SS-Status	OPTIONAL,
+	ss-SubscriptionOption	SS-SubscriptionOption	OPTIONAL,
+	basicServiceGroupList	BasicServiceGroupList	OPTIONAL,
+	...,
+	defaultPriority	EMLPP-Priority	OPTIONAL,
+	nbrUser		[5] MC-Bearers	OPTIONAL
+	}
+
+SS-SubscriptionOption ::= CHOICE {
+	cliRestrictionOption	[2] CliRestrictionOption,
+	overrideCategory	[1] OverrideCategory}
+
+CliRestrictionOption ::= ENUMERATED {
+	permanent  (0),
+	temporaryDefaultRestricted  (1),
+	temporaryDefaultAllowed  (2)}
+
+OverrideCategory ::= ENUMERATED {
+	overrideEnabled  (0),
+	overrideDisabled  (1)}
+
+SS-ForBS-Code ::= SEQUENCE {
+	ss-Code		SS-Code,
+	basicService	BasicServiceCode	OPTIONAL,
+	...,
+	longFTN-Supported	[4]	NULL		OPTIONAL }
+
+GenericServiceInfo ::= SEQUENCE {
+	ss-Status	SS-Status,
+	cliRestrictionOption	CliRestrictionOption	OPTIONAL,
+	...,
+	maximumEntitledPriority	[0] EMLPP-Priority	OPTIONAL,
+	defaultPriority	[1] EMLPP-Priority	OPTIONAL,
+	ccbs-FeatureList	[2] CCBS-FeatureList	OPTIONAL,
+	nbrSB		[3] MaxMC-Bearers	OPTIONAL,
+	nbrUser		[4] MC-Bearers	OPTIONAL,
+	nbrSN		[5] MC-Bearers	OPTIONAL }
+
+CCBS-FeatureList ::= SEQUENCE SIZE (1..maxNumOfCCBS-Requests) OF
+				CCBS-Feature
+
+maxNumOfCCBS-Requests  INTEGER ::= 5
+
+CCBS-Feature ::= SEQUENCE {
+	ccbs-Index	[0] CCBS-Index	OPTIONAL,
+	b-subscriberNumber	[1] ISDN-AddressString	OPTIONAL,
+	b-subscriberSubaddress	[2] ISDN-SubaddressString	OPTIONAL,
+	basicServiceGroup	[3] BasicServiceCode	OPTIONAL,
+	...}
+
+CCBS-Index  ::= INTEGER (1..maxNumOfCCBS-Requests)
+
+InterrogateSS-Res ::= CHOICE {
+	ss-Status		[0] SS-Status,
+	basicServiceGroupList	[2] BasicServiceGroupList,
+	forwardingFeatureList	[3] ForwardingFeatureList,
+	genericServiceInfo	[4]	GenericServiceInfo }
+
+USSD-Arg ::= SEQUENCE {
+	ussd-DataCodingScheme	USSD-DataCodingScheme,
+	ussd-String	USSD-String,
+	... ,
+	alertingPattern	AlertingPattern	OPTIONAL,
+	msisdn		[0] ISDN-AddressString	OPTIONAL }
+
+USSD-Res ::= SEQUENCE {
+	ussd-DataCodingScheme	USSD-DataCodingScheme,
+	ussd-String	USSD-String,
+	...}
+
+USSD-DataCodingScheme ::= OCTET STRING (SIZE (1))
+	-- The structure of the USSD-DataCodingScheme is defined by
+	-- the Cell Broadcast Data Coding Scheme as described in
+	-- TS 3GPP TS 23.038 [25]
+
+USSD-String ::= OCTET STRING (SIZE (1..maxUSSD-StringLength))
+	-- The structure of the contents of the USSD-String is dependent
+	-- on the USSD-DataCodingScheme as described in TS 3GPP TS 23.038 [25].
+
+maxUSSD-StringLength  INTEGER ::= 160
+
+Password ::= NumericString
+	(FROM ("0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"))
+	(SIZE (4))
+
+GuidanceInfo ::= ENUMERATED {
+	enterPW  (0),
+	enterNewPW  (1),
+	enterNewPW-Again  (2)}
+	-- How this information is really delivered to the subscriber
+	-- (display, announcement, ...) is not part of this
+	-- specification.
+
+SS-List ::= SEQUENCE SIZE (1..maxNumOfSS) OF
+				SS-Code
+
+maxNumOfSS  INTEGER ::= 30
+
+SS-InfoList ::= SEQUENCE SIZE (1..maxNumOfSS) OF
+				SS-Info
+
+BasicServiceGroupList ::= SEQUENCE SIZE (1..maxNumOfBasicServiceGroups) OF
+				BasicServiceCode
+
+maxNumOfBasicServiceGroups  INTEGER ::= 13
+
+SS-InvocationNotificationArg ::= SEQUENCE {
+	imsi			[0] IMSI,
+	msisdn		[1] ISDN-AddressString,
+	ss-Event		[2] SS-Code,
+	-- The following SS-Code values are allowed :
+	-- ect		SS-Code ::= '00110001'B
+	-- multiPTY	SS-Code ::= '01010001'B
+	-- cd		SS-Code ::= '00100100'B
+	-- ccbs		SS-Code ::= '01000100'B
+	ss-EventSpecification	[3] SS-EventSpecification	OPTIONAL,
+	extensionContainer	[4] ExtensionContainer	OPTIONAL,
+	...,
+	b-subscriberNumber	[5]	ISDN-AddressString	OPTIONAL,
+	ccbs-RequestState	[6]	CCBS-RequestState	OPTIONAL
+	}
+
+CCBS-RequestState ::= ENUMERATED {
+	request  	(0),
+	recall  	(1),
+	active  	(2),
+	completed	(3),
+	suspended	(4),
+	frozen	(5),
+	deleted	(6)
+	}
+
+SS-InvocationNotificationRes ::= SEQUENCE {
+	extensionContainer	ExtensionContainer	OPTIONAL,
+	...
+	}
+
+SS-EventSpecification ::= SEQUENCE SIZE (1..maxEventSpecification) OF
+				AddressString
+
+maxEventSpecification  INTEGER ::= 2
+
+RegisterCC-EntryArg ::= SEQUENCE {
+	ss-Code		[0]	SS-Code,
+	ccbs-Data		[1]	CCBS-Data	OPTIONAL,
+	...}
+
+CCBS-Data ::= SEQUENCE {
+	ccbs-Feature	[0]	CCBS-Feature,
+	translatedB-Number	[1]	ISDN-AddressString,
+	serviceIndicator	[2]	ServiceIndicator	OPTIONAL,
+	callInfo		[3]	ExternalSignalInfo,
+	networkSignalInfo	[4]	ExternalSignalInfo,
+	...}
+
+ServiceIndicator ::= BIT STRING {
+	clir-invoked (0),
+	camel-invoked (1)} (SIZE(2..32)) 
+	-- exception handling:
+	-- bits 2 to 31 shall be ignored if received and not understood
+
+RegisterCC-EntryRes ::= SEQUENCE {
+	ccbs-Feature	[0] CCBS-Feature	OPTIONAL,
+	...}
+
+EraseCC-EntryArg ::= SEQUENCE {
+	ss-Code		[0]	SS-Code,
+	ccbs-Index	[1]	CCBS-Index	OPTIONAL,
+	...}
+
+EraseCC-EntryRes ::= SEQUENCE {
+	ss-Code		[0]	SS-Code,
+	ss-Status		[1] SS-Status	OPTIONAL,
+	...}
+
+END
+
diff --git a/rrlp-ephemeris/asn1/MAP-TS-Code.asn b/rrlp-ephemeris/asn1/MAP-TS-Code.asn
new file mode 100644
index 0000000..5ac00bf
--- /dev/null
+++ b/rrlp-ephemeris/asn1/MAP-TS-Code.asn
@@ -0,0 +1,92 @@
+-- $Id: MAP-TS-Code.asn 28149 2009-04-25 17:45:34Z etxrab $
+-- 3GPP TS 29.002 V8.9.0 (2009-04) 
+-- 17.7.9	Teleservice Codes
+ 
+MAP-TS-Code {
+   itu-t identified-organization (4) etsi (0) mobileDomain (0)
+   gsm-Network (1) modules (3) map-TS-Code (19) version11 (11)}
+
+DEFINITIONS
+
+::=
+
+BEGIN
+
+TeleserviceCode ::= OCTET STRING (SIZE (1))
+	-- This type is used to represent the code identifying a single
+	-- teleservice, a group of teleservices, or all teleservices. The
+	-- services are defined in TS GSM 22.003 [4].
+	-- The internal structure is defined as follows:
+
+	-- bits 87654321: group (bits 8765) and specific service
+	-- (bits 4321)
+
+Ext-TeleserviceCode ::= OCTET STRING (SIZE (1..5))
+	-- This type is used to represent the code identifying a single
+	-- teleservice, a group of teleservices, or all teleservices. The
+	-- services are defined in TS GSM 22.003 [4].
+	-- The internal structure is defined as follows:
+
+	-- OCTET 1:
+	-- bits 87654321: group (bits 8765) and specific service
+	-- (bits 4321)
+
+	-- OCTETS 2-5: reserved for future use. If received the
+    -- Ext-TeleserviceCode shall be
+ 	-- treated according to the exception handling defined for the
+	-- operation that uses this type.
+
+	-- Ext-TeleserviceCode includes all values defined for TeleserviceCode.
+
+allTeleservices	TeleserviceCode ::= '00000000'B
+
+allSpeechTransmissionServices	TeleserviceCode ::= '00010000'B
+telephony			TeleserviceCode ::= '00010001'B
+emergencyCalls		TeleserviceCode ::= '00010010'B
+
+allShortMessageServices	TeleserviceCode ::= '00100000'B
+shortMessageMT-PP	TeleserviceCode ::= '00100001'B
+shortMessageMO-PP	TeleserviceCode ::= '00100010'B
+
+allFacsimileTransmissionServices	TeleserviceCode ::= '01100000'B
+facsimileGroup3AndAlterSpeech	TeleserviceCode ::= '01100001'B
+automaticFacsimileGroup3	TeleserviceCode ::= '01100010'B
+facsimileGroup4	TeleserviceCode ::= '01100011'B
+
+-- The following non-hierarchical Compound Teleservice Groups
+-- are defined in TS 3GPP TS 22.030:
+allDataTeleservices	TeleserviceCode ::= '01110000'B
+	-- covers Teleservice Groups 'allFacsimileTransmissionServices'
+	-- and 'allShortMessageServices'
+allTeleservices-ExeptSMS	TeleserviceCode ::= '10000000'B
+	-- covers Teleservice Groups 'allSpeechTransmissionServices' and
+	-- 'allFacsimileTransmissionServices'
+--
+-- Compound Teleservice Group Codes are only used in call
+-- independent supplementary service operations, i.e. they
+-- are not used in InsertSubscriberData or in
+-- DeleteSubscriberData messages.
+
+allVoiceGroupCallServices	TeleserviceCode ::= '10010000'B
+voiceGroupCall		TeleserviceCode ::= '10010001'B
+voiceBroadcastCall	TeleserviceCode ::= '10010010'B
+
+allPLMN-specificTS	TeleserviceCode ::= '11010000'B
+plmn-specificTS-1	TeleserviceCode ::= '11010001'B
+plmn-specificTS-2	TeleserviceCode ::= '11010010'B
+plmn-specificTS-3	TeleserviceCode ::= '11010011'B
+plmn-specificTS-4	TeleserviceCode ::= '11010100'B
+plmn-specificTS-5	TeleserviceCode ::= '11010101'B
+plmn-specificTS-6	TeleserviceCode ::= '11010110'B
+plmn-specificTS-7	TeleserviceCode ::= '11010111'B
+plmn-specificTS-8	TeleserviceCode ::= '11011000'B
+plmn-specificTS-9	TeleserviceCode ::= '11011001'B
+plmn-specificTS-A	TeleserviceCode ::= '11011010'B
+plmn-specificTS-B	TeleserviceCode ::= '11011011'B
+plmn-specificTS-C	TeleserviceCode ::= '11011100'B
+plmn-specificTS-D	TeleserviceCode ::= '11011101'B
+plmn-specificTS-E	TeleserviceCode ::= '11011110'B
+plmn-specificTS-F	TeleserviceCode ::= '11011111'B
+
+END
+
diff --git a/rrlp-ephemeris/asn1/RRLP-Components.asn b/rrlp-ephemeris/asn1/RRLP-Components.asn
new file mode 100644
index 0000000..3bade6a
--- /dev/null
+++ b/rrlp-ephemeris/asn1/RRLP-Components.asn
@@ -0,0 +1,1488 @@
+-- RRLP-Components.asn
+-- $Id$
+-- Taken from 3GPP TS 44.031 V7.4.0 (2007-03)
+-- http://www.3gpp.org/ftp/Specs/archive/44_series/44.031/44031-740.zip/44031-740.doc
+--
+-- 4 Components
+-- 5 Elements of Components
+--
+
+RRLP-Components
+-- { RRLP-Components }
+
+DEFINITIONS AUTOMATIC TAGS ::=
+
+BEGIN
+
+IMPORTS
+	Ext-GeographicalInformation, VelocityEstimate
+FROM
+	MAP-LCS-DataTypes {
+	itu-t identified-organization (4) etsi (0) mobileDomain (0)
+	gsm-Network (1) modules (3) map-LCS-DataTypes (25) version11 (11)}
+
+	ExtensionContainer
+FROM MAP-ExtensionDataTypes {
+	itu-t identified-organization (4) etsi (0) mobileDomain (0)
+	gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+;
+
+
+-- Add here other ASN.1 definitions presented below
+-- in chapters 4 and 5.
+
+-- Measurement Position request component
+MsrPosition-Req ::= SEQUENCE {
+	positionInstruct		PositionInstruct,
+	referenceAssistData		ReferenceAssistData		OPTIONAL,
+	msrAssistData			MsrAssistData			OPTIONAL,
+	systemInfoAssistData	SystemInfoAssistData	OPTIONAL,
+	gps-AssistData			GPS-AssistData			OPTIONAL,
+	extensionContainer		ExtensionContainer		OPTIONAL,
+	...,
+	-- Release 98 extension element
+rel98-MsrPosition-Req-extension			Rel98-MsrPosition-Req-Extension			OPTIONAL,	
+	-- Release 5 extension element
+rel5-MsrPosition-Req-extension			Rel5-MsrPosition-Req-Extension			OPTIONAL,
+	-- Release 7 extension element
+rel7-MsrPosition-Req-extension			Rel7-MsrPosition-Req-Extension			OPTIONAL
+}
+
+-- Measurement Position response component
+MsrPosition-Rsp ::= SEQUENCE {
+	multipleSets			MultipleSets			OPTIONAL,
+	referenceIdentity		ReferenceIdentity		OPTIONAL,
+	otd-MeasureInfo			OTD-MeasureInfo			OPTIONAL,
+	locationInfo			LocationInfo			OPTIONAL,
+	gps-MeasureInfo			GPS-MeasureInfo			OPTIONAL,
+	locationError			LocationError			OPTIONAL,
+	extensionContainer		ExtensionContainer		OPTIONAL,
+	...,	
+	-- Release extension here
+	rel-98-MsrPosition-Rsp-Extension		
+							Rel-98-MsrPosition-Rsp-Extension		OPTIONAL,
+	rel-5-MsrPosition-Rsp-Extension
+							Rel-5-MsrPosition-Rsp-Extension		OPTIONAL,
+	-- rel-5-MsrPosition-Rsp-Extension and other possible future extensions
+	-- are the only information elements that may be included in the 2nd
+	-- MsrPosition-Rsp component when RRLP pseudo-segmentation is used
+	rel-7-MsrPosition-Rsp-Extension
+							Rel-7-MsrPosition-Rsp-Extension		OPTIONAL
+}
+
+-- Assistance Data component
+AssistanceData ::= SEQUENCE {
+	referenceAssistData	ReferenceAssistData			OPTIONAL,
+	msrAssistData			MsrAssistData			OPTIONAL,
+	systemInfoAssistData	SystemInfoAssistData	OPTIONAL,
+	gps-AssistData			GPS-AssistData			OPTIONAL,	
+	moreAssDataToBeSent		MoreAssDataToBeSent		OPTIONAL,	-- If not present, interpret as only
+																-- Assistance Data component used to
+																-- deliver entire set of assistance
+																-- data.
+	extensionContainer		ExtensionContainer		OPTIONAL,
+	...,
+	-- Release extension here
+	rel98-AssistanceData-Extension	Rel98-AssistanceData-Extension	OPTIONAL,
+	rel5-AssistanceData-Extension	Rel5-AssistanceData-Extension	OPTIONAL,
+ rel7-AssistanceData-Extension Rel7-AssistanceData-Extension OPTIONAL	
+}
+
+-- Protocol Error component
+ProtocolError ::= SEQUENCE {
+	errorCause				ErrorCodes,	
+	extensionContainer		ExtensionContainer		OPTIONAL,
+	...,
+	-- Release extensions here
+	rel-5-ProtocolError-Extension Rel-5-ProtocolError-Extension		OPTIONAL
+}
+
+-- Position instructions
+PositionInstruct ::= SEQUENCE {
+	-- Method type
+	methodType				MethodType,	
+	positionMethod			PositionMethod,
+	measureResponseTime		MeasureResponseTime,		
+	useMultipleSets			UseMultipleSets,		
+	environmentCharacter	EnvironmentCharacter	OPTIONAL	
+}
+
+--
+MethodType ::= CHOICE {
+	msAssisted		AccuracyOpt,	-- accuracy is optional
+	msBased			Accuracy,		-- accuracy is mandatory
+	msBasedPref		Accuracy,		-- accuracy is mandatory
+	msAssistedPref	Accuracy 		-- accuracy is mandatory
+}
+
+-- Accuracy of the location estimation
+AccuracyOpt ::= SEQUENCE {
+	accuracy		Accuracy	OPTIONAL
+}
+
+-- The values of this field are defined in 3GPP TS 23.032 (Uncertainty code)
+Accuracy ::= INTEGER (0..127)
+
+
+-- Position Method
+PositionMethod ::= ENUMERATED {
+	eotd (0),
+	gps (1),
+	gpsOrEOTD (2)
+}
+
+-- Measurement request response time
+MeasureResponseTime ::= INTEGER (0..7)
+
+-- useMultiple Sets, FFS!
+UseMultipleSets ::= ENUMERATED {
+	multipleSets (0),		-- multiple sets are allowed
+	oneSet (1)				-- sending of multiple is not allowed
+}
+
+-- Environment characterization
+EnvironmentCharacter ::= ENUMERATED {
+	badArea (0),		-- bad urban or suburban, heavy multipath and NLOS
+	notBadArea (1),		-- light multipath and NLOS
+	mixedArea (2),		-- not defined or mixed environment
+	...
+}
+
+-- E-OTD reference BTS for Assitance data IE
+ReferenceAssistData ::= SEQUENCE {
+	bcchCarrier		BCCHCarrier,						-- BCCH carrier
+	bsic			BSIC,								-- BSIC
+	timeSlotScheme	TimeSlotScheme, 					-- Timeslot scheme
+	btsPosition		BTSPosition			OPTIONAL
+}
+
+-- ellipsoid point and
+-- ellipsoid point with altitude and uncertainty ellipsoid shapes are supported
+BTSPosition ::= Ext-GeographicalInformation
+
+-- RF channel number of BCCH
+BCCHCarrier ::= INTEGER (0..1023)
+
+-- Base station Identity Code
+BSIC ::= INTEGER (0..63)
+
+-- Timeslot scheme
+TimeSlotScheme ::= ENUMERATED {
+	equalLength (0),
+	variousLength (1)
+}
+
+-- Time slot (modulo)
+ModuloTimeSlot ::= INTEGER (0..3)
+
+-- E-OTD measurement assistance data IE
+-- The total number of neighbors in this element (MsrAssistData)
+-- and in SystemInfoAssistData element (presented neighbors
+-- can be at a maximum 15!)
+MsrAssistData ::= SEQUENCE {
+	 msrAssistList SeqOfMsrAssistBTS
+}
+SeqOfMsrAssistBTS ::= SEQUENCE (SIZE(1..15)) OF MsrAssistBTS
+
+MsrAssistBTS ::= SEQUENCE {
+	bcchCarrier			BCCHCarrier,		-- BCCH carrier
+	bsic				BSIC,				-- BSIC
+	multiFrameOffset	MultiFrameOffset, 	-- multiframe offset
+	timeSlotScheme		TimeSlotScheme,		-- Timeslot scheme
+	roughRTD			RoughRTD,			-- rough RTD value
+
+	-- Location Calculation Assistance data is moved here
+	calcAssistanceBTS	CalcAssistanceBTS	OPTIONAL
+}
+
+-- Multiframe offset
+MultiFrameOffset ::= INTEGER (0..51)
+-- The Multiframe Offset value 51 shall not be encoded by the transmitting entity and
+-- shall be treated by the receiving entity as 0.
+
+-- Rough RTD value between one base station and reference BTS
+RoughRTD ::= INTEGER (0..1250)
+-- The RoughRTD value 1250 shall not be encoded by the transmitting entity and shall
+-- be treated by the receiving entity as 0.
+
+-- E-OTD Measurement assistance data for system information List IE
+-- The total number of base stations in this element (SystemInfoAssistData
+-- presented neighbors) and in MsrAssistData element can be at a maximum 15.
+SystemInfoAssistData ::= SEQUENCE {
+	systemInfoAssistList 	SeqOfSystemInfoAssistBTS
+}
+SeqOfSystemInfoAssistBTS::= SEQUENCE (SIZE(1..32)) OF SystemInfoAssistBTS
+
+-- whether n.th is present or not ?
+SystemInfoAssistBTS ::= CHOICE {
+	notPresent		NULL,
+	present			AssistBTSData
+}
+
+-- Actual assistance data for system information base station
+AssistBTSData ::= SEQUENCE {
+	bsic				BSIC,				-- BSIC
+	multiFrameOffset	MultiFrameOffset,	-- multiframe offset
+	timeSlotScheme		TimeSlotScheme,		-- Timeslot scheme
+	roughRTD			RoughRTD,			-- rough RTD value
+
+	-- Location Calculation Assistance data
+	calcAssistanceBTS	CalcAssistanceBTS	OPTIONAL
+}
+
+-- E-OTD Location calculation assistance data,
+-- CalcAssistanceBTS element is optional not subfields
+CalcAssistanceBTS ::= SEQUENCE {
+	fineRTD				FineRTD,		-- fine RTD value between base stations
+	referenceWGS84		ReferenceWGS84	-- reference coordinates
+}
+
+-- Coordinates of neighbour BTS, WGS-84 ellipsoid
+ReferenceWGS84 ::= SEQUENCE {
+	relativeNorth	RelDistance,				-- relative distance (south negative)
+	relativeEast	RelDistance,				-- relative distance (west negative)
+	-- Relative Altitude is not always known
+	relativeAlt		RelativeAlt		OPTIONAL	-- relative altitude
+}
+
+-- Fine RTD value between this BTS and the reference BTS	
+FineRTD ::= INTEGER (0..255)
+
+-- Relative north/east distance
+RelDistance ::= INTEGER (-200000..200000)
+
+-- Relative altitude
+RelativeAlt ::= INTEGER (-4000..4000)
+
+-- Measure position response IEs
+-- Reference Identity
+-- Multiple sets
+MultipleSets ::= SEQUENCE {
+	-- number of reference sets
+	nbrOfSets			INTEGER (2..3),		
+
+	-- This field actually tells the number of reference BTSs
+	nbrOfReferenceBTSs	INTEGER (1..3),
+
+	-- This field is conditional and included optionally only if
+	-- nbrOfSets is 3 and number of reference BTSs is 2.
+	referenceRelation		ReferenceRelation		OPTIONAL
+}
+
+-- Relation between refence BTSs and sets
+ReferenceRelation ::= ENUMERATED {
+	secondBTSThirdSet (0),	-- 1st BTS related to 1st and 2nd sets
+	secondBTSSecondSet (1),	-- 1st BTS related to 1st and 3rd sets
+	firstBTSFirstSet (2)	-- 1st BTS related to 1st set
+}
+
+-- Reference BTS Identity, this element contains number of
+-- BTSs told nbrOfReferenceBTSs field in Multiple sets element)
+ReferenceIdentity ::= SEQUENCE {
+	-- Reference BTS list
+	refBTSList		SeqOfReferenceIdentityType
+}
+SeqOfReferenceIdentityType ::= SEQUENCE (SIZE(1..3)) OF ReferenceIdentityType
+
+-- Cell identity
+ReferenceIdentityType ::= CHOICE {
+	bsicAndCarrier	BSICAndCarrier,		-- BSIC and Carrier
+	ci				CellID,				-- Cell ID, LAC not needed
+	requestIndex	RequestIndex,		-- Index to Requested Neighbor List
+	systemInfoIndex	SystemInfoIndex,	-- Index to System info list, this type of ref. identity
+										-- shall not be used by the MS unless it has received
+										-- the SystemInfoAssistData from the SMLC for this cell.
+	ciAndLAC		CellIDAndLAC		-- CI and LAC
+}
+
+BSICAndCarrier ::= SEQUENCE {
+	carrier	BCCHCarrier,
+	bsic		BSIC
+}
+
+RequestIndex ::= INTEGER (1..16)
+
+SystemInfoIndex ::= INTEGER (1..32)
+
+CellIDAndLAC ::= SEQUENCE {
+	referenceLAC	LACID,				-- Location area code
+	referenceCI		CellID				-- Cell identity
+}
+CellID ::= INTEGER (0..65535)
+LACID ::= INTEGER (0..65535)
+
+-- OTD-MeasureInfo
+OTD-MeasureInfo ::= SEQUENCE {
+	-- Measurement info elements, OTD-MsrElement is repeated number of times
+	-- told in nbrOfReferenceBTSs in MultipleSets, default value is 1
+	otdMsrFirstSets		OTD-MsrElementFirst,
+
+	-- if more than one sets are present this element is repeated
+	-- NumberOfSets - 1 (-1 = first set)
+	otdMsrRestSets		SeqOfOTD-MsrElementRest		OPTIONAL
+}
+
+SeqOfOTD-MsrElementRest ::= SEQUENCE (SIZE(1..2)) OF OTD-MsrElementRest
+
+-- OTD measurent information for 1 set
+OTD-MsrElementFirst ::= SEQUENCE {
+	refFrameNumber			INTEGER (0..42431), 				-- Frame number modulo 42432
+	referenceTimeSlot		ModuloTimeSlot,
+	toaMeasurementsOfRef	TOA-MeasurementsOfRef	OPTIONAL,
+	stdResolution			StdResolution,
+	taCorrection			INTEGER (0..960)		OPTIONAL,	-- TA correction
+
+	-- measured neighbors in OTD measurements
+	otd-FirstSetMsrs 		SeqOfOTD-FirstSetMsrs 	OPTIONAL
+}
+SeqOfOTD-FirstSetMsrs ::= SEQUENCE (SIZE(1..10)) OF OTD-FirstSetMsrs
+
+-- OTD measurent information 2 and 3 sets if exist
+OTD-MsrElementRest ::= SEQUENCE {
+	refFrameNumber			INTEGER (0..42431), 					-- Frame number modulo 42432
+	referenceTimeSlot		ModuloTimeSlot,
+	toaMeasurementsOfRef	TOA-MeasurementsOfRef		OPTIONAL,
+	stdResolution			StdResolution,
+	taCorrection			INTEGER (0..960)			OPTIONAL,	-- TA correction
+
+	-- measured neighbors in OTD measurements
+	otd-MsrsOfOtherSets 	SeqOfOTD-MsrsOfOtherSets	OPTIONAL
+}
+SeqOfOTD-MsrsOfOtherSets ::= SEQUENCE (SIZE(1..10)) OF OTD-MsrsOfOtherSets
+
+-- Standard deviation of the TOA measurements from the reference BTS
+TOA-MeasurementsOfRef ::= SEQUENCE {
+	refQuality			RefQuality,
+	numOfMeasurements	NumOfMeasurements
+}
+
+RefQuality ::= INTEGER (0..31)			-- St Dev of TOA of reference as defined in annex
+NumOfMeasurements ::= INTEGER (0..7)	-- No. of measurements for RefQuality as defined in annex
+StdResolution ::= INTEGER (0..3)		-- Values of resolution are defined in annex
+
+OTD-FirstSetMsrs ::= OTD-MeasurementWithID
+
+-- Neighbour info in OTD measurements 0-10 times in TD measurement info
+OTD-MsrsOfOtherSets ::= CHOICE {
+	identityNotPresent	OTD-Measurement,	
+	identityPresent		OTD-MeasurementWithID
+}
+
+-- For this OTD measurement identity is same as the identity of BTS
+-- in the first set with same sequence number
+OTD-Measurement ::= SEQUENCE {
+	nborTimeSlot	ModuloTimeSlot,
+	eotdQuality		EOTDQuality,
+	otdValue		OTDValue
+}
+
+-- This measurement contains the BTS identity and measurement
+OTD-MeasurementWithID ::=SEQUENCE {
+	neighborIdentity	NeighborIdentity,
+	nborTimeSlot		ModuloTimeSlot,
+	eotdQuality			EOTDQuality,
+	otdValue			OTDValue
+}
+
+EOTDQuality ::= SEQUENCE {
+	nbrOfMeasurements	INTEGER	(0..7),
+	stdOfEOTD			INTEGER (0..31)
+}
+
+NeighborIdentity ::= CHOICE {
+	bsicAndCarrier		BSICAndCarrier,		-- BSIC and Carrier
+	ci					CellID,				-- Cell ID, LAC not needed
+	multiFrameCarrier	MultiFrameCarrier, 	-- MultiFrameOffest and BSIC
+	requestIndex		RequestIndex,		-- Index to Requested Neighbor List
+	systemInfoIndex		SystemInfoIndex,	-- Index to System info list, this type of neighbour
+											-- identity shall not be used by the MS unless it has
+											-- received the SystemInfoAssistData from the SMLC for
+											-- this cell.
+	ciAndLAC			CellIDAndLAC		-- CI and LAC
+}
+
+-- Multiframe and carrier
+MultiFrameCarrier ::= SEQUENCE {
+	bcchCarrier			BCCHCarrier,
+	multiFrameOffset	MultiFrameOffset
+}
+
+-- OTD measurement value for neighbour
+OTDValue ::= INTEGER (0..39999)
+
+-- Location information IE
+LocationInfo ::= SEQUENCE {
+	refFrame		INTEGER (0..65535),			-- Reference Frame number
+	-- If refFrame is within (42432..65535), it shall be ignored by the receiver
+	-- in that case the MS should provide GPS TOW if available
+	gpsTOW			INTEGER (0..14399999)	OPTIONAL,	-- GPS TOW
+	fixType			FixType,
+	-- Note that applicable range for refFrame is 0 - 42431
+	-- Possible shapes carried in posEstimate are
+	-- ellipsoid point,
+	-- ellipsoid point with uncertainty circle
+	-- ellipsoid point with uncertainty ellipse
+	-- ellipsoid point with altitude and uncertainty ellipsoid
+	posEstimate		Ext-GeographicalInformation
+}
+
+FixType ::= INTEGER {
+	twoDFix (0),
+	threeDFix (1)
+} (0..1)
+
+-- GPS-Measurement information
+GPS-MeasureInfo ::= SEQUENCE {
+	-- Measurement info elements
+	-- user has to make sure that in this element is number of elements
+	-- defined in reference BTS identity
+	gpsMsrSetList	SeqOfGPS-MsrSetElement	
+}
+SeqOfGPS-MsrSetElement ::= SEQUENCE (SIZE(1..3)) OF GPS-MsrSetElement
+
+-- OTD measurent information 1-3 times in message
+GPS-MsrSetElement ::= SEQUENCE {
+	refFrame		INTEGER (0..65535)	OPTIONAL, 	-- Reference Frame number
+	gpsTOW			GPSTOW24b,						-- GPS TOW
+	-- Note that applicable range for refFrame is 0 - 42431
+
+--N_SAT can be read from number of elements of gps-msrList
+
+	gps-msrList		SeqOfGPS-MsrElement
+}
+
+-- 24 bit presentation for GPSTOW
+GPSTOW24b ::= INTEGER (0..14399999)
+
+-- measured elements in measurement parameters field
+SeqOfGPS-MsrElement ::= SEQUENCE (SIZE(1..16)) OF GPS-MsrElement
+
+GPS-MsrElement ::= SEQUENCE {
+	satelliteID		SatelliteID,				-- Satellite identifier
+	cNo				INTEGER (0..63),			-- carrier noise ratio
+	doppler			INTEGER (-32768..32767), 	-- doppler, mulltiply by 0.2
+	wholeChips		INTEGER (0..1022),			-- whole value of the code phase measurement
+	fracChips		INTEGER (0..1024),			-- fractional value of the code phase measurement
+											-- a value of 1024 shall not be encoded by the sender
+											-- the receiver shall consider a value of 1024 to be
+											-- invalid data
+	mpathIndic		MpathIndic,					-- multipath indicator
+	pseuRangeRMSErr	INTEGER (0..63)				-- index		
+}
+
+-- Multipath indicator
+MpathIndic ::= ENUMERATED {
+	notMeasured (0),
+	low (1),
+	medium (2),
+	high (3)
+}
+
+-- Location error IE
+LocationError ::= SEQUENCE {
+	locErrorReason				LocErrorReason,
+	additionalAssistanceData	AdditionalAssistanceData	OPTIONAL,
+	...
+}
+
+LocErrorReason ::= ENUMERATED {
+	unDefined (0),	
+	notEnoughBTSs (1),
+	notEnoughSats (2),
+	eotdLocCalAssDataMissing (3),
+	eotdAssDataMissing (4),
+	gpsLocCalAssDataMissing (5),
+	gpsAssDataMissing (6),
+	methodNotSupported (7),
+	notProcessed (8),
+	refBTSForGPSNotServingBTS (9),
+	refBTSForEOTDNotServingBTS (10),
+	...,
+	notEnoughGANSSSats (11),	
+ ganssAssDataMissing (12),
+	refBTSForGANSSNotServingBTS (13)
+}
+
+-- exception handling:
+-- an unrecognized value shall be treated the same as value 0
+
+
+-- defines additional assistance data needed for any new location attempt
+-- MS shall retain any assistance data already received
+AdditionalAssistanceData ::= SEQUENCE {
+	gpsAssistanceData		GPSAssistanceData		OPTIONAL,
+	extensionContainer		ExtensionContainer		OPTIONAL,
+	...,
+	ganssAssistanceData GANSSAssistanceData OPTIONAL
+}
+
+GPSAssistanceData ::= OCTET STRING (SIZE (1..maxGPSAssistanceData))
+-- GPSAssistanceData has identical structure and encoding to octets 3 to n of the
+-- GPS Assistance Data IE in 3GPP TS 49.031
+
+maxGPSAssistanceData	INTEGER ::= 40
+
+GANSSAssistanceData ::= OCTET STRING (SIZE (1..maxGANSSAssistanceData))
+-- GANSSAssistanceData has identical structure and encoding to octets 3 to n of the
+-- GANSS Assistance Data IE in 3GPP TS 49.031
+
+maxGANSSAssistanceData	INTEGER ::= 40
+
+
+-- Protocol Error Causes
+ErrorCodes ::= ENUMERATED {
+	unDefined (0),
+missingComponet (1),			
+incorrectData (2),			
+missingIEorComponentElement (3),		
+messageTooShort (4),			
+unknowReferenceNumber (5),		
+...
+}
+
+-- exception handling:
+-- an unrecognized value shall be treated the same as value 0
+
+-- GPS assistance data IE
+GPS-AssistData ::= SEQUENCE {
+	controlHeader		ControlHeader
+}
+
+-- More Assistance Data To Be Sent IE
+-- More Assistance Data Components On the Way indication for delivery of an entire set of assistance
+-- data in multiple Assistance Data components.
+
+MoreAssDataToBeSent ::= ENUMERATED {
+	noMoreMessages (0),			-- This is the only or last Assistance Data message used to deliver
+								-- the entire set of assistance data.
+	moreMessagesOnTheWay (1)	-- The SMLC will send more Assistance Data messages or a final RRLP
+								-- Measure Position Request message to deliver the
+								-- the entire set of assistance data.
+}
+
+-- Control header of the GPS assistance data
+ControlHeader ::= SEQUENCE {
+
+	-- Field type Present information
+	referenceTime		ReferenceTime		OPTIONAL,
+	refLocation			RefLocation			OPTIONAL,
+	dgpsCorrections		DGPSCorrections		OPTIONAL,
+	navigationModel		NavigationModel		OPTIONAL,
+	ionosphericModel		IonosphericModel		OPTIONAL,
+	utcModel			UTCModel			OPTIONAL,
+	almanac			Almanac			OPTIONAL,
+	acquisAssist		AcquisAssist		OPTIONAL,
+	realTimeIntegrity SeqOf-BadSatelliteSet OPTIONAL
+}
+
+ReferenceTime ::= SEQUENCE {
+	gpsTime				GPSTime,
+	gsmTime				GSMTime				OPTIONAL,
+	gpsTowAssist		GPSTOWAssist		OPTIONAL
+}
+
+-- GPS Time includes week number and time-of-week (TOW)
+GPSTime ::= SEQUENCE {
+	gpsTOW23b			GPSTOW23b,
+	gpsWeek				GPSWeek
+}
+
+-- GPSTOW, range 0-604799.92, resolution 0.08 sec, 23-bit presentation
+GPSTOW23b ::= INTEGER (0..7559999)
+
+-- GPS week number
+GPSWeek ::= INTEGER (0..1023)
+
+-- GPSTOWAssist consists of TLM message, Anti-spoof flag, Alert flag, and 2 reserved bits in TLM Word
+-- for each visible satellite.
+-- N_SAT can be read from number of elements in GPSTOWAssist
+GPSTOWAssist ::= SEQUENCE (SIZE(1..12)) OF GPSTOWAssistElement
+
+GPSTOWAssistElement ::= SEQUENCE {
+	satelliteID			SatelliteID,
+	tlmWord				TLMWord,
+	antiSpoof			AntiSpoofFlag,
+	alert				AlertFlag,
+	tlmRsvdBits			TLMReservedBits
+}
+
+-- TLM Word, 14 bits
+TLMWord ::= INTEGER (0..16383)
+
+-- Anti-Spoof flag
+AntiSpoofFlag ::= INTEGER (0..1)
+
+-- Alert flag
+AlertFlag ::= INTEGER (0..1)
+
+-- Reserved bits in TLM word, MSB occurs earlier in TLM Word transmitted by satellite
+TLMReservedBits ::= INTEGER (0..3)
+
+GSMTime ::= SEQUENCE {
+	bcchCarrier		BCCHCarrier,	-- BCCH carrier
+	bsic			BSIC,			-- BSIC
+	frameNumber		FrameNumber,
+	timeSlot		TimeSlot,
+	bitNumber		BitNumber
+}
+
+-- Frame number
+FrameNumber ::= INTEGER (0..2097151)
+
+-- Time slot number
+TimeSlot ::= INTEGER (0..7)
+
+-- Bit number
+BitNumber ::= INTEGER (0..156)
+
+
+-- Reference Location IE
+RefLocation ::= SEQUENCE {
+	threeDLocation			Ext-GeographicalInformation
+}
+
+-- DGPS Corrections IE
+DGPSCorrections ::= SEQUENCE {
+
+	gpsTOW		INTEGER (0..604799),	-- DGPS reference time
+	status		INTEGER (0..7),
+	-- N_SAT can be read from number of elements of satList
+	satList		SeqOfSatElement 	
+}
+SeqOfSatElement ::= SEQUENCE (SIZE (1..16)) OF SatElement
+
+-- number of correction for satellites
+SatElement ::= SEQUENCE {
+	satelliteID		SatelliteID,
+
+
+--- Sequence number for ephemeris
+	iode 			INTEGER (0..239),
+	-- User Differential Range Error
+	udre			INTEGER (0..3),		
+
+	-- Pseudo Range Correction, range is
+	-- -655.04 - +655.04,
+	pseudoRangeCor	INTEGER (-2047..2047), 	
+
+	-- Pseudo Range Rate Correction, range is
+	-- -4.064 - +4.064,
+	rangeRateCor	INTEGER (-127..127),
+
+-- Delta Pseudo Range Correction 2 	
+	deltaPseudoRangeCor2 	INTEGER (-127..127),	-- This IE shall be ignored by the receiver and
+													-- set to zero by the sender
+	-- Delta Pseudo Range Correction 2	
+	deltaRangeRateCor2		INTEGER (-7..7),		-- This IE shall be ignored by the receiver and
+													-- set to zero by the sender
+	-- Delta Pseudo Range Correction 3
+	deltaPseudoRangeCor3 	INTEGER (-127..127),	-- This IE shall be ignored by the receiver and
+													-- set to zero by the sender
+	-- Delta Pseudo Range Correction 3
+	deltaRangeRateCor3		INTEGER (-7..7)			-- This IE shall be ignored by the receiver and
+													-- set to zero by the sender
+}
+
+SatelliteID ::= INTEGER (0..63)	-- identifies satellite
+
+-- Navigation Model IE
+NavigationModel ::= SEQUENCE {
+	navModelList	SeqOfNavModelElement	
+}
+
+-- navigation model satellite list
+SeqOfNavModelElement ::= SEQUENCE (SIZE(1..16)) OF NavModelElement
+
+NavModelElement ::= SEQUENCE {
+	satelliteID		SatelliteID,			
+	satStatus		SatStatus		-- satellite status
+}
+
+-- the Status of the navigation model
+SatStatus ::= CHOICE {
+	-- New satellite, new Navigation Model
+	newSatelliteAndModelUC	UncompressedEphemeris,
+
+	-- Existing satellite, Existing Navigation Model
+	oldSatelliteAndModel	NULL,
+
+	-- Existing satellite, new Navigation Model
+	newNaviModelUC			UncompressedEphemeris,
+	...
+}
+
+-- Uncompressed satellite emhemeris and clock corrections
+UncompressedEphemeris ::= SEQUENCE {
+	ephemCodeOnL2	INTEGER (0..3),
+	ephemURA		INTEGER (0..15),
+	ephemSVhealth	INTEGER (0..63),
+	ephemIODC		INTEGER	(0..1023),
+	ephemL2Pflag	INTEGER (0..1),
+	ephemSF1Rsvd	EphemerisSubframe1Reserved,
+	ephemTgd		INTEGER (-128..127),
+	ephemToc		INTEGER (0..37799),
+	ephemAF2		INTEGER (-128..127),
+	ephemAF1		INTEGER (-32768..32767),
+	ephemAF0		INTEGER (-2097152..2097151),
+	ephemCrs		INTEGER (-32768..32767),
+	ephemDeltaN		INTEGER (-32768..32767),
+	ephemM0			INTEGER (-2147483648..2147483647),
+	ephemCuc		INTEGER (-32768..32767),
+	ephemE			INTEGER (0..4294967295),
+	ephemCus		INTEGER (-32768..32767),
+	ephemAPowerHalf	INTEGER (0..4294967295),
+	ephemToe		INTEGER (0..37799),
+	ephemFitFlag	INTEGER (0..1),
+	ephemAODA		INTEGER (0..31),
+	ephemCic		INTEGER (-32768..32767),
+	ephemOmegaA0	INTEGER (-2147483648..2147483647),
+	ephemCis		INTEGER (-32768..32767),
+	ephemI0			INTEGER (-2147483648..2147483647),
+	ephemCrc		INTEGER (-32768..32767),
+	ephemW			INTEGER (-2147483648..2147483647),
+	ephemOmegaADot	INTEGER (-8388608..8388607),
+	ephemIDot		INTEGER (-8192..8191)
+}
+
+-- Reserved bits in subframe 1 of navigation message
+EphemerisSubframe1Reserved ::= SEQUENCE {
+	reserved1		INTEGER (0..8388607),	-- 23-bit field
+	reserved2		INTEGER (0..16777215),	-- 24-bit field
+	reserved3		INTEGER (0..16777215),	-- 24-bit field
+	reserved4		INTEGER (0..65535)		-- 16-bit field
+}
+
+-- Ionospheric Model IE
+IonosphericModel ::= SEQUENCE {
+	alfa0			INTEGER (-128..127),
+	alfa1			INTEGER (-128..127),
+	alfa2			INTEGER (-128..127),
+	alfa3			INTEGER (-128..127),
+	beta0			INTEGER (-128..127),
+	beta1			INTEGER (-128..127),
+	beta2			INTEGER (-128..127),
+	beta3			INTEGER (-128..127)
+}
+
+-- Universal Time Coordinate Model
+UTCModel ::= SEQUENCE {
+	utcA1			INTEGER (-8388608..8388607),
+	utcA0			INTEGER (-2147483648..2147483647),
+	utcTot			INTEGER (0..255),
+	utcWNt			INTEGER (0..255),
+	utcDeltaTls		INTEGER (-128..127),
+	utcWNlsf		INTEGER (0..255),
+	utcDN			INTEGER (-128..127),
+	utcDeltaTlsf	INTEGER (-128..127)
+}
+
+-- Almanac, Long term model
+-- NOTE: These are parameters are subset of the ephemeris
+-- NOTE: But with reduced resolution and accuracy
+Almanac ::= SEQUENCE {
+	alamanacWNa		INTEGER (0..255),	-- Once per message
+
+	-- navigation model satellite list.
+	-- The size of almanacList is actually Nums_Sats_Total field
+	almanacList		SeqOfAlmanacElement		
+}
+SeqOfAlmanacElement ::= SEQUENCE (SIZE(1..64)) OF AlmanacElement
+
+-- Almanac info once per satellite
+AlmanacElement ::= SEQUENCE {
+	satelliteID			SatelliteID,
+	almanacE			INTEGER (0..65535),
+	alamanacToa			INTEGER (0..255),
+	almanacKsii			INTEGER (-32768..32767),
+	almanacOmegaDot		INTEGER (-32768..32767),
+	almanacSVhealth		INTEGER (0..255),
+	almanacAPowerHalf	INTEGER (0..16777215),
+	almanacOmega0		INTEGER (-8388608..8388607),
+	almanacW			INTEGER (-8388608..8388607),
+	almanacM0			INTEGER (-8388608..8388607),
+	almanacAF0			INTEGER (-1024..1023),
+	almanacAF1			INTEGER (-1024..1023)
+}
+
+-- Acquisition Assistance
+AcquisAssist ::= SEQUENCE {
+
+	-- Number of Satellites can be read from acquistList
+	timeRelation	TimeRelation,
+
+	-- Acquisition assistance list
+	-- The size of Number of Satellites is actually Number of Satellites field
+	acquisList		SeqOfAcquisElement		
+}
+SeqOfAcquisElement ::= SEQUENCE (SIZE(1..16)) OF AcquisElement
+
+-- the relationship between GPS time and air-interface timing
+TimeRelation ::= SEQUENCE {
+	--
+	gpsTOW		GPSTOW23b,		-- 23b presentation
+	gsmTime		GSMTime		OPTIONAL
+}
+
+-- data occuring per number of satellites
+AcquisElement ::= SEQUENCE {
+	svid					SatelliteID,
+
+	-- Doppler 0th order term,
+	-- -5120.0 - 5117.5 Hz (= -2048 - 2047 with 2.5 Hz resolution)
+	doppler0				INTEGER (-2048..2047),	
+	addionalDoppler			AddionalDopplerFields	OPTIONAL,
+	codePhase				INTEGER (0..1022),	-- Code Phase
+	intCodePhase			INTEGER (0..19),	-- Integer Code Phase
+	gpsBitNumber			INTEGER (0..3), 	-- GPS bit number
+	codePhaseSearchWindow	INTEGER (0..15),	-- Code Phase Search Window
+	addionalAngle			AddionalAngleFields		OPTIONAL
+}
+
+AddionalDopplerFields ::= SEQUENCE {
+	-- Doppler 1st order term, -1.0 - +0.5 Hz/sec
+ -- (= -42 + (0 to 63) with 1/42 Hz/sec. resolution)
+	doppler1				INTEGER (0..63),
+	dopplerUncertainty		INTEGER (0..7)
+ -- a sender shall not encode any DopplerUncertainty value in the range 5 to 7
+ -- a receiver shall ignore any value between 5 and 7.
+}
+
+AddionalAngleFields	::= SEQUENCE {
+	-- azimuth angle, 0 - 348.75 deg (= 0 - 31 with 11.25 deg resolution)
+	azimuth					INTEGER (0..31),
+	-- elevation angle, 0 - 78.75 deg (= 0 - 7 with 11.25 deg resolution)
+	elevation				INTEGER (0..7)
+}
+
+-- Real-Time Integrity
+-- number of bad satellites can be read from this element
+SeqOf-BadSatelliteSet ::= SEQUENCE (SIZE(1..16)) OF SatelliteID
+
+-- Extension Elements
+
+-- Release 98 Extensions here
+Rel98-MsrPosition-Req-Extension ::= SEQUENCE {
+	rel98-Ext-ExpOTD			Rel98-Ext-ExpOTD		OPTIONAL,	-- ExpectedOTD extension
+	..., 
+ gpsTimeAssistanceMeasurementRequest NULL OPTIONAL, 
+ gpsReferenceTimeUncertainty GPSReferenceTimeUncertainty OPTIONAL
+
+-- Further R98 extensions here
+}
+Rel98-AssistanceData-Extension ::= SEQUENCE {
+	rel98-Ext-ExpOTD			Rel98-Ext-ExpOTD		OPTIONAL,	-- ExpectedOTD extension
+	..., 
+ gpsTimeAssistanceMeasurementRequest NULL OPTIONAL,
+ gpsReferenceTimeUncertainty GPSReferenceTimeUncertainty OPTIONAL
+
+-- Further R98 extensions here
+}
+
+-- Release 98 ExpOTD extension
+Rel98-Ext-ExpOTD ::= SEQUENCE {
+-- If MsrAssistBTS is included in message, msrAssistData-R98-ExpOTD shall be included.
+	msrAssistData-R98-ExpOTD			MsrAssistData-R98-ExpOTD			OPTIONAL,
+
+-- If SystemInfoAssistaData is included in message, systemInfoAssistData-R98-ExpOTD shall be
+-- included.
+	systemInfoAssistData-R98-ExpOTD	SystemInfoAssistData-R98-ExpOTD	OPTIONAL
+}
+
+-- MsrAssistData R98 extension
+MsrAssistData-R98-ExpOTD ::= SEQUENCE {
+	 msrAssistList-R98-ExpOTD			 SeqOfMsrAssistBTS-R98-ExpOTD
+}
+
+-- Indexes in SeqOfMsrAssistBTS-R98-ExpOTD refer to SeqOfMsrAssistBTS
+-- If the index exceeds the SegOfMsrAssistBTS range or if there is other
+-- inconsistencies between the BTS indices, the MS shall apply protocol
+-- error cause incorrectData
+SeqOfMsrAssistBTS-R98-ExpOTD ::= SEQUENCE (SIZE(1..15)) OF MsrAssistBTS-R98-ExpOTD
+
+-- This element completes MsrAssistBTS IE
+MsrAssistBTS-R98-ExpOTD ::= SEQUENCE {
+	expectedOTD				ExpectedOTD,
+	expOTDUncertainty		ExpOTDUncertainty
+}
+
+-- SystemInfoAssistData R98 extension
+SystemInfoAssistData-R98-ExpOTD ::= SEQUENCE {
+	systemInfoAssistListR98-ExpOTD 	SeqOfSystemInfoAssistBTS-R98-ExpOTD
+}
+
+-- SeqOfSystemInfoAssistBTS-R98-ExpOTD index refer to SeqOfSystemInfoAssistBTS
+-- If the index exceeds the SegOfSystemInfoAssistBTS range or if there is other
+-- inconsistencies between the BTS indices, the MS shall apply protocol
+-- error cause incorrectData
+SeqOfSystemInfoAssistBTS-R98-ExpOTD ::= SEQUENCE (SIZE(1..32)) OF SystemInfoAssistBTS-R98-ExpOTD
+
+-- whether n.th is present or not ?
+SystemInfoAssistBTS-R98-ExpOTD ::= CHOICE {
+	notPresent		NULL,
+	present			AssistBTSData-R98-ExpOTD
+}
+
+-- This element completes AssistBTSData IE
+AssistBTSData-R98-ExpOTD ::= SEQUENCE {
+	expectedOTD				ExpectedOTD,
+	expOTDuncertainty		ExpOTDUncertainty	-- Uncertainty of expected OTD
+}
+
+-- Expected OTD value between nbor base station and reference BTS
+-- at MS's current estimated location.
+ExpectedOTD ::= INTEGER (0..1250)
+-- The ExpectedOTD value 1250 shall not be encoded by the transmitting entity and
+-- shall be treated by the receiving entity as 0.
+-- Uncertainty of Exptected OTD in bits
+ExpOTDUncertainty ::= INTEGER(0..7)
+
+-- Release 98 extensions
+
+GPSReferenceTimeUncertainty ::= INTEGER (0 .. 127) -- Coding according to Annex
+
+GPSTimeAssistanceMeasurements ::= SEQUENCE {
+ referenceFrameMSB INTEGER (0 .. 63), -- MSB of frame number
+ gpsTowSubms INTEGER (0 .. 9999) OPTIONAL, -- in units of 100ns, for MS based AGPS
+ deltaTow INTEGER (0 .. 127) OPTIONAL, -- for MS assisted AGPS
+ gpsReferenceTimeUncertainty GPSReferenceTimeUncertainty OPTIONAL
+}
+
+Rel-98-MsrPosition-Rsp-Extension ::= SEQUENCE {
+
+	-- First extension to Release 98
+	rel-98-Ext-MeasureInfo	SEQUENCE {
+		otd-MeasureInfo-R98-Ext	OTD-MeasureInfo-R98-Ext		OPTIONAL
+	},
+	..., 
+ timeAssistanceMeasurements GPSTimeAssistanceMeasurements OPTIONAL 
+ -- Further R98 extensions here
+}
+
+-- This is an addition to OTD-MeasureInfo element defined in original message,
+-- If OTD-MeasureInfo is absent, or if one or more OTD-MsrElementRest are present
+-- OTD-MeasureInfo-R98-Ext shall be absent.
+-- OTD-MeasureInfo-R98-Ext
+OTD-MeasureInfo-R98-Ext ::= SEQUENCE {
+	-- Measurement info elements
+	otdMsrFirstSets-R98-Ext		OTD-MsrElementFirst-R98-Ext
+}
+
+-- OTD measurement information Ext for the first set only
+OTD-MsrElementFirst-R98-Ext ::= SEQUENCE {
+	-- additional measured neighbors in OTD measurements
+	otd-FirstSetMsrs-R98-Ext 	SeqOfOTD-FirstSetMsrs-R98-Ext 	OPTIONAL
+}
+SeqOfOTD-FirstSetMsrs-R98-Ext ::= SEQUENCE (SIZE(1..5)) OF OTD-FirstSetMsrs
+
+Rel-5-MsrPosition-Rsp-Extension ::= SEQUENCE {
+
+	extended-reference	Extended-reference 	OPTIONAL,
+	-- The extended-reference shall be included by the MS if and only if previously
+	-- received from the SMLC in a Measure Position Request. When included, the value sent
+	-- by the MS shall equal the value received from the SMLC.
+
+	-- extension to Release 5, for RRLP pseudo-segmentation here
+	otd-MeasureInfo-5-Ext	OTD-MeasureInfo-5-Ext	OPTIONAL,
+	ulPseudoSegInd			UlPseudoSegInd			OPTIONAL,	-- Included when uplink RRLP
+	-- Pseudo-segmentation is used, not included when no uplink pseudo-segmentation is used
+	...
+					-- Possibly more extensions for Release 5 here later
+}
+
+Extended-reference ::= SEQUENCE {
+	smlc-code			INTEGER (0..63),
+	transaction-ID		INTEGER (0..262143)
+}
+
+OTD-MeasureInfo-5-Ext ::= SeqOfOTD-MsrElementRest
+	-- if more than one measurement sets are present this element is repeated
+	-- NumberOfSets - 1 (-1 = first set) combined in OTD-MeasureInfo-5-Ext and
+	-- OTD-MeasureInfo (e.g. if NumberOfSets is 3, then one otdMsrRestSets may
+	-- be sent in OTD-MeasureInfo-5-Ext and one in OTD-MeasureInfo)
+
+-- First part of Uplink RRLP Pseudo-segmentation indication, possibly more may be defined
+-- in the future for segmentation with more than two segments.
+UlPseudoSegInd ::= ENUMERATED {
+	firstOfMany (0),
+	secondOfMany(1)
+}
+
+Rel5-MsrPosition-Req-Extension ::= SEQUENCE {
+	extended-reference			Extended-reference,
+	...
+	-- Possibly more extensions for Release 5 here later
+}
+
+Rel5-AssistanceData-Extension ::= SEQUENCE {
+	extended-reference			Extended-reference,
+	...
+
+-- Possibly more extensions for Release 5 here later
+}
+
+Rel-5-ProtocolError-Extension::= SEQUENCE {
+	extended-reference			Extended-reference 	OPTIONAL,
+	-- The extended-reference shall be included by the MS if and only if previously
+	-- received from the SMLC.
+	-- When included, the value sent by the MS shall equal the value received from the SMLC.
+	...
+
+	-- Possibly more extensions for Release 5 here later
+}
+
+-- Release 7 Extensions here
+
+Rel7-MsrPosition-Req-Extension ::= SEQUENCE {
+velocityRequested		 NULL		 OPTIONAL,
+ ganssPositionMethod GANSSPositioningMethod OPTIONAL,
+ ganss-AssistData GANSS-AssistData OPTIONAL,
+ ganssCarrierPhaseMeasurementRequest NULL OPTIONAL,
+ ganssTODGSMTimeAssociationMeasurementRequest NULL OPTIONAL,
+requiredResponseTime	RequiredResponseTime	OPTIONAL,
+	...
+	-- Further Release 7 extentions here
+}
+
+-- additional satellite systems may be added in future versions of the protocol
+GANSSPositioningMethod ::= BIT STRING {
+	gps (0),
+	galileo (1)} (SIZE (2..16))
+
+GANSS-AssistData ::= SEQUENCE {
+	ganss-controlHeader	GANSS-ControlHeader
+}
+
+GANSS-ControlHeader ::= SEQUENCE {
+ ganssCommonAssistData GANSSCommonAssistData OPTIONAL,
+ ganssGenericAssistDataList SeqOfGANSSGenericAssistDataElement OPTIONAL
+}
+
+-- GANSS Common Assistance Data Elements
+GANSSCommonAssistData ::= SEQUENCE {
+	ganssReferenceTime		 GANSSReferenceTime		 OPTIONAL,
+	ganssRefLocation			GANSSRefLocation		 OPTIONAL,
+	ganssIonosphericModel		GANSSIonosphericModel	 OPTIONAL,
+	...
+}
+
+-- List of GANSS Generic Assistance Data Elements, up to 8 GANSS
+SeqOfGANSSGenericAssistDataElement ::= SEQUENCE (SIZE (1..8)) OF GANSSGenericAssistDataElement
+
+-- GANSS Generic Assistance Data Elements
+GANSSGenericAssistDataElement ::= SEQUENCE {
+ ganssID INTEGER (0..7) OPTIONAL, -- Coding according to Annex
+ ganssTimeModel SeqOfGANSSTimeModel OPTIONAL,
+	ganssDiffCorrections		 GANSSDiffCorrections	 OPTIONAL,
+	ganssNavigationModel		 GANSSNavModel OPTIONAL,
+	ganssRealTimeIntegrity	 GANSSRealTimeIntegrity	 OPTIONAL,
+	ganssDataBitAssist			 GANSSDataBitAssist		 OPTIONAL,
+	ganssRefMeasurementAssist	 GANSSRefMeasurementAssist OPTIONAL,
+ ganssAlmanacModel GANSSAlmanacModel OPTIONAL,
+ ganssUTCModel GANSSUTCModel OPTIONAL,
+	...
+}
+
+-- GANSS COMMON ASSISTANCE DATA ELEMENTS
+
+-- GANSS Reference Time IE
+GANSSReferenceTime ::= SEQUENCE {
+	ganssRefTimeInfo			 GANSSRefTimeInfo,
+	ganssTOD-GSMTimeAssociation GANSSTOD-GSMTimeAssociation OPTIONAL
+}
+
+-- GANSS Reference Time includes GANSS TOD, GANSS Day, uncertainty
+GANSSRefTimeInfo ::= SEQUENCE {
+	ganssDay			 INTEGER(0 .. 8191) OPTIONAL,
+ ganssTOD GANSSTOD,
+	ganssTODUncertainty	 GANSSTODUncertainty OPTIONAL,
+ ganssTimeID INTEGER (0 .. 7) OPTIONAL
+}
+
+-- GANSS TOD integer seconds
+GANSSTOD ::= INTEGER (0 .. 86399)
+
+-- GANSS TOD uncertainty
+GANSSTODUncertainty ::= INTEGER (0 .. 127) -- Coding according to Annex
+
+-- GANSS TOD-GSM Time association 
+GANSSTOD-GSMTimeAssociation ::= SEQUENCE {
+	bcchCarrier		BCCHCarrier,	-- BCCH carrier
+	bsic			BSIC,			-- BSIC
+	frameNumber		FrameNumber,
+	timeSlot		TimeSlot,
+	bitNumber		BitNumber,
+ frameDrift FrameDrift OPTIONAL
+}
+
+-- Frame drift
+FrameDrift ::= INTEGER(-64 .. 63)
+
+-- GANSS Reference Location IE
+GANSSRefLocation ::= SEQUENCE {
+	threeDLocation			Ext-GeographicalInformation
+}
+
+-- GANSS Ionospheric Model IE
+-- GANSS Ionospheric Model consists of NeQuick model parameters and storm flags
+
+GANSSIonosphericModel ::= SEQUENCE {
+ ganssIonoModel GANSSIonosphereModel,
+ ganssIonoStormFlags GANSSIonoStormFlags OPTIONAL,
+ ...
+}
+
+-- GANSS ionosphere model. Coding according to Annex 
+GANSSIonosphereModel ::= SEQUENCE {
+ ai0 INTEGER (0 .. 4095),
+ ai1 INTEGER (0 .. 4095),
+ ai2 INTEGER (0 .. 4095)
+}
+
+-- GANSS ionosphere storm flags
+GANSSIonoStormFlags ::= SEQUENCE {
+	ionoStormFlag1	INTEGER (0 .. 1),
+	ionoStormFlag2	INTEGER (0 .. 1),
+	ionoStormFlag3	INTEGER (0 .. 1),
+	ionoStormFlag4	INTEGER (0 .. 1),
+	ionoStormFlag5	INTEGER (0 .. 1)
+}
+
+-- GANSS GENERIC ASSISTANCE DATA ELEMENTS
+
+-- GANSS Time Model IE consists of time offset and first and second order parameters to relate GNSS
+-- specific system time to selected time reference
+SeqOfGANSSTimeModel ::= SEQUENCE (SIZE(1..7)) OF GANSSTimeModelElement
+
+GANSSTimeModelElement ::= SEQUENCE {
+	ganssTimeModelRefTime		INTEGER(0 .. 65535),
+	tA0		 TA0,
+	tA1		 TA1 OPTIONAL,
+	tA2		 TA2 OPTIONAL,
+ gnssTOID INTEGER (0 .. 7),
+ weekNumber INTEGER (0 .. 8191) OPTIONAL 
+}
+
+-- GANSS time model parameter A0
+TA0 ::= INTEGER (-2147483648 .. 2147483647)
+
+-- GANSS time model parameter A1
+TA1 ::= INTEGER (-8388608 .. 8388607)
+
+-- GANSS time model parameter A2
+TA2 ::= INTEGER (-64 .. 63)
+
+-- DGANSS Corrections IE
+GANSSDiffCorrections ::= SEQUENCE {
+	dganssRefTime		INTEGER (0 .. 119),	 -- DGANSS reference time
+
+	-- N_SGN_TYPE can be read from number of elements of sgnTypeList
+	sgnTypeList		 SeqOfSgnTypeElement
+}
+
+SeqOfSgnTypeElement ::= SEQUENCE (SIZE (1..3)) OF SgnTypeElement -- max three signals per GNSS
+
+-- DGANSS signal type element, once per GNSS signal type included in DGANSS
+SgnTypeElement ::= SEQUENCE {
+	ganssSignalID		 GANSSSignalID OPTIONAL, -- signal type identity
+ ganssStatusHealth INTEGER (0 .. 7),
+	-- N_SGN can be read from number of elements of dganssSgnList
+ dganssSgnList SeqOfDGANSSSgnElement
+}
+
+GANSSSignalID ::= INTEGER (0 .. 3)	-- Coding according to Annex
+SeqOfDGANSSSgnElement ::= SEQUENCE (SIZE (1..16)) OF DGANSSSgnElement
+
+-- number of correction for signals
+DGANSSSgnElement ::= SEQUENCE {
+	svID		 SVID, -- Satellite identity
+
+--- Sequence number for GANSS Navigation Model that matches the DGANSS correction set
+	iod 			INTEGER (0 .. 1023),
+
+	-- User Differential Range Error
+	udre			INTEGER (0..3),		
+
+	-- Pseudo Range Correction, range is
+	-- -655.04 - +655.04,
+	pseudoRangeCor	INTEGER (-2047..2047), 	
+
+	-- Pseudo Range Rate Correction, range is
+	-- -4.064 - +4.064,
+	rangeRateCor	INTEGER (-127..127)
+}
+
+SVID ::= INTEGER (0 .. 63)	-- Coding according to Annex
+
+-- GANSS Navigation Model IE
+GANSSNavModel ::= SEQUENCE {
+ nonBroadcastIndFlag INTEGER (0 .. 1),
+ toeMSB INTEGER (0 .. 31) OPTIONAL, -- 5 MSB of toe and toc 
+ eMSB INTEGER (0 .. 127) OPTIONAL,
+ sqrtAMBS INTEGER (0 .. 63) OPTIONAL,
+	ganssSatelliteList SeqOfGANSSSatelliteElement
+}
+
+SeqOfGANSSSatelliteElement ::= SEQUENCE (SIZE(1..32)) OF GANSSSatelliteElement
+
+GANSSSatelliteElement ::= SEQUENCE {
+	svID SVID,
+ svHealth INTEGER (-7 .. 13), -- Coding according to Annex
+ iod INTEGER (0 .. 1023), -- Coding according to Annex
+ ganssClockModel GANSSClockModel, 
+ ganssOrbitModel GANSSOrbitModel, 
+ ...
+}
+
+-- GANSS orbit model for the GNSS satellite according to the choice
+GANSSOrbitModel ::= CHOICE {
+	keplerianSet	 NavModel-KeplerianSet,	
+ ...
+}
+
+-- Navigation model in Keplerian parameters
+NavModel-KeplerianSet ::= SEQUENCE {
+ keplerToeLSB INTEGER (0 .. 511), -- 9LSB are given in GANSSNavigationModel
+	keplerW			 INTEGER (-2147483648..2147483647),
+	keplerDeltaN	 INTEGER (-32768..32767),
+	keplerM0		 INTEGER (-2147483648..2147483647),
+	keplerOmegaDot	 INTEGER (-8388608..8388607),
+	keplerELSB 	 INTEGER (0..33554431),
+	keplerIDot		 INTEGER (-8192..8191),
+	keplerAPowerHalfLSB INTEGER (0.. 67108863),
+	keplerI0		 INTEGER (-2147483648..2147483647),
+	keplerOmega0 INTEGER (-2147483648..2147483647),
+	keplerCrs		 INTEGER (-32768..32767),
+	keplerCis		 INTEGER (-32768..32767),
+	keplerCus		 INTEGER (-32768..32767),
+	keplerCrc		 INTEGER (-32768..32767),
+	keplerCic		 INTEGER (-32768..32767),
+	keplerCuc		 INTEGER (-32768..32767)
+}
+
+-- GANSS clock model for the GNSS satellite according to the choice
+GANSSClockModel ::= CHOICE {
+	standardClockModelList	 SeqOfStandardClockModelElement,	
+ ...
+}
+
+SeqOfStandardClockModelElement ::= SEQUENCE (SIZE(1..2)) OF StandardClockModelElement
+
+StandardClockModelElement ::= SEQUENCE {
+ stanClockTocLSB INTEGER (0 .. 511), -- 9LSB of time of clock
+ stanClockAF2 INTEGER (-2048 .. 2047), 
+ stanClockAF1 INTEGER (-131072 .. 131071), 
+ stanClockAF0 INTEGER (-134217728 .. 134217727),
+ stanClockTgd INTEGER (-512 .. 511) OPTIONAL,
+ stanModelID INTEGER (0 .. 1) OPTIONAL,
+ ...
+} 
+
+-- GANSS Real-Time Integrity IE
+GANSSRealTimeIntegrity ::= SEQUENCE {
+ -- list of bad signals
+ -- NBS can be read from number of elements in SeqOf-BadSignalSet
+ ganssBadSignalList SeqOfBadSignalElement
+}
+
+SeqOfBadSignalElement ::= SEQUENCE (SIZE(1..16)) OF BadSignalElement
+
+BadSignalElement ::= SEQUENCE {
+ badSVID SVID, -- Coding according to Annex
+ badSignalID INTEGER (0 .. 3) OPTIONAL -- Coding according to Annex
+}
+
+
+-- GANSS Data Bit Assistance IE
+GANSSDataBitAssist ::= SEQUENCE {
+ ganssTOD INTEGER (0 .. 59),
+ svID SVID,
+ ganssDataTypeID INTEGER (0 .. 2), -- Coding according to Annex
+ -- list of navigation data bits
+ -- N_BIT can be read from number of elements in SeqOf-DataBits
+ ganssDataBits SeqOf-GANSSDataBits
+}
+
+SeqOf-GANSSDataBits ::= SEQUENCE (SIZE(1 .. 1024)) OF GANSSDataBit
+GANSSDataBit ::= INTEGER(0 .. 1)
+
+-- GANSS Reference Measurement Assistance IE
+-- Code and Doppler assistance from the network. 
+GANSSRefMeasurementAssist ::= SEQUENCE {
+ ganssSignalID INTEGER (0 .. 3) OPTIONAL, -- Coding according to Annex
+ ganssRefMeasAssitList SeqOfGANSSRefMeasurementElement
+}
+
+SeqOfGANSSRefMeasurementElement ::= SEQUENCE (SIZE(1 .. 16)) OF GANSSRefMeasurementElement
+
+GANSSRefMeasurementElement ::= SEQUENCE {
+ svID SVID,
+	-- Doppler 0th order term,
+	-- -1024 m/s to 1023.5 m/s with 0.5 m/s resolution)
+	doppler0				INTEGER (-2048 .. 2047),	-- Coding according to Annex
+	additionalDoppler		AdditionalDopplerFields	 OPTIONAL,
+	codePhase				INTEGER (0 .. 1022),	 -- Code Phase in ms
+	intCodePhase			INTEGER (0 .. 127),	 -- Integer Code Phase in ms
+	codePhaseSearchWindow	INTEGER (0 .. 31),	 -- Code Phase Search Window, see Annex
+	additionalAngle			AddionalAngleFields		 OPTIONAL,
+ ...
+}
+
+AdditionalDopplerFields ::= SEQUENCE {
+	-- Doppler 1st order term, -0.2 - +0.1 m/s2
+	doppler1				INTEGER (0..63),
+	dopplerUncertainty		INTEGER (0..4)
+}
+
+-- GANSS Almanac Model IE
+GANSSAlmanacModel ::= SEQUENCE {
+ weekNumber INTEGER (0 .. 255),
+ svIDMask SVIDMASK,
+ toa INTEGER (0 .. 255) OPTIONAL,
+ ioda INTEGER (0 .. 3) OPTIONAL,
+ ganssAlmanacList SeqOfGANSSAlmanacElement
+}
+
+-- SV ID Mask, LSB for ID 1 and MSB for ID 36
+SVIDMASK ::= BIT STRING (SIZE (1..36))
+
+SeqOfGANSSAlmanacElement ::= SEQUENCE (SIZE(1 .. 36)) OF GANSSAlmanacElement
+
+-- GANSS Almanac Model 
+GANSSAlmanacElement ::= CHOICE {
+	keplerianAlmanacSet	 Almanac-KeplerianSet,	
+ ...
+}
+
+-- Almanac parameters according to Keplerian parameters
+Almanac-KeplerianSet ::= SEQUENCE {
+ 	kepAlmanacE			 INTEGER (0 .. 2047),
+	kepAlmanacDeltaI		INTEGER (-1024 .. 1023),
+	kepAlmanacOmegaDot		INTEGER (-1024 .. 1023),
+ kepSVHealth INTEGER (0 .. 15), -- Coding according to Annex
+	kepAlmanacAPowerHalf	INTEGER (-65536 .. 65535),
+	kepAlmanacOmega0		INTEGER (-32768 .. 32767),
+	kepAlmanacW			 INTEGER (-32768 .. 32767),
+	kepAlmanacM0			INTEGER (-32768 .. 32767),
+	kepAlmanacAF0			INTEGER (-8192 .. 8191),
+	kepAlmanacAF1			INTEGER (-1024..1023)
+}
+
+-- GANSS Universal Time Coordinate Model
+GANSSUTCModel ::= SEQUENCE {
+	ganssUtcA1			INTEGER (-8388608..8388607),
+	ganssUtcA0			INTEGER (-2147483648..2147483647),
+	ganssUtcTot			INTEGER (0..255),
+	ganssUtcWNt			INTEGER (0..255),
+	ganssUtcDeltaTls	INTEGER (-128..127),
+	ganssUtcWNlsf		INTEGER (0..255),
+	ganssUtcDN			INTEGER (-128..127),
+	ganssUtcDeltaTlsf	INTEGER (-128..127)
+}
+
+--Required Measurement Request Response Time, range is 1 to 128 seconds. 
+RequiredResponseTime ::= INTEGER  (1..128)
+
+Rel-7-MsrPosition-Rsp-Extension ::= SEQUENCE {
+
+	velEstimate 	VelocityEstimate OPTIONAL,
+ -- Horizontal Velocity
+ -- Horizontal with Vertical Velocity
+ -- Horizontal Velocity with Uncertainty
+ -- Horizontal with Vertical Velocity and Uncertainty
+ ganssLocationInfo GANSSLocationInfo OPTIONAL,
+ ganssMeasureInfo GANSSMeasureInfo OPTIONAL,
+	...
+-- Further Release 7 extensions here
+}
+
+-- GANSS Location Information contains location estimate, time stamp with uncertainty 
+-- and optionally Reference Frame field
+GANSSLocationInfo ::= SEQUENCE {
+ referenceFrame ReferenceFrame OPTIONAL, -- Reference Frame Number
+ ganssTODm GANSSTODm OPTIONAL, -- GNSS TOD modulo
+ ganssTODFrac INTEGER (0 .. 16384) OPTIONAL, -- Coding according to Annex
+	ganssTODUncertainty GANSSTODUncertainty OPTIONAL, -- Coding according to Annex
+ ganssTimeID INTEGER (0 .. 3) OPTIONAL, -- Coding according to Annex
+	fixType			 FixType,
+ posData PositionData,
+ stationaryIndication INTEGER(0 .. 1) OPTIONAL, -- '0' if moving or motion not known 
+	-- Possible shapes carried in posEstimate are
+	-- ellipsoid point,
+	-- ellipsoid point with uncertainty circle
+	-- ellipsoid point with uncertainty ellipse
+	-- ellipsoid point with altitude and uncertainty ellipsoid
+	posEstimate		 Ext-GeographicalInformation,
+ ...
+}
+
+PositionData ::= BIT STRING {
+ e-otd(0),
+	gps (1),
+	galileo (2) } (SIZE (3..16))
+
+
+-- GANSS TOD modulo 1 hour
+GANSSTODm ::= INTEGER (0 .. 3599999)
+
+ReferenceFrame ::= SEQUENCE {
+ referenceFN INTEGER (0 .. 65535),
+	-- Note that applicable range for referenceFN is 0 - 42431
+ referenceFNMSB INTEGER (0 .. 63) OPTIONAL -- MSB of Reference Frame Number
+}
+
+
+
+-- GANSS Measurement Information 
+GANSSMeasureInfo ::= SEQUENCE {
+	-- Measurement info elements
+	-- user has to make sure that in this element is number of elements
+	-- defined in reference BTS identity
+ ganssMsrSetList SeqOfGANSS-MsrSetElement
+}
+SeqOfGANSS-MsrSetElement ::= SEQUENCE (SIZE(1..3)) OF GANSS-MsrSetElement
+
+-- GANSS measurement information 1-3 times in a message
+GANSS-MsrSetElement ::= SEQUENCE {
+ referenceFrame ReferenceFrame OPTIONAL, -- Reference Frame Number
+ ganssTODm GANSSTODm OPTIONAL, -- GANSS TOD modulo
+ deltaGNASSTOD INTEGER (0 .. 127) OPTIONAL,
+	ganssTODUncertainty GANSSTODUncertainty OPTIONAL, -- Coding accoring to Annex
+
+ --N_SGN_TYPE can be read from number of elements of SeqOfGANSS-SgnTypeElement
+ ganss-SgnTypeList SeqOfGANSS-SgnTypeElement
+}
+
+-- Measurements can be returned up to 6 different signal types 
+SeqOfGANSS-SgnTypeElement ::= SEQUENCE (SIZE(1..6)) OF GANSS-SgnTypeElement
+
+GANSS-SgnTypeElement ::= SEQUENCE {
+	ganssSignalID		INTEGER (0 .. 15), -- Coding accroding to Annex
+ --N_SGN can be read from number of elements of SeqOfGANSS-SgnElement
+ ganss-SgnList SeqOfGANSS-SgnElement
+}
+
+-- Measurements can be returned up to 16 per signal types 
+SeqOfGANSS-SgnElement ::= SEQUENCE (SIZE(1..16)) OF GANSS-SgnElement
+
+
+GANSS-SgnElement ::= SEQUENCE {
+ svID SVID,
+ cNo INTEGER (0 .. 63),
+ mpathDet MpathIndic, -- Coding according to Annex
+ carrierQualityInd INTEGER (0 .. 3) OPTIONAL, -- Coding according to Annex
+ codePhase INTEGER (0 .. 2097151),
+ integerCodePhase INTEGER (0 .. 63) OPTIONAL,
+ codePhaseRMSError INTEGER (0..63), -- Coding accoring to Annex
+ doppler INTEGER (-32768 .. 32767) OPTIONAL,
+ adr INTEGER (0 .. 33554431) OPTIONAL
+}
+
+Rel7-AssistanceData-Extension ::= SEQUENCE {
+ ganss-AssistData GANSS-AssistData OPTIONAL,
+ ganssCarrierPhaseMeasurementRequest NULL OPTIONAL,
+ ganssTODGSMTimeAssociationMeasurementRequest NULL OPTIONAL,
+	...
+-- Possibly more extensions for Release 7 here
+}
+
+END
diff --git a/rrlp-ephemeris/asn1/RRLP-Messages.asn b/rrlp-ephemeris/asn1/RRLP-Messages.asn
new file mode 100644
index 0000000..79140e2
--- /dev/null
+++ b/rrlp-ephemeris/asn1/RRLP-Messages.asn
@@ -0,0 +1,38 @@
+-- RRLP-Messages.asn
+-- $Id$
+-- Taken from 3GPP TS 44.031 V7.4.0 (2007-03)
+-- http://www.3gpp.org/ftp/Specs/archive/44_series/44.031/44031-740.zip/44031-740.doc
+--
+-- 3.1 General Format of RRLP Message
+--
+
+RRLP-Messages
+-- { RRLP-messages }
+
+DEFINITIONS AUTOMATIC TAGS ::=
+
+BEGIN
+
+IMPORTS
+	MsrPosition-Req, MsrPosition-Rsp, AssistanceData,
+	ProtocolError
+FROM
+	RRLP-Components 	-- { RRLP-Components }
+;
+
+PDU ::= SEQUENCE {
+	referenceNumber			INTEGER (0..7),
+	component				RRLP-Component
+}
+
+RRLP-Component ::= CHOICE {
+	msrPositionReq			MsrPosition-Req,
+	msrPositionRsp			MsrPosition-Rsp,
+	assistanceData			AssistanceData,
+	assistanceDataAck		NULL,
+	protocolError			ProtocolError,
+	...
+
+}
+
+END
diff --git a/rrlp-ephemeris/asn1/patch-rrlp-components.diff b/rrlp-ephemeris/asn1/patch-rrlp-components.diff
new file mode 100644
index 0000000..a5e55ae
--- /dev/null
+++ b/rrlp-ephemeris/asn1/patch-rrlp-components.diff
@@ -0,0 +1,36 @@
+--- RRLP-Components.asn	2009-10-26 22:10:44.000000000 +0100
++++ RRLP-Components.asn	2009-10-26 22:10:44.000000000 +0100
+@@ -18,16 +18,16 @@
+ 	Ext-GeographicalInformation, VelocityEstimate
+ FROM
+ 	MAP-LCS-DataTypes {
+-	ccitt identified-organization (4) etsi (0) mobileDomain (0)
+-	gsm-Network (1) modules (3) map-LCS-DataTypes (25) version5 (5)}
++	itu-t identified-organization (4) etsi (0) mobileDomain (0)
++	gsm-Network (1) modules (3) map-LCS-DataTypes (25) version11 (11)}
+ 
+ 	ExtensionContainer
+ FROM MAP-ExtensionDataTypes {
+-	ccitt identified-organization (4) etsi (0) mobileDomain (0)
+-	gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version4 (4)}
++	itu-t identified-organization (4) etsi (0) mobileDomain (0)
++	gsm-Network (1) modules (3) map-ExtensionDataTypes (21) version11 (11)}
+ ;
+ 
+ -- Add here other ASN.1 definitions presented below
+ -- in chapters 4 and 5.
+ 
+@@ -305,11 +305,11 @@
+ SystemInfoIndex ::= INTEGER (1..32)
+ 
+ CellIDAndLAC ::= SEQUENCE {
+-	referenceLAC	LAC,				-- Location area code
++	referenceLAC	LACID,				-- Location area code
+ 	referenceCI		CellID				-- Cell identity
+ }
+ CellID ::= INTEGER (0..65535)
+-LAC ::= INTEGER (0..65535)
++LACID ::= INTEGER (0..65535)
+ 
+ -- OTD-MeasureInfo
+ OTD-MeasureInfo ::= SEQUENCE {
diff --git a/rrlp-ephemeris/asn1c_patches/00_add_enumerated_verbose.diff b/rrlp-ephemeris/asn1c_patches/00_add_enumerated_verbose.diff
new file mode 100644
index 0000000..64c22a3
--- /dev/null
+++ b/rrlp-ephemeris/asn1c_patches/00_add_enumerated_verbose.diff
@@ -0,0 +1,56 @@
+Index: skeletons/NativeEnumerated.c
+===================================================================
+--- skeletons/NativeEnumerated.c	(revision 1407)
++++ skeletons/NativeEnumerated.c	(working copy)
+@@ -22,7 +22,7 @@
+ 	"ENUMERATED",			/* The ASN.1 type is still ENUMERATED */
+ 	"ENUMERATED",
+ 	NativeInteger_free,
+-	NativeInteger_print,
++	NativeEnumerated_print,
+ 	asn_generic_no_constraint,
+ 	NativeInteger_decode_ber,
+ 	NativeInteger_encode_der,
+@@ -205,3 +205,30 @@
+ 	_ASN_ENCODED_OK(er);
+ }
+ 
++int
++NativeEnumerated_print(asn_TYPE_descriptor_t *td, const void *sptr, int ilevel,
++	asn_app_consume_bytes_f *cb, void *app_key) {
++	asn_INTEGER_specifics_t *specs=(asn_INTEGER_specifics_t *)td->specifics;
++	const long *native = (const long *)sptr;
++	char scratch[256];
++	int ret;
++
++	(void)td;	/* Unused argument */
++	(void)ilevel;	/* Unused argument */
++
++	if(native) {
++		const asn_INTEGER_enum_map_t *map = INTEGER_map_value2enum(specs, *native);
++		if (map && map->enum_len && map->enum_name) {
++			ret = snprintf(scratch, sizeof(scratch),
++				"%s", map->enum_name);
++		} else {
++			ret = snprintf(scratch, sizeof(scratch),
++				(specs && specs->field_unsigned)
++				? "%lu" : "%ld", *native);
++		}
++		assert(ret > 0 && (size_t)ret < sizeof(scratch));
++		return (cb(scratch, ret, app_key) < 0) ? -1 : 0;
++	} else {
++		return (cb("<absent>", 8, app_key) < 0) ? -1 : 0;
++	}
++}
+Index: skeletons/NativeEnumerated.h
+===================================================================
+--- skeletons/NativeEnumerated.h	(revision 1407)
++++ skeletons/NativeEnumerated.h	(working copy)
+@@ -24,6 +24,7 @@
+ xer_type_encoder_f NativeEnumerated_encode_xer;
+ per_type_decoder_f NativeEnumerated_decode_uper;
+ per_type_encoder_f NativeEnumerated_encode_uper;
++asn_struct_print_f NativeEnumerated_print;
+ 
+ #ifdef __cplusplus
+ }
diff --git a/rrlp-ephemeris/asn1c_patches/01_fix_per_encoding_dieter.diff b/rrlp-ephemeris/asn1c_patches/01_fix_per_encoding_dieter.diff
new file mode 100644
index 0000000..a09c201
--- /dev/null
+++ b/rrlp-ephemeris/asn1c_patches/01_fix_per_encoding_dieter.diff
@@ -0,0 +1,17 @@
+Index: skeletons/per_support.c
+===================================================================
+--- skeletons/per_support.c	(revision 1407)
++++ skeletons/per_support.c	(working copy)
+@@ -336,7 +336,12 @@
+ 		buf[3] = bits;
+ 	else {
+ 		ASN_DEBUG("->[PER out split %d]", obits);
++#if 1 // Dieter
++		po->nboff -= obits; // undo incrementation from a few lines above
++		per_put_few_bits(po, bits >> (obits - 24), 24); // shift according to the rest of the bits 
++#else		
+ 		per_put_few_bits(po, bits >> 8, 24);
++#endif
+ 		per_put_few_bits(po, bits, obits - 24);
+ 		ASN_DEBUG("<-[PER out split %d]", obits);
+ 	}
diff --git a/rrlp-ephemeris/data.ubx b/rrlp-ephemeris/data.ubx
new file mode 100644
index 0000000..07cdddc
--- /dev/null
+++ b/rrlp-ephemeris/data.ubx
Binary files differ
diff --git a/rrlp-ephemeris/get-test-data.sh b/rrlp-ephemeris/get-test-data.sh
new file mode 100755
index 0000000..169f43c
--- /dev/null
+++ b/rrlp-ephemeris/get-test-data.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+DEV=$1
+OUTF=$2
+
+# Change mode
+echo -en "\$PUBX,41,1,0001,0001,9600,0*14\r\n" > ${DEV}
+
+# Wait a little
+sleep 2
+
+# Start dump
+echo -en "\xb5\x62\x01\x02\x00\x00\x03\x0a" | \
+	socat -t5 ${DEV},b9600,raw,clocal=1,echo=0 - > ${OUTF}
+echo -en "\xb5\x62\x0b\x10\x00\x00\x1b\x5c" | \
+	socat -t10 ${DEV},b9600,raw,clocal=1,echo=0 - >> ${OUTF}
+
diff --git a/COPYING b/rrlp-ephemeris/gpl-2.0.txt
similarity index 100%
copy from COPYING
copy to rrlp-ephemeris/gpl-2.0.txt
diff --git a/rrlp-ephemeris/gpl-3.0.txt b/rrlp-ephemeris/gpl-3.0.txt
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/rrlp-ephemeris/gpl-3.0.txt
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/rrlp-ephemeris/gps.c b/rrlp-ephemeris/gps.c
new file mode 100644
index 0000000..c235748
--- /dev/null
+++ b/rrlp-ephemeris/gps.c
@@ -0,0 +1,126 @@
+/*
+ * gps.c
+ *
+ * A few utility functions to deal with low level GPS data
+ *
+ *
+ * Copyright (C) 2009  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gps.h"
+
+
+#define GET_FIELD_U(w, nb, pos) (((w) >> (pos)) & ((1<<(nb))-1))
+#define GET_FIELD_S(w, nb, pos) (((int)((w) << (32-(nb)-(pos)))) >> (32-(nb)))
+
+/*
+ * Unpacks GPS Subframe 1,2,3 payloads (3 * 8 words)
+ *
+ * Note: eph->sv_id is not filled here since not present in those subframes
+ *
+ * (no parity bit checking is done, only the lower 24 bits of each word
+ *  are used)
+ */
+int
+gps_unpack_sf123(uint32_t *sf, struct gps_ephemeris_sv *eph)
+{
+	uint32_t *sf1 = &sf[0];
+	uint32_t *sf2 = &sf[8];
+	uint32_t *sf3 = &sf[16];
+
+	int iode1, iode2;
+
+	eph->week_no	= GET_FIELD_U(sf1[0], 10, 14);
+	eph->code_on_l2	= GET_FIELD_U(sf1[0],  2, 12);
+	eph->sv_ura	= GET_FIELD_U(sf1[0],  4,  8);
+	eph->sv_health	= GET_FIELD_U(sf1[0],  6,  2);
+	eph->l2_p_flag	= GET_FIELD_U(sf1[1],  1, 23);
+	eph->t_gd	= GET_FIELD_S(sf1[4],  8,  0);
+	eph->iodc	= (GET_FIELD_U(sf1[0],  2,  0) << 8) | \
+	                   GET_FIELD_U(sf1[5],  8, 16);
+	eph->t_oc	= GET_FIELD_U(sf1[5], 16,  0);
+	eph->a_f2	= GET_FIELD_S(sf1[6],  8, 16);
+	eph->a_f1	= GET_FIELD_S(sf1[6], 16,  0);
+	eph->a_f0	= GET_FIELD_S(sf1[7], 22,  2);
+
+	iode1		= GET_FIELD_U(sf2[0],  8, 16);
+	eph->c_rs	= GET_FIELD_S(sf2[0], 16,  0);
+	eph->delta_n	= GET_FIELD_S(sf2[1], 16,  8);
+	eph->m_0	= (GET_FIELD_S(sf2[1],  8,  0) << 24) | \
+	                   GET_FIELD_U(sf2[2], 24,  0);
+	eph->c_uc	= GET_FIELD_S(sf2[3], 16,  8);
+	eph->e		= (GET_FIELD_U(sf2[3],  8,  0) << 24) | \
+	                   GET_FIELD_U(sf2[4], 24,  0);
+	eph->c_us	= GET_FIELD_S(sf2[5], 16,  8);
+	eph->a_powhalf	= (GET_FIELD_U(sf2[5],  8,  0) << 24) | \
+	                   GET_FIELD_U(sf2[6], 24,  0);
+	eph->t_oe	= GET_FIELD_U(sf2[7], 16,  8);
+	eph->fit_flag	= GET_FIELD_U(sf2[7],  1,  7);
+
+	eph->c_ic	= GET_FIELD_S(sf3[0], 16,  8);
+	eph->omega_0	= (GET_FIELD_S(sf3[0],  8,  0) << 24) | \
+	                   GET_FIELD_U(sf3[1], 24,  0);
+	eph->c_is	= GET_FIELD_S(sf3[2], 16,  8);
+	eph->i_0	= (GET_FIELD_S(sf3[2],  8,  0) << 24) | \
+	                   GET_FIELD_U(sf3[3], 24,  0);
+	eph->c_rc	= GET_FIELD_S(sf3[4], 16,  8);
+	eph->w		= (GET_FIELD_S(sf3[4],  8,  0) << 24) | \
+	                   GET_FIELD_U(sf3[5], 24,  0);
+	eph->omega_dot	= GET_FIELD_S(sf3[6], 24,  0);
+	iode2		= GET_FIELD_U(sf3[7],  8, 16);
+	eph->idot	= GET_FIELD_S(sf3[7], 14,  2);
+
+	eph->_rsvd1	= GET_FIELD_U(sf1[1], 23,  0);
+	eph->_rsvd2	= GET_FIELD_U(sf1[2], 24,  0);
+	eph->_rsvd3	= GET_FIELD_U(sf1[3], 24,  0);
+	eph->_rsvd4	= GET_FIELD_U(sf1[4], 16,  8);
+	eph->aodo	= GET_FIELD_U(sf2[7],  5,  2);
+
+	/* Check & cross-validate iodc[7:0], iode1, iode2 */
+	if ((iode1 != iode2) || (iode1 != (eph->iodc & 0xff)))
+		return -1;
+
+	return 0;
+}
+
+
+/*
+ * Unpacks GPS Subframe 4 or 5 Almanac pages payload (8 words)
+ *
+ * (no parity bit checking is done, only the lower 24 bits of each word
+ *  are used)
+ */
+int
+gps_unpack_sf45_almanac(uint32_t *sf, struct gps_almanac_sv *alm)
+{
+	alm->sv_id      = GET_FIELD_U(sf[0],  6, 16);
+
+	alm->e		= GET_FIELD_U(sf[0], 16,  0);
+	alm->t_oa	= GET_FIELD_U(sf[1],  8, 16);
+	alm->ksii	= GET_FIELD_S(sf[1], 16,  0);
+	alm->omega_dot	= GET_FIELD_S(sf[2], 16,  8);
+	alm->sv_health	= GET_FIELD_U(sf[2],  8,  0);
+	alm->a_powhalf	= GET_FIELD_U(sf[3], 24,  0);
+	alm->omega_0	= GET_FIELD_S(sf[4], 24,  0);
+	alm->w		= GET_FIELD_S(sf[5], 24,  0);
+	alm->m_0	= GET_FIELD_S(sf[6], 24,  0);
+	alm->a_f0	= (GET_FIELD_S(sf[7], 8, 16) << 3) | \
+	                   GET_FIELD_U(sf[7], 3,  2);
+	alm->a_f1	= GET_FIELD_S(sf[7], 11,  5);
+
+	return 0;
+}
+
diff --git a/rrlp-ephemeris/gps.h b/rrlp-ephemeris/gps.h
new file mode 100644
index 0000000..241b9d7
--- /dev/null
+++ b/rrlp-ephemeris/gps.h
@@ -0,0 +1,190 @@
+/*
+ * gps.h
+ *
+ * Header to deal with low level GPS data
+ *
+ *
+ * Copyright (C) 2009  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GPS_H__
+#define __GPS_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+
+#define MAX_SV	64
+
+
+/* Ionosperic model data */
+struct gps_ionosphere_model {
+			/* #bits  Scale factor  Effective  Units            */
+			/*           (LSB)       range                      */
+
+	int alpha_0;	/* s 8       2^-30                 seconds          */
+	int alpha_1;	/* s 8       2^-27                 s / semi-circles */
+	int alpha_2;	/* s 8       2^-24                 s / (semi-circles)^2 */
+	int alpha_3;	/* s 8       2^-24                 s / (semi-circles)^3 */
+	int beta_0;	/* s 8       2^11                  seconds          */
+	int beta_1;	/* s 8       2^14                  s / semi-circles */
+	int beta_2;	/* s 8       2^16                  s / (semi-circles)^2 */
+	int beta_3;	/* s 8       2^16                  s / (semi-circles)^3 */
+};
+
+
+/* UTC model data */
+struct gps_utc_model {
+			/* #bits  Scale factor  Effective  Units            */
+			/*           (LSB)       range                      */
+
+	int a0;		/* s 32      2^-30                 seconds          */
+	int a1;		/* s 24      2^-50                 seconds / seconds */
+	int delta_t_ls;	/* s  8      1                     seconds          */
+	int t_ot;	/* u  8      2^12       602,112    seconds          */
+	int wn_t;	/* u  8      1                     weeks            */
+	int wn_lsf;	/* u  8      1                     weeks            */
+	int dn;		/* u  8      1                7    days             */
+	int delta_t_lsf;/* s  8      1                     seconds          */
+};
+
+
+/* Almanach data */
+struct gps_almanac_sv {
+	int sv_id;
+	int sv_health;
+
+			/* #bits  Scale factor  Effective  Units            */
+			/*           (LSB)       range                      */
+
+	int e;		/* u 16      2^-21                                  */
+	int t_oa;	/* u  8      2^12       602,112    seconds          */
+	int ksii;	/* s 16      2^-19                 semi-circles     */
+	int omega_dot;	/* s 16      2^-38                 semi-circles / s */
+	int a_powhalf;	/* u 24      2^-11                 meters           */
+	int omega_0;	/* s 24      2^-23                 semi-circles     */
+	int w;		/* s 24      2^-23                 semi-circles     */
+	int m_0;	/* s 24      2^-23                 semi-circles     */
+	int a_f0;	/* s 11      2^-20                 seconds          */
+	int a_f1;	/* s 11      2^-38                 seconds / seconds */
+};
+
+struct gps_almanac {
+	int wna;
+	int n_sv;
+	struct gps_almanac_sv svs[MAX_SV];
+};
+
+
+/* Ephemeris data */
+struct gps_ephemeris_sv {
+	int sv_id;
+
+			/* #bits  Scale factor  Effective  Units            */
+			/*           (LSB)       range                      */
+
+	int code_on_l2;	/* u  2      1                     /                */
+	int week_no;	/* u 10      1                     week             */
+	int l2_p_flag;	/* u  1      1                     /                */
+	int sv_ura;	/* u  4      /                     /                */
+	int sv_health;	/* u  6      /                     /                */
+	int t_gd;	/* s  8      2^-31                 seconds          */
+	int iodc;	/* u 10      /                     /                */
+	int t_oc;	/* u 16      2^4        604,784    seconds          */
+	int a_f2;	/* s  8      2^-55                 sec / sec^2      */
+	int a_f1;	/* s 16      2^-43                 sec / sec        */
+	int a_f0;	/* s 22      2^-31                 seconds          */
+
+	int c_rs;	/* s 16      2^-5                  meters           */
+	int delta_n;	/* s 16      2^-43                 semi-circles / s */
+	int m_0;	/* s 32      2^-31                 semi-circles     */
+	int c_uc;	/* s 16      2^-29                 radians          */
+	unsigned int e;	/* u 32      2^-33      0.03       /                */
+	int c_us;	/* s 16      2^-29                 radians          */
+	unsigned int a_powhalf; /* u 32  2^-19             meters^(1/2)     */
+	int t_oe;	/* u 16      2^4        604,784    seconds          */
+	int fit_flag;	/* u  1      /                     /                */
+
+	int c_ic;	/* s 16      2^-29                 radians          */
+	int omega_0;	/* s 32      2^-31                 semi-circles     */
+	int c_is;	/* s 16      2^-29                 radians          */
+	int i_0;	/* s 32      2^-31                 semi-circles     */
+	int c_rc;	/* s 16      2^-5                  meters           */
+	int w;		/* s 32      2^-31                 semi-circles     */
+	int omega_dot;	/* s 24      2^-43                 semi-circles / s */
+	int idot;	/* s 14      2^-43                 semi-circles / s */
+
+	int _rsvd1;	/* 23 bits */
+	int _rsvd2;	/* 24 bits */
+	int _rsvd3;	/* 24 bits */
+	int _rsvd4;	/* 16 bits */
+	int aodo;	/* 8 bits  Not sure it needs to be here ... */
+};
+
+struct gps_ephemeris {
+	int n_sv;
+	struct gps_ephemeris_sv svs[MAX_SV];
+};
+
+
+/* Reference position */
+struct gps_ref_pos {	/* WSG84 ellipsoid */
+	double latitude;	/* deg */
+	double longitude;	/* deg */
+	double altitude;	/* m above ellipsoid */
+};
+
+
+/* Reference time */
+struct gps_ref_time {
+	int wn;			/* GPS week number */
+	double tow;		/* in seconds */
+};
+
+
+/* All assist data */
+#define GPS_FIELD_IONOSPHERE	(1<<0)
+#define GPS_FIELD_UTC		(1<<1)
+#define GPS_FIELD_ALMANAC	(1<<2)
+#define GPS_FIELD_EPHEMERIS	(1<<3)
+#define GPS_FIELD_REFPOS	(1<<4)
+#define GPS_FIELD_REFTIME	(1<<5)
+
+struct gps_assist_data {
+	int fields;
+	struct gps_ionosphere_model	ionosphere;
+	struct gps_utc_model		utc;
+	struct gps_almanac		almanac;
+	struct gps_ephemeris		ephemeris;
+	struct gps_ref_pos		ref_pos;
+	struct gps_ref_time		ref_time;
+};
+
+
+/* GPS Subframe utility methods (see gps.c for details) */
+int gps_unpack_sf123(uint32_t *sf, struct gps_ephemeris_sv *eph);
+int gps_unpack_sf45_almanac(uint32_t *sf, struct gps_almanac_sv *alm);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GPS_H__ */
+
diff --git a/rrlp-ephemeris/main.c b/rrlp-ephemeris/main.c
new file mode 100644
index 0000000..bb025a2
--- /dev/null
+++ b/rrlp-ephemeris/main.c
@@ -0,0 +1,99 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "gps.h"
+#include "ubx.h"
+#include "ubx-parse.h"
+#include "rrlp.h"
+
+static int
+do_ubx_read(struct gps_assist_data *gps, const char *filename)
+{
+	int rv, fd, i;
+	struct stat st;
+	void *buf;
+
+	/* Load file */
+	fd = open(filename, O_RDONLY);
+	if (fd < 0)
+		return -1;
+
+	rv = fstat(fd, &st);
+	if (rv < 0) {
+		close(fd);
+		return -1;
+	}
+
+	buf = malloc(st.st_size);
+	if (!buf) {
+		close(fd);
+		return -1;
+	}
+
+	rv = read(fd, buf, st.st_size);
+	if (rv != st.st_size) {
+		free(buf);
+		close(fd);
+		return -1;
+	}
+
+	/* Parse each message */
+	for (i=0; i<st.st_size;) {
+		int rv;
+		rv = ubx_msg_dispatch(ubx_parse_dt, buf + i, st.st_size - i, gps);
+		if (rv < 0)
+			i++;	/* Invalid message: try one byte later */
+		else
+			i += rv;
+	}
+
+	/* Done */
+	free(buf);
+	close(fd);
+
+	return 0;
+}
+
+static int
+do_rrlp(struct gps_assist_data *gps)
+{
+	struct rrlp_assist_req ar;
+	void *pdus[64];
+	int len[64];
+	int i, rv;
+
+	char *test = "\x28\x00\x80\x10\x01\x32\x00\x19\x4F\x07\x15\x04";
+
+	rrlp_decode_assistance_request(&ar, test, 12);
+	printf("%08x %016llx\n", ar.req_elems, (long long unsigned) ar.eph_svs);
+
+	ar.req_elems = -1;
+	ar.eph_svs = -1LL;
+	rv = rrlp_gps_assist_pdus(gps, &ar, pdus, len, 64);
+	printf("%d\n", rv);
+	for (i=0; i<rv; i++) {
+		printf("%p %d\n", pdus[i], len[i]);
+	}
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	struct gps_assist_data gps;
+	int rv;
+
+	memset(&gps, 0x00, sizeof(gps));
+
+	rv = do_ubx_read(&gps, "data.ubx");
+	
+	rv = do_rrlp(&gps);
+
+	return 0;
+}
+
diff --git a/rrlp-ephemeris/rrlp.c b/rrlp-ephemeris/rrlp.c
new file mode 100644
index 0000000..e60c3ab
--- /dev/null
+++ b/rrlp-ephemeris/rrlp.c
@@ -0,0 +1,648 @@
+/*
+ * rrlp.c
+ *
+ * RRLP implementation
+ *
+ *
+ * Copyright (C) 2009  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <errno.h>
+#include <math.h>
+
+#include "gps.h"
+#include "rrlp.h"
+
+#include <PDU.h>
+#include <GPS-AssistData.h>
+#include <NavigationModel.h>
+#include <IonosphericModel.h>
+#include <UTCModel.h>
+#include <Almanac.h>
+#include <RefLocation.h>
+#include <ReferenceTime.h>
+
+
+/* ------------------------------------------------------------------------ */
+/* RRLP Assistance request decoding                                         */
+/* ---------------------------------------------------------------------{{{ */
+/* Decode and validate the assistance data request messages.
+ * See section 10.10 of
+ *  . ETSI TS 149 031 V8.1.0 (2009-01)
+ *  . 3GPP TS 49.031 version 8.1.0 Release 8
+ */
+
+/* Packed structure from 49.031 spec (RGA = Request GPS Assistance) */
+
+#define RRLP_RGA0_ALMANAC	(1<<0)
+#define RRLP_RGA0_UTC_MODEL	(1<<1)
+#define RRLP_RGA0_IONO_MODEL	(1<<2)
+#define RRLP_RGA0_NAV_MODEL	(1<<3)
+#define RRLP_RGA0_DGPS		(1<<4)
+#define RRLP_RGA0_REF_LOC	(1<<5)
+#define RRLP_RGA0_REF_TIME	(1<<6)
+#define RRLP_RGA0_ACQ_ASSIST	(1<<7)
+
+#define RRLP_RGA1_REALTIME_INT	(1<<0)
+#define RRLP_RGA1_EPH_EXT	(1<<1)
+#define RRLP_RGA1_EPH_EXT_CHECK	(1<<2)
+
+struct rrlp_rga_hdr {
+	uint8_t items0;
+	uint8_t items1;
+} __attribute__((packed));
+
+struct rrlp_rga_eph_sv {
+	uint8_t sv_id;		/* [7:6] reserved, [5:0] sv_id */
+	uint8_t iode;		/* latest eph in the MS memory in hours */
+} __attribute__((packed));
+
+struct rrlp_rga_eph {
+	uint8_t wn_hi;		/* [7:6] = wn[9:8] */
+	uint8_t wn_lo;		/* wn[7:0] */
+	uint8_t toe;		/* latest eph in the MS memory in hours */
+	uint8_t nsat_tmtoe;	/* [7:4] nstat, [3:0] T-Toe limit */
+	struct rrlp_rga_eph_sv svs[0];
+} __attribute__((packed));
+
+struct rrlp_rga_eph_ext {
+	uint8_t validity;	/* in 4 hours units */
+} __attribute__((packed));
+
+struct rrlp_rga_eph_ext_check {
+		/* weeks are in gps week modulo 4 */
+	uint8_t wn_begin_end;	/* [7:4] begin, [3:0] end */
+	uint8_t tow_begin;
+	uint8_t tow_end;
+} __attribute__((packed));
+
+
+/* Parsing function */
+
+int
+rrlp_decode_assistance_request(
+	struct rrlp_assist_req *ar,
+	void *req, int req_len)
+{
+	struct rrlp_rga_hdr *hdr = NULL;
+	struct rrlp_rga_eph *eph = NULL;
+	struct rrlp_rga_eph_ext *eph_ext = NULL;
+	struct rrlp_rga_eph_ext_check *eph_ext_check = NULL;
+	int p = 0;
+
+	/* Reset */
+	ar->req_elems = 0;
+	ar->eph_svs = 0;
+
+	/* Parse message */
+	hdr = req;
+	p += sizeof(struct rrlp_rga_hdr);
+	if (p > req_len)
+		return -1;
+
+	if (hdr->items0 & RRLP_RGA0_NAV_MODEL) {
+		eph = req + p;
+		p += sizeof(struct rrlp_rga_eph);
+		if (p > req_len)
+			return -1;
+		p += (eph->nsat_tmtoe >> 4) * sizeof(struct rrlp_rga_eph_sv);
+		if (p > req_len)
+			return -1;
+	}
+
+	if (hdr->items1 & RRLP_RGA1_EPH_EXT) {
+		eph_ext = req + p;
+		p += sizeof(struct rrlp_rga_eph_ext);
+		if (p > req_len)
+			return -1;
+	}
+
+	if (hdr->items1 & RRLP_RGA1_EPH_EXT_CHECK) {
+		eph_ext_check = req + p;
+		p += sizeof(struct rrlp_rga_eph_ext_check);
+		if (p > req_len)
+			return -1;
+	}
+
+	if (p != req_len)
+		return -2; /* not all bytes consumed ??? */
+
+	/* Print a warning for unsupported requests */
+	if ((eph_ext != NULL) ||
+	    (eph_ext_check != NULL) ||
+	    (hdr->items0 & (RRLP_RGA0_DGPS | RRLP_RGA0_ACQ_ASSIST)) ||
+	    (hdr->items1 & RRLP_RGA1_REALTIME_INT)) {
+		fprintf(stderr, "[w] Unsupported assistance data requested, ignored ...\n");
+	}
+
+	/* Copy the request */
+	if (hdr->items0 & RRLP_RGA0_ALMANAC)
+		ar->req_elems |= RRLP_AR_ALMANAC;
+
+	if (hdr->items0 & RRLP_RGA0_UTC_MODEL)
+		ar->req_elems |= RRLP_AR_UTC_MODEL;
+
+	if (hdr->items0 & RRLP_RGA0_IONO_MODEL)
+		ar->req_elems |= RRLP_AR_IONO_MODEL;
+
+	if (hdr->items0 & RRLP_RGA0_REF_LOC)
+		ar->req_elems |= RRLP_AR_REF_LOC;
+
+	if (hdr->items0 & RRLP_RGA0_REF_TIME)
+		ar->req_elems |= RRLP_AR_REF_TIME;
+
+	if (hdr->items0 & RRLP_RGA0_NAV_MODEL) {
+		int i, n_svs = eph->nsat_tmtoe >> 4;
+		ar->req_elems |= RRLP_AR_EPHEMERIS;
+		for (i=0; i<n_svs; i++)
+			ar->eph_svs |= (1ULL << (eph->svs[i].sv_id - 1));
+	}
+
+	return 0;
+}
+
+/* }}} */
+
+
+/* ------------------------------------------------------------------------ */
+/* RRLP elements fill                                                       */
+/* ---------------------------------------------------------------------{{{ */
+
+	/* Helpers */
+
+static void
+_ts_23_032_store_latitude(double lat, uint8_t *b)
+{
+	uint32_t x;
+	x = (uint32_t) floor(fabs(lat/90.0) * ((double)(1<<23)));
+	if (x >= (1<<23))
+		x = (1<<23) - 1;
+	if (lat < 0.0)
+		x |= (1<<23);
+	b[0] = (x >> 16) & 0xff;
+	b[1] = (x >>  8) & 0xff;
+	b[2] =  x        & 0xff;
+}
+
+static void
+_ts_23_032_store_longitude(double lon, uint8_t *b)
+{
+	int32_t x;
+	x = floor((lon/360.0) * ((double)(1<<24)));
+	if (x >= (1<<23))
+		x = 0x007fffff;
+	else if (x < -(1<<23))
+		x = 0x00800000;
+	b[0] = (x >> 16) & 0xff;
+	b[1] = (x >>  8) & 0xff;
+	b[2] =  x        & 0xff;
+}
+
+static void
+_ts_23_032_store_altitude(double alt, uint8_t *b)
+{
+	int alt_i = (int)fabs(alt);
+	b[0] = ((alt_i >> 8) & 0x7f) | (alt<0.0 ? 0x80 : 0x00);
+	b[1] = alt_i & 0xff;
+}
+
+
+	/* Fill methods */
+
+static void
+_rrlp_fill_navigation_model_element(
+	struct NavModelElement *rrlp_nme,
+	struct gps_ephemeris_sv *gps_eph_sv)
+{
+	struct UncompressedEphemeris *rrlp_eph;
+
+	rrlp_nme->satStatus.present = SatStatus_PR_newSatelliteAndModelUC;
+	rrlp_nme->satelliteID = gps_eph_sv->sv_id;
+
+	rrlp_eph = &rrlp_nme->satStatus.choice.newSatelliteAndModelUC;
+
+	rrlp_eph->ephemCodeOnL2   = gps_eph_sv->code_on_l2;
+	rrlp_eph->ephemURA        = gps_eph_sv->sv_ura;
+	rrlp_eph->ephemSVhealth   = gps_eph_sv->sv_health;
+	rrlp_eph->ephemIODC       = gps_eph_sv->iodc;
+	rrlp_eph->ephemL2Pflag    = gps_eph_sv->l2_p_flag;
+	rrlp_eph->ephemTgd        = gps_eph_sv->t_gd;
+	rrlp_eph->ephemToc        = gps_eph_sv->t_oc;
+	rrlp_eph->ephemAF2        = gps_eph_sv->a_f2;
+	rrlp_eph->ephemAF1        = gps_eph_sv->a_f1;
+	rrlp_eph->ephemAF0        = gps_eph_sv->a_f0;
+	rrlp_eph->ephemCrs        = gps_eph_sv->c_rs;
+	rrlp_eph->ephemDeltaN     = gps_eph_sv->delta_n;
+	rrlp_eph->ephemM0         = gps_eph_sv->m_0;
+	rrlp_eph->ephemCuc        = gps_eph_sv->c_uc;
+	rrlp_eph->ephemE          = gps_eph_sv->e;
+	rrlp_eph->ephemCus        = gps_eph_sv->c_us;
+	rrlp_eph->ephemAPowerHalf = gps_eph_sv->a_powhalf;
+	rrlp_eph->ephemToe        = gps_eph_sv->t_oe;
+	rrlp_eph->ephemFitFlag    = gps_eph_sv->fit_flag;
+	rrlp_eph->ephemAODA       = gps_eph_sv->aodo;
+	rrlp_eph->ephemCic        = gps_eph_sv->c_ic;
+	rrlp_eph->ephemOmegaA0    = gps_eph_sv->omega_0;
+	rrlp_eph->ephemCis        = gps_eph_sv->c_is;
+	rrlp_eph->ephemI0         = gps_eph_sv->i_0;
+	rrlp_eph->ephemCrc        = gps_eph_sv->c_rc;
+	rrlp_eph->ephemW          = gps_eph_sv->w;
+	rrlp_eph->ephemOmegaADot  = gps_eph_sv->omega_dot;
+	rrlp_eph->ephemIDot       = gps_eph_sv->idot;
+
+	rrlp_eph->ephemSF1Rsvd.reserved1 = gps_eph_sv->_rsvd1;
+	rrlp_eph->ephemSF1Rsvd.reserved2 = gps_eph_sv->_rsvd2;
+	rrlp_eph->ephemSF1Rsvd.reserved3 = gps_eph_sv->_rsvd3;
+	rrlp_eph->ephemSF1Rsvd.reserved4 = gps_eph_sv->_rsvd4;
+}
+
+static void
+_rrlp_fill_almanac_element(
+	struct AlmanacElement *rrlp_ae,
+	struct gps_almanac_sv *gps_alm_sv)
+{
+	rrlp_ae->satelliteID = gps_alm_sv->sv_id;
+
+	rrlp_ae->almanacE          = gps_alm_sv->e;
+	rrlp_ae->alamanacToa       = gps_alm_sv->t_oa;
+	rrlp_ae->almanacKsii       = gps_alm_sv->ksii;
+	rrlp_ae->almanacOmegaDot   = gps_alm_sv->omega_dot;
+	rrlp_ae->almanacSVhealth   = gps_alm_sv->sv_health;
+	rrlp_ae->almanacAPowerHalf = gps_alm_sv->a_powhalf;
+	rrlp_ae->almanacOmega0     = gps_alm_sv->omega_0;
+	rrlp_ae->almanacW          = gps_alm_sv->w;
+	rrlp_ae->almanacM0         = gps_alm_sv->m_0;
+	rrlp_ae->almanacAF0        = gps_alm_sv->a_f0;
+	rrlp_ae->almanacAF1        = gps_alm_sv->a_f1;
+
+}
+
+static void
+_rrlp_fill_ionospheric_model(
+	struct IonosphericModel *rrlp_iono,
+	struct gps_ionosphere_model *gps_iono)
+{
+	rrlp_iono->alfa0 = gps_iono->alpha_0;
+	rrlp_iono->alfa1 = gps_iono->alpha_1;
+	rrlp_iono->alfa2 = gps_iono->alpha_2;
+	rrlp_iono->alfa3 = gps_iono->alpha_3;
+	rrlp_iono->beta0 = gps_iono->beta_0;
+	rrlp_iono->beta1 = gps_iono->beta_1;
+	rrlp_iono->beta2 = gps_iono->beta_2;
+	rrlp_iono->beta3 = gps_iono->beta_3;
+}
+
+static void
+_rrlp_fill_utc_model(
+	struct UTCModel *rrlp_utc,
+	struct gps_utc_model *gps_utc)
+{
+	rrlp_utc->utcA1        = gps_utc->a1;
+	rrlp_utc->utcA0        = gps_utc->a0;
+	rrlp_utc->utcTot       = gps_utc->t_ot;
+	rrlp_utc->utcWNt       = gps_utc->wn_t & 0xff;
+	rrlp_utc->utcDeltaTls  = gps_utc->delta_t_ls;
+	rrlp_utc->utcWNlsf     = gps_utc->wn_lsf & 0xff;
+	rrlp_utc->utcDN        = gps_utc->dn;
+	rrlp_utc->utcDeltaTlsf = gps_utc->delta_t_lsf;
+}
+
+/* }}} */
+
+
+/* ------------------------------------------------------------------------ */
+/* RRLP Assistance PDU Generation                                           */
+/* ---------------------------------------------------------------------{{{ */
+
+struct PDU *
+_rrlp_create_gps_assist_pdu(int refnum, struct GPS_AssistData **o_gps_ad)
+{
+	struct PDU *pdu;
+	struct GPS_AssistData *gps_ad;
+
+	pdu = calloc(1, sizeof(*pdu));
+	if (!pdu)
+		return NULL;
+
+	gps_ad = calloc(1, sizeof(*gps_ad));
+	if (!gps_ad) {
+		free(pdu);
+		return NULL;
+	}
+
+	if (o_gps_ad)
+		*o_gps_ad = gps_ad;
+
+	pdu->referenceNumber = refnum;
+	pdu->component.present = RRLP_Component_PR_assistanceData;
+	pdu->component.choice.assistanceData.gps_AssistData = gps_ad;
+
+	return pdu;
+}
+
+static int
+_rrlp_add_ionospheric_model(
+	struct GPS_AssistData *rrlp_gps_ad,
+	struct gps_assist_data *gps_ad)
+{
+	struct IonosphericModel *rrlp_iono;
+
+	if (!(gps_ad->fields & GPS_FIELD_IONOSPHERE))
+		return -EINVAL;
+
+	rrlp_iono = calloc(1, sizeof(*rrlp_iono));
+	if (!rrlp_iono)
+		return -ENOMEM;
+	rrlp_gps_ad->controlHeader.ionosphericModel = rrlp_iono;
+
+	_rrlp_fill_ionospheric_model(rrlp_iono, &gps_ad->ionosphere);
+
+	return 0;
+}
+
+static int
+_rrlp_add_utc_model(
+	struct GPS_AssistData *rrlp_gps_ad,
+	struct gps_assist_data *gps_ad)
+{
+	struct UTCModel *rrlp_utc;
+
+	if (!(gps_ad->fields & GPS_FIELD_UTC))
+		return -EINVAL;
+
+	rrlp_utc = calloc(1, sizeof(*rrlp_utc));
+	if (!rrlp_utc)
+		return -ENOMEM;
+	rrlp_gps_ad->controlHeader.utcModel = rrlp_utc;
+
+	_rrlp_fill_utc_model(rrlp_utc, &gps_ad->utc);
+
+	return 0;
+}
+
+static int
+_rrlp_add_reference_location(
+	struct GPS_AssistData *rrlp_gps_ad,
+	struct gps_assist_data *gps_ad)
+{
+	struct RefLocation *rrlp_refloc;
+	uint8_t *b;
+
+	if (!(gps_ad->fields & GPS_FIELD_REFPOS))
+		return -EINVAL;
+
+	rrlp_refloc = calloc(1, sizeof(*rrlp_refloc));
+	if (!rrlp_refloc)
+		return -ENOMEM;
+	rrlp_gps_ad->controlHeader.refLocation = rrlp_refloc;
+
+	b = malloc(9);
+
+	b[0] = 0x80; /* Ellipsoid Point with altitude */
+	_ts_23_032_store_latitude(gps_ad->ref_pos.latitude, &b[1]);
+	_ts_23_032_store_longitude(gps_ad->ref_pos.longitude, &b[4]);
+	_ts_23_032_store_altitude(gps_ad->ref_pos.altitude, &b[7]);
+
+	rrlp_refloc->threeDLocation.buf = b;
+	rrlp_refloc->threeDLocation.size = 9;
+
+	return 0;
+}
+
+static int
+_rrlp_add_reference_time(
+	struct GPS_AssistData *rrlp_gps_ad,
+	struct gps_assist_data *gps_ad)
+{
+	struct ReferenceTime *rrlp_reftime;
+
+	if (!(gps_ad->fields & GPS_FIELD_REFTIME))
+		return -EINVAL;
+
+	rrlp_reftime = calloc(1, sizeof(*rrlp_reftime));
+	if (!rrlp_reftime)
+		return -ENOMEM;
+	rrlp_gps_ad->controlHeader.referenceTime = rrlp_reftime;
+
+	rrlp_reftime->gpsTime.gpsWeek   = gps_ad->ref_time.wn & 0x3ff; /* 10b */
+	rrlp_reftime->gpsTime.gpsTOW23b =
+		((int)floor(gps_ad->ref_time.tow / 0.08)) & 0x7fffff;  /* 23b */
+
+	return 0;
+}
+
+static int
+_rrlp_add_almanac(
+	struct GPS_AssistData *rrlp_gps_ad,
+	struct gps_assist_data *gps_ad, int *start, int count)
+{
+	int i;
+	struct Almanac *rrlp_alm;
+	struct gps_almanac *gps_alm = &gps_ad->almanac;
+
+	if (!(gps_ad->fields & GPS_FIELD_ALMANAC))
+		return -EINVAL;
+
+	rrlp_alm = calloc(1, sizeof(*rrlp_alm));
+	if (!rrlp_alm)
+		return -ENOMEM;
+	rrlp_gps_ad->controlHeader.almanac = rrlp_alm;
+
+	rrlp_alm->alamanacWNa = gps_alm->wna;
+	if (count == -1)
+		count = gps_alm->n_sv - *start;
+	for (i=*start; (i<*start+count) && (i<gps_alm->n_sv); i++) {
+		struct AlmanacElement *ae;
+		ae = calloc(1, sizeof(*ae));
+		if (!ae)
+			return -ENOMEM;
+		_rrlp_fill_almanac_element(ae, &gps_alm->svs[i]);
+		ASN_SEQUENCE_ADD(&rrlp_alm->almanacList.list, ae);
+	}
+
+	*start = i;
+
+	return i < gps_alm->n_sv;
+}
+
+static int
+_rrlp_add_ephemeris(
+	struct GPS_AssistData *rrlp_gps_ad,
+	struct gps_assist_data *gps_ad, int *start, int count, uint64_t mask)
+{
+	int i, j;
+	struct NavigationModel *rrlp_nav;
+	struct gps_ephemeris *gps_eph = &gps_ad->ephemeris;
+
+	if (!(gps_ad->fields & GPS_FIELD_EPHEMERIS))
+		return -EINVAL;
+
+	rrlp_nav = calloc(1, sizeof(*rrlp_nav));
+	if (!rrlp_nav)
+		return -ENOMEM;
+	rrlp_gps_ad->controlHeader.navigationModel = rrlp_nav;
+
+	if (count == -1)
+		count = gps_eph->n_sv - *start;
+	for (i=*start,j=0; (j<count) && (i<gps_eph->n_sv); i++) {
+		if (!(mask & (1ULL<<(gps_eph->svs[i].sv_id-1))))
+			continue;
+		struct NavModelElement *nme;
+		nme = calloc(1, sizeof(*nme));
+		if (!nme)
+			return -ENOMEM;
+		_rrlp_fill_navigation_model_element(nme, &gps_eph->svs[i]);
+		ASN_SEQUENCE_ADD(&rrlp_nav->navModelList.list, nme);
+		j++;
+	}
+
+	*start = i;
+
+	return i < gps_eph->n_sv;
+}
+
+
+#define MAX_PDUS 64
+
+int
+rrlp_gps_assist_pdus(
+	struct gps_assist_data *gps_ad, struct rrlp_assist_req *req,
+	void **o_pdu, int *o_len, int o_max_pdus)
+{
+	struct PDU *lst_pdu[MAX_PDUS];
+	int lst_cnt = 0;
+
+	struct PDU *rrlp_pdu = NULL;
+	struct GPS_AssistData *rrlp_gps_ad = NULL;
+	uint32_t re = req->req_elems;
+	int i, rv = 0;
+
+	/* IonosphericModel, UTCModel, RefLocation, ReferenceTime */
+	if (re & (RRLP_AR_IONO_MODEL |
+	          RRLP_AR_UTC_MODEL |
+	          RRLP_AR_REF_TIME |
+	          RRLP_AR_REF_LOC))
+	{
+		int pdu_has_data = 0;
+
+		rrlp_pdu = _rrlp_create_gps_assist_pdu(1, &rrlp_gps_ad);
+		if (!rrlp_pdu) {
+			rv = -ENOMEM;
+			goto error;
+		}
+
+		if (re & RRLP_AR_IONO_MODEL)
+			if (!_rrlp_add_ionospheric_model(rrlp_gps_ad, gps_ad))
+				pdu_has_data = 1;
+
+		if (re & RRLP_AR_UTC_MODEL)
+			if (!_rrlp_add_utc_model(rrlp_gps_ad, gps_ad))
+				pdu_has_data = 1;
+
+		if (re & RRLP_AR_REF_TIME)
+			if (!_rrlp_add_reference_time(rrlp_gps_ad, gps_ad))
+				pdu_has_data = 1;
+
+		if (re & RRLP_AR_REF_LOC)
+			if (!_rrlp_add_reference_location(rrlp_gps_ad, gps_ad))
+				pdu_has_data = 1;
+
+		if (pdu_has_data) {
+			lst_pdu[lst_cnt++] = rrlp_pdu;
+			rrlp_pdu = NULL;
+		}
+	}
+
+	/* Almanac */
+	if (re & RRLP_AR_ALMANAC) {
+		i = 0;
+		do {
+			if (!(gps_ad->fields & GPS_FIELD_ALMANAC))
+				break;
+
+			if (!rrlp_pdu) {
+				rrlp_pdu = _rrlp_create_gps_assist_pdu(1, &rrlp_gps_ad);
+				if (!rrlp_pdu) {
+					rv = -ENOMEM;
+					goto error;
+				}
+			}
+
+			rv = _rrlp_add_almanac(rrlp_gps_ad, gps_ad, &i, 10);
+			if (rv < 0)
+				goto error;
+
+			lst_pdu[lst_cnt++] = rrlp_pdu;
+			rrlp_pdu = NULL;
+		} while (rv);
+	}
+
+	/* Ephemeris */
+	if (re & RRLP_AR_EPHEMERIS) {
+		i = 0;
+		do {
+			if (!(gps_ad->fields & GPS_FIELD_EPHEMERIS))
+				break;
+
+			if (!rrlp_pdu) {
+				rrlp_pdu = _rrlp_create_gps_assist_pdu(1, &rrlp_gps_ad);
+				if (!rrlp_pdu) {
+					rv = -ENOMEM;
+					goto error;
+				}
+			}
+
+			rv = _rrlp_add_ephemeris(rrlp_gps_ad, gps_ad, &i, 2, req->eph_svs);
+
+			lst_pdu[lst_cnt++] = rrlp_pdu;
+			rrlp_pdu = NULL;
+
+		} while (rv);
+	}
+
+	/* Serialize & Release all PDUs */
+	for (i=0; i<lst_cnt && i<o_max_pdus; i++) {
+		/* Pseudo segmentation flags */
+		MoreAssDataToBeSent_t *mad = calloc(1, sizeof(*mad));
+		*mad = (i == (lst_cnt-1)) ?
+				MoreAssDataToBeSent_noMoreMessages :
+				MoreAssDataToBeSent_moreMessagesOnTheWay;
+		lst_pdu[i]->component.choice.assistanceData.moreAssDataToBeSent = mad;
+
+		/* Serialization */
+		// asn_fprint(stdout, &asn_DEF_PDU, lst_pdu[i]);
+		rv = uper_encode_to_new_buffer(&asn_DEF_PDU, NULL, lst_pdu[i], &o_pdu[i]);
+		if (rv < 0)
+			goto error;
+		o_len[i] = rv;
+	}
+
+	rv = lst_cnt;
+
+	/* Release ASN.1 objects */
+error:
+	if (rrlp_pdu)
+		asn_DEF_PDU.free_struct(&asn_DEF_PDU, (void*)rrlp_pdu, 0);
+
+	for (i=0; i<lst_cnt; i++)
+		asn_DEF_PDU.free_struct(&asn_DEF_PDU, lst_pdu[i], 0);
+
+	return rv;
+}
+
+/* }}} */
+
diff --git a/rrlp-ephemeris/rrlp.h b/rrlp-ephemeris/rrlp.h
new file mode 100644
index 0000000..a5e4344
--- /dev/null
+++ b/rrlp-ephemeris/rrlp.h
@@ -0,0 +1,64 @@
+/*
+ * rrlp.h
+ *
+ * RRLP Header
+ *
+ *
+ * Copyright (C) 2009  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __RRLP_H__
+#define __RRLP_H__
+
+#include <stdint.h>
+
+#include "gps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Our internal simplified structure for requests */
+
+#define RRLP_AR_REF_LOC		(1<<0)
+#define RRLP_AR_REF_TIME	(1<<1)
+#define RRLP_AR_UTC_MODEL	(1<<2)
+#define RRLP_AR_IONO_MODEL	(1<<3)
+#define RRLP_AR_ALMANAC		(1<<4)
+#define RRLP_AR_EPHEMERIS	(1<<5)
+
+struct rrlp_assist_req {
+	uint32_t req_elems;
+	uint64_t eph_svs;
+};
+
+
+/* Methods */
+int rrlp_decode_assistance_request(struct rrlp_assist_req *ar,
+	void *req, int req_len);
+
+int rrlp_gps_assist_pdus(
+	struct gps_assist_data *gps_ad, struct rrlp_assist_req *req,
+	void **o_pdu, int *o_len, int o_max_pdus);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __RRLP_H__ */
+
diff --git a/rrlp-ephemeris/ubx-parse.c b/rrlp-ephemeris/ubx-parse.c
new file mode 100644
index 0000000..c3d0f70
--- /dev/null
+++ b/rrlp-ephemeris/ubx-parse.c
@@ -0,0 +1,177 @@
+/*
+ * ubx-parse.c
+ *
+ * Implementation of parsing code converting UBX messages to GPS assist
+ * data
+ *
+ *
+ * Copyright (C) 2009  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+
+#include "gps.h"
+#include "ubx.h"
+#include "ubx-parse.h"
+
+
+/* Helpers */
+
+static int
+float_to_fixedpoint(float f, int sf)
+{
+	if (sf < 0) {
+		while (sf++ < 0)
+			f *= 2.0f;
+	} else {
+		while (sf-- > 0)
+			f *= 0.5f;
+	}
+
+	return (int)f;
+}
+
+static inline int
+double_to_fixedpoint(double d, int sf)
+{
+	if (sf < 0) {
+		while (sf++ < 0)
+			d *= 2.0;
+	} else {
+		while (sf-- > 0)
+			d *= 0.5;
+	}
+
+	return (int)d;
+}
+
+
+/* UBX message parsing to fill gps assist data */
+
+static void
+_ubx_msg_parse_nav_posllh(struct ubx_hdr *hdr, void *pl, int pl_len, void *ud)
+{
+	struct ubx_nav_posllh *nav_posllh = pl;
+	struct gps_assist_data *gps = ud;
+
+	//printf("[.] NAV_POSLLH\n");
+
+	gps->fields |= GPS_FIELD_REFPOS;
+
+	gps->ref_pos.latitude  = (double)(nav_posllh->lat) * 1e-7;
+	gps->ref_pos.longitude = (double)(nav_posllh->lon) * 1e-7;
+	gps->ref_pos.altitude  = (double)(nav_posllh->height) * 1e-3;
+}
+
+static void
+_ubx_msg_parse_aid_ini(struct ubx_hdr *hdr, void *pl, int pl_len, void *ud)
+{
+	struct ubx_aid_ini *aid_ini = pl;
+	struct gps_assist_data *gps = ud;
+
+	//printf("[.] AID_INI\n");
+
+	/* Extract info for "Reference Time" */
+	gps->fields |= GPS_FIELD_REFTIME;
+
+	gps->ref_time.wn = aid_ini->wn;
+	gps->ref_time.tow = (double)aid_ini->tow * 1e-3;
+
+	// FIXME: We could extract ref position as well but we need it in
+	//        WGS84 geodetic coordinates and it's provided as ecef, so
+	//        we need a lot of math ...
+}
+
+static void
+_ubx_msg_parse_aid_hui(struct ubx_hdr *hdr, void *pl, int pl_len, void *ud)
+{
+	struct ubx_aid_hui *aid_hui = pl;
+	struct gps_assist_data *gps = ud;
+
+	//printf("[.] AID_HUI\n");
+
+	if (aid_hui->flags & 0x2) { /* UTC parameters valid */
+		struct gps_utc_model *utc = &gps->utc;
+
+		gps->fields |= GPS_FIELD_UTC;
+
+		utc->a0          = double_to_fixedpoint(aid_hui->utc_a0, -30);
+		utc->a1          = double_to_fixedpoint(aid_hui->utc_a1, -50);
+		utc->delta_t_ls  = aid_hui->utc_ls;
+		utc->t_ot        = aid_hui->utc_tot >> 12;
+		utc->wn_t        = aid_hui->utc_wnt;
+		utc->wn_lsf      = aid_hui->utc_wnf;
+		utc->dn          = aid_hui->utc_dn;
+		utc->delta_t_lsf = aid_hui->utc_lsf;
+	}
+
+	if (aid_hui->flags & 0x04) { /* Klobuchar parameters valid */
+		struct gps_ionosphere_model *iono = &gps->ionosphere;
+
+		gps->fields |= GPS_FIELD_IONOSPHERE;
+
+		iono->alpha_0 = float_to_fixedpoint(aid_hui->klob_a0, -30);
+		iono->alpha_1 = float_to_fixedpoint(aid_hui->klob_a1, -27);
+		iono->alpha_2 = float_to_fixedpoint(aid_hui->klob_a2, -24);
+		iono->alpha_3 = float_to_fixedpoint(aid_hui->klob_a3, -24);
+		iono->beta_0 = float_to_fixedpoint(aid_hui->klob_b0, 11);
+		iono->beta_1 = float_to_fixedpoint(aid_hui->klob_b1, 14);
+		iono->beta_2 = float_to_fixedpoint(aid_hui->klob_b2, 16);
+		iono->beta_3 = float_to_fixedpoint(aid_hui->klob_b3, 16);
+	}
+}
+
+static void
+_ubx_msg_parse_aid_alm(struct ubx_hdr *hdr, void *pl, int pl_len, void *ud)
+{
+	struct ubx_aid_alm *aid_alm = pl;
+	struct gps_assist_data *gps = ud;
+
+	//printf("[.] AID_ALM %d - %d\n", aid_alm->sv_id, aid_alm->gps_week);
+
+	if (aid_alm->gps_week) {
+		gps->fields |= GPS_FIELD_ALMANAC;
+		gps->almanac.wna = aid_alm->gps_week & 0xff;
+		gps_unpack_sf45_almanac(aid_alm->alm_words, &gps->almanac.svs[gps->almanac.n_sv++]);
+	}
+}
+
+static void
+_ubx_msg_parse_aid_eph(struct ubx_hdr *hdr, void *pl, int pl_len, void *ud)
+{
+	struct ubx_aid_eph *aid_eph = pl;
+	struct gps_assist_data *gps = ud;
+
+	//printf("[.] AID_EPH %d - %s\n", aid_eph->sv_id, aid_eph->present ? "present" : "not present");
+
+	if (aid_eph->present) {
+		int i = gps->ephemeris.n_sv++;
+		gps->fields |= GPS_FIELD_EPHEMERIS;
+		gps->ephemeris.svs[i].sv_id = aid_eph->sv_id;
+		gps_unpack_sf123(aid_eph->eph_words, &gps->ephemeris.svs[i]);
+	}
+}
+
+
+/* Dispatch table */
+struct ubx_dispatch_entry ubx_parse_dt[] = {
+	UBX_DISPATCH(NAV, POSLLH, _ubx_msg_parse_nav_posllh),
+	UBX_DISPATCH(AID, INI, _ubx_msg_parse_aid_ini),
+	UBX_DISPATCH(AID, HUI, _ubx_msg_parse_aid_hui),
+	UBX_DISPATCH(AID, ALM, _ubx_msg_parse_aid_alm),
+	UBX_DISPATCH(AID, EPH, _ubx_msg_parse_aid_eph),
+};
+
diff --git a/rrlp-ephemeris/ubx-parse.h b/rrlp-ephemeris/ubx-parse.h
new file mode 100644
index 0000000..621475d
--- /dev/null
+++ b/rrlp-ephemeris/ubx-parse.h
@@ -0,0 +1,45 @@
+/*
+ * ubx-parse.h
+ *
+ * Header for parsing code converting UBX messages to GPS assist data
+ *
+ *
+ * Copyright (C) 2009  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBX_PARSE_H__
+#define __UBX_PARSE_H__
+
+
+#include "gps.h"
+#include "ubx.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Dispatch table */
+extern struct ubx_dispatch_entry ubx_parse_dt[];
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __UBX_PARSE_H__ */
+
diff --git a/rrlp-ephemeris/ubx.c b/rrlp-ephemeris/ubx.c
new file mode 100644
index 0000000..83dd1f0
--- /dev/null
+++ b/rrlp-ephemeris/ubx.c
@@ -0,0 +1,81 @@
+/*
+ * ubx.c
+ *
+ * Implementation of generic UBX helpers
+ *
+ *
+ * Copyright (C) 2009  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include "ubx.h"
+
+
+static void
+ubx_checksum(uint8_t *data, int len, uint8_t *cksum)
+{
+	int i;
+	uint8_t ck0 = 0, ck1 = 0;
+	for (i=0; i<len; i++) {
+		ck0 += data[i];
+		ck1 += ck0;
+	}
+	cksum[0] = ck0;
+	cksum[1] = ck1;
+}
+
+
+static ubx_msg_handler_t
+ubx_find_handler(struct ubx_dispatch_entry *dt, uint8_t msg_class, uint8_t msg_id)
+{
+	while (dt->handler) {
+		if ((dt->msg_class == msg_class) && (dt->msg_id == msg_id))
+			return dt->handler;
+		dt++;
+	}
+	return NULL;
+}
+
+
+int
+ubx_msg_dispatch(struct ubx_dispatch_entry *dt,
+                 void *msg, int len, void *userdata)
+{
+	struct ubx_hdr *hdr = msg;
+	uint8_t cksum[2], *cksum_ptr;
+	ubx_msg_handler_t h;
+
+	if ((hdr->sync[0] != UBX_SYNC0) || (hdr->sync[1] != UBX_SYNC1)) {
+		fprintf(stderr, "[!] Invalid sync bytes\n");
+		return -1;
+	}
+
+	ubx_checksum(msg + 2, sizeof(struct ubx_hdr) + hdr->payload_len - 2, cksum);
+	cksum_ptr = msg + (sizeof(struct ubx_hdr) + hdr->payload_len);
+	if ((cksum_ptr[0] != cksum[0]) || (cksum_ptr[1] != cksum[1])) {
+		fprintf(stderr, "[!] Invalid checksum\n");
+		return -1;
+	}
+
+	h = ubx_find_handler(dt, hdr->msg_class, hdr->msg_id);
+	if (h)
+		h(hdr, msg + sizeof(struct ubx_hdr), hdr->payload_len, userdata);
+
+	return sizeof(struct ubx_hdr) + hdr->payload_len + 2;
+}
+
diff --git a/rrlp-ephemeris/ubx.h b/rrlp-ephemeris/ubx.h
new file mode 100644
index 0000000..8264386
--- /dev/null
+++ b/rrlp-ephemeris/ubx.h
@@ -0,0 +1,232 @@
+/*
+ * ubx.h
+ *
+ * Header for UBX related stuff
+ *
+ *
+ * Copyright (C) 2009  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBX_H__
+#define __UBX_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+/* Constants used in UBX */
+
+	/* Sync bytes (two first bytes of each message) */
+#define UBX_SYNC0		0xb5
+#define UBX_SYNC1		0x62
+
+	/* UBX messages classes */
+#define UBX_CLASS_NAV		0x01
+#define UBX_CLASS_RXM		0x02
+#define UBX_CLASS_INF		0x04
+#define UBX_CLASS_ACK		0x05
+#define UBX_CLASS_CFG		0x06
+#define UBX_CLASS_UPD		0x09
+#define UBX_CLASS_MON		0x0a
+#define UBX_CLASS_AID		0x0b
+#define UBX_CLASS_TIM		0x0d
+
+	/* UBX messages type ID (by class) */
+#define UBX_NAV_POSECEF		0x01
+#define UBX_NAV_POSLLH		0x02
+#define UBX_NAV_STATUS		0x03
+#define UBX_NAV_DOP		0x04
+#define UBX_NAV_SOL		0x06
+#define UBX_NAV_POSUTM		0x08
+#define UBX_NAV_VELECEF		0x11
+#define UBX_NAV_VELNED		0x12
+#define UBX_NAV_TIMEGPS		0x20
+#define UBX_NAV_TIMEUTC		0x21
+#define UBX_NAV_CLOCK		0x22
+#define UBX_NAV_SVINFO		0x30
+#define UBX_NAV_DGPS		0x31
+#define UBX_NAV_SBAS		0x32
+#define UBX_NAV_EKFSTATUS	0x40
+
+#define UBX_RXM_RAW		0x10
+#define UBX_RXM_SFRB		0x11
+#define UBX_RXM_SVSI		0x20
+#define UBX_RXM_SVSI_GPS	0x20
+#define UBX_RXM_ALM		0x30
+#define UBX_RXM_EPH		0x31
+#define UBX_RXM_POSREQ		0x40
+
+#define UBX_INF_ERROR		0x00
+#define UBX_INF_WARNING		0x01
+#define UBX_INF_NOTICE		0x02
+#define UBX_INF_TEST		0x03
+#define UBX_INF_DEBUG		0x04
+#define UBX_INF_USER		0x07
+
+#define UBX_ACK_NAK		0x00
+#define UBX_ACK_ACK		0x01
+
+#define UBX_CFG_PRT		0x00
+#define UBX_CFG_USB		0x1b
+#define UBX_CFG_MSG		0x01
+#define UBX_CFG_NMEA		0x17
+#define UBX_CFG_RATE		0x08
+#define UBX_CFG_CFG		0x09
+#define UBX_CFG_TP		0x07
+#define UBX_CFG_NAV2		0x1a
+#define UBX_CFG_DAT		0x06
+#define UBX_CFG_INF		0x02
+#define UBX_CFG_RST		0x04
+#define UBX_CFG_RXM		0x11
+#define UBX_CFG_ANT		0x13
+#define UBX_CFG_FXN		0x0e
+#define UBX_CFG_SBAS		0x16
+#define UBX_CFG_LIC		0x80
+#define UBX_CFG_TM		0x10
+#define UBX_CFG_TM2		0x19
+#define UBX_CFG_TMODE		0x1d
+#define UBX_CFG_EKF		0x12
+
+#define UBX_UPD_DOWNL		0x01
+#define UBX_UPD_UPLOAD		0x02
+#define UBX_UPD_EXEC		0x03
+#define UBX_UPD_MEMCPY		0x04
+
+#define UBX_MON_SCHD		0x01
+#define UBX_MON_IO		0x02
+#define UBX_MON_IPC		0x03
+#define UBX_MON_VER		0x04
+#define UBX_MON_EXCEPT		0x05
+#define UBX_MON_MSGPP		0x06
+#define UBX_MON_RXBUF		0x07
+#define UBX_MON_TXBUF		0x08
+#define UBX_MON_HW		0x09
+#define UBX_MON_USB		0x0a
+
+#define UBX_AID_REQ		0x00
+#define UBX_AID_INI		0x01
+#define UBX_AID_HUI		0x02
+#define UBX_AID_DATA		0x10
+#define UBX_AID_ALM		0x30
+#define UBX_AID_EPH		0x31
+
+#define UBX_TIM_TP		0x01
+#define UBX_TIM_TM		0x02
+#define UBX_TIM_TM2		0x03
+#define UBX_TIM_SVIN		0x04
+
+
+/* Header */
+struct ubx_hdr {
+        uint8_t  sync[2];
+        uint8_t  msg_class;
+        uint8_t  msg_id;
+        uint16_t payload_len;
+} __attribute__((packed));
+
+
+/* Payload formats (some of them) */
+struct ubx_nav_posllh {
+	uint32_t itow;
+	int32_t  lon;	/* scaling 1e-7 */
+	int32_t  lat;	/* scaling 1e-7 */
+	int32_t  height;/* mm */
+	int32_t  hsl;	/* mm */
+	uint32_t hacc;	/* mm */
+	uint32_t vacc;	/* mm */
+} __attribute__((packed));
+
+struct ubx_aid_ini {
+	int32_t  x;
+	int32_t  y;
+	int32_t  z;
+	uint32_t posacc;
+	uint16_t tm_cfg;
+	uint16_t wn;
+	uint32_t tow;
+	int32_t  tow_ns;
+	uint32_t tacc_ms;
+	uint32_t tacc_ns;
+	int32_t  clkd;
+	uint32_t clkdacc;
+	uint32_t flags;
+} __attribute__((packed));
+
+struct ubx_aid_hui {
+	uint32_t health;
+	double   utc_a1;
+	double   utc_a0;
+	int32_t  utc_tot;
+	int16_t  utc_wnt;
+	int16_t  utc_ls;
+	int16_t  utc_wnf;
+	int16_t  utc_dn;
+	int16_t  utc_lsf;
+	int16_t  utc_spare;
+	float    klob_a0;
+	float    klob_a1;
+	float    klob_a2;
+	float    klob_a3;
+	float    klob_b0;
+	float    klob_b1;
+	float    klob_b2;
+	float    klob_b3;
+	uint32_t flags;
+} __attribute__((packed));
+
+struct ubx_aid_alm {
+	uint32_t sv_id;
+	uint32_t gps_week;
+	uint32_t alm_words[8];	/* Present only if 'gps_week' != 0 */
+} __attribute__((packed));
+
+struct ubx_aid_eph {
+	uint32_t sv_id;
+	uint32_t present;
+	uint32_t eph_words[24];	/* Present only if 'present' != 0 */
+} __attribute__((packed));
+
+
+/* Message handler */
+typedef void (*ubx_msg_handler_t)(
+	struct ubx_hdr *hdr, void *payload, int payload_len, void *userdata);
+
+struct ubx_dispatch_entry {
+	uint8_t msg_class;
+	uint8_t msg_id;
+	ubx_msg_handler_t handler;
+};
+
+#define UBX_DISPATCH(kls,id,hdl) {		\
+	.msg_class = UBX_CLASS_ ## kls ,	\
+	.msg_id = UBX_ ## kls ## _ ## id,	\
+	.handler = (hdl),			\
+}
+
+
+/* Methods */
+int ubx_msg_dispatch(struct ubx_dispatch_entry *dt,
+                     void *msg, int len, void *userdata);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __UBX_H__ */
+
diff --git a/wireshark/abis_oml.patch b/wireshark/abis_oml.patch
new file mode 100644
index 0000000..4013211
--- /dev/null
+++ b/wireshark/abis_oml.patch
@@ -0,0 +1,2209 @@
+From 5857518be87641fdab45e593bc9fd5ef5595e619 Mon Sep 17 00:00:00 2001
+From: Holger Hans Peter Freyther <zecke@selfish.org>
+Date: Mon, 19 Apr 2010 13:23:51 +0800
+Subject: [PATCH 1/2] Add the Abis OML patch.
+
+---
+ epan/dissectors/Makefile.common       |    1 +
+ epan/dissectors/packet-gsm_abis_oml.c | 1382 +++++++++++++++++++++++++++++++++
+ epan/dissectors/packet-gsm_abis_oml.h |  787 +++++++++++++++++++
+ 3 files changed, 2170 insertions(+), 0 deletions(-)
+ create mode 100644 epan/dissectors/packet-gsm_abis_oml.c
+ create mode 100644 epan/dissectors/packet-gsm_abis_oml.h
+
+diff --git a/epan/dissectors/Makefile.common b/epan/dissectors/Makefile.common
+index dbc3726..98dcdc3 100644
+--- a/epan/dissectors/Makefile.common
++++ b/epan/dissectors/Makefile.common
+@@ -481,6 +481,7 @@ DISSECTOR_SRC = \
+ 	packet-gsm_a_gm.c		\
+ 	packet-gsm_a_rp.c		\
+ 	packet-gsm_a_rr.c	\
++	packet-gsm_abis_oml.c	\
+ 	packet-gsm_ipa.c	\
+ 	packet-gsm_bsslap.c		\
+ 	packet-gsm_bssmap_le.c	\
+diff --git a/epan/dissectors/packet-gsm_abis_oml.c b/epan/dissectors/packet-gsm_abis_oml.c
+new file mode 100644
+index 0000000..fa46ab5
+--- /dev/null
++++ b/epan/dissectors/packet-gsm_abis_oml.c
+@@ -0,0 +1,1382 @@
++/* packet-abis_oml.c
++ * Routines for packet dissection of GSM A-bis over IP (3GPP TS 12.21)
++ * Copyright 2009 by Harald Welte <laforge@gnumonks.org>
++ * Copyright 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
++ * based on A-bis OML code in OpenBSC
++ *
++ * $Id$
++ *
++ * Wireshark - Network traffic analyzer
++ * By Gerald Combs <gerald@wireshark.org>
++ * Copyright 1998 Gerald Combs
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * 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 General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++ */
++
++#ifdef HAVE_CONFIG_H
++# include "config.h"
++#endif
++
++#include <glib.h>
++
++#include <epan/packet.h>
++#include <epan/emem.h>
++#include <epan/lapd_sapi.h>
++#include <epan/prefs.h>
++
++#include "packet-gsm_abis_oml.h"
++#include "packet-gsm_a_common.h"
++
++/* initialize the protocol and registered fields */
++static int proto_abis_oml = -1;
++
++/* OML header */
++static int hf_oml_msg_disc = -1;
++static int hf_oml_placement = -1;
++static int hf_oml_sequence = -1;
++static int hf_oml_length = -1;
++/* FOM header */
++static int hf_oml_fom_msgtype = -1;
++static int hf_oml_fom_objclass = -1;
++static int hf_oml_fom_inst_bts = -1;
++static int hf_oml_fom_inst_trx = -1;
++static int hf_oml_fom_inst_ts = -1;
++static int hf_oml_fom_attr_tag = -1;
++static int hf_oml_fom_attr_len = -1;
++static int hf_oml_fom_attr_val = -1;
++/* FOM attributes */
++static int hf_attr_adm_state = -1;
++static int hf_attr_arfcn = -1;
++static int hf_attr_oper_state = -1;
++static int hf_attr_avail_state = -1;
++static int hf_attr_event_type = -1;
++static int hf_attr_severity = -1;
++static int hf_attr_bcch_arfcn = -1;
++static int hf_attr_bsic = -1;
++static int hf_attr_test_no = -1;
++static int hf_attr_tsc = -1;
++static int hf_attr_tei = -1;
++static int hf_attr_ach_btsp = -1;
++static int hf_attr_ach_tslot = -1;
++static int hf_attr_ach_sslot = -1;
++static int hf_attr_gsm_time = -1;
++static int hf_attr_chan_comb = -1;
++/* Ipaccess */
++static int hf_oml_ipa_tres_attr_tag = -1;
++static int hf_oml_ipa_tres_attr_len = -1;
++static int hf_attr_ipa_test_res = -1;
++static int hf_attr_ipa_tr_rxlev = -1;
++static int hf_attr_ipa_tr_b_rxlev = -1;
++static int hf_attr_ipa_tr_arfcn = -1;
++static int hf_attr_ipa_tr_f_qual = -1;
++static int hf_attr_ipa_tr_f_err = -1;
++static int hf_attr_ipa_tr_rxqual = -1;
++static int hf_attr_ipa_tr_frame_offs = -1;
++static int hf_attr_ipa_tr_framenr_offs = -1;
++static int hf_attr_ipa_tr_bsic = -1;
++static int hf_attr_ipa_tr_cell_id = -1;
++static int hf_attr_ipa_tr_si2 = -1;
++static int hf_attr_ipa_tr_si2bis = -1;
++static int hf_attr_ipa_tr_si2ter = -1;
++static int hf_attr_ipa_tr_chan_desc = -1;
++static int hf_attr_ipa_rsl_ip = -1;
++static int hf_attr_ipa_rsl_port = -1;
++static int hf_attr_ipa_prim_oml_ip = -1;
++static int hf_attr_ipa_prim_oml_port = -1;
++static int hf_attr_ipa_location_name = -1;
++static int hf_attr_ipa_unit_id = -1;
++static int hf_attr_ipa_unit_name = -1;
++static int hf_attr_ipa_nv_flags = -1;
++static int hf_attr_ipa_nv_mask = -1;
++static int hf_attr_ipa_nsl_sport = -1;
++static int hf_attr_ipa_nsl_daddr = -1;
++static int hf_attr_ipa_nsl_dport = -1;
++static int hf_attr_ipa_nsei = -1;
++static int hf_attr_ipa_nsvci = -1;
++static int hf_attr_ipa_bvci = -1;
++static int hf_attr_ipa_rac = -1;
++
++/* initialize the subtree pointers */
++static int ett_oml = -1;
++static int ett_oml_fom = -1;
++static int ett_oml_fom_att = -1;
++
++/* Decode things as nanoBTS traces */
++static gboolean global_oml_use_nano_bts = FALSE;
++
++static proto_tree *top_tree;
++
++/* TS 12.21 Chapter 8.1 / TS 08.59 */
++static const value_string oml_msg_disc_vals[] = {
++	{ ABIS_OM_MDISC_FOM,	"Formatted O&M" },
++	{ ABIS_OM_MDISC_MMI,	"MMI Transfer" },
++	{ ABIS_OM_MDISC_TRAU,	"TRAU O&M" },
++	{ ABIS_OM_MDISC_MANUF,	"Manufacturer specific" },
++};
++
++/* TS 12.21 Chapter 8.1.1 */
++static const value_string oml_placement_vals[] = {
++	{ ABIS_OM_PLACEMENT_ONLY,	"Only" },
++	{ ABIS_OM_PLACEMENT_FIRST,	"First" },
++	{ ABIS_OM_PLACEMENT_MIDDLE,	"Middle" },
++	{ ABIS_OM_PLACEMENT_LAST,	"Last" },
++};
++
++/* TS 12.21 Chapter 9.2 */
++static const value_string oml_fom_msgtype_vals[] = {
++	{ NM_MT_LOAD_INIT,		"Software Load Init" },
++	{ NM_MT_LOAD_INIT_ACK,		"Software Load Init ACK" },
++	{ NM_MT_LOAD_INIT_NACK,		"Software Load Init NACK" },
++	{ NM_MT_LOAD_SEG,		"Software Load Segment" },
++	{ NM_MT_LOAD_SEG_ACK,		"Software Load Segment ACK" },
++	{ NM_MT_LOAD_END,		"Software Load End" },
++	{ NM_MT_LOAD_END_ACK,		"Software Load End ACK" },
++	{ NM_MT_LOAD_END_NACK,		"Software Load End NACK" },
++	{ NM_MT_SW_ACT_REQ,		"Software Activate Request" },
++	{ NM_MT_SW_ACT_REQ_ACK,		"Software Activate Request ACK" },
++	{ NM_MT_SW_ACT_REQ_NACK,	"Software Activate Request NACK" },
++	{ NM_MT_ACTIVATE_SW,		"Activate Software" },
++	{ NM_MT_ACTIVATE_SW_ACK,	"Activate Software ACK" },
++	{ NM_MT_ACTIVATE_SW_NACK,	"Activate Software NACK" },
++	{ NM_MT_SW_ACTIVATED_REP,	"Software Activated Report" },
++	{ NM_MT_ESTABLISH_TEI,		"Establish TEI" },
++	{ NM_MT_ESTABLISH_TEI_ACK,	"Establish TEI ACK" },
++	{ NM_MT_ESTABLISH_TEI_NACK,	"Establish TEI NACK" },
++	{ NM_MT_CONN_TERR_SIGN,		"Connect Terrestrial Signalling" },
++	{ NM_MT_CONN_TERR_SIGN_ACK,	"Connect Terrestrial Signalling ACK" },
++	{ NM_MT_CONN_TERR_SIGN_NACK,	"Connect Terrestrial Signalling NACK" },
++	{ NM_MT_DISC_TERR_SIGN,		"Disconnect Terrestrial Signalling" },
++	{ NM_MT_DISC_TERR_SIGN_ACK,	"Disconnect Terrestrial Signalling ACK" },
++	{ NM_MT_DISC_TERR_SIGN_NACK,	"Disconnect Terrestrial Signalling NACK" },
++	{ NM_MT_CONN_TERR_TRAF,		"Connect Terrestrial Traffic" },
++	{ NM_MT_CONN_TERR_TRAF_ACK,	"Connect Terrestrial Traffic ACK" },
++	{ NM_MT_CONN_TERR_TRAF_NACK,	"Connect Terrestrial Traffic NACK" },
++	{ NM_MT_DISC_TERR_TRAF,		"Disconnect Terrestrial Traffic" },
++	{ NM_MT_DISC_TERR_TRAF_ACK,	"Disconnect Terrestrial Traffic ACK" },
++	{ NM_MT_DISC_TERR_TRAF_NACK,	"Disconnect Terrestrial Traffic NACK" },
++	{ NM_MT_CONN_MDROP_LINK,	"Connect Multi-Drop Link" },
++	{ NM_MT_CONN_MDROP_LINK_ACK,	"Connect Multi-Drop Link ACK" },
++	{ NM_MT_CONN_MDROP_LINK_NACK,	"Connect Multi-Drop Link NACK" },
++	{ NM_MT_DISC_MDROP_LINK,	"Disconnect Multi-Drop Link" },
++	{ NM_MT_DISC_MDROP_LINK_ACK,	"Disconnect Multi-Drop Link ACK" },
++	{ NM_MT_DISC_MDROP_LINK_NACK,	"Disconnect Multi-Drop Link NACK" },
++	{ NM_MT_SET_BTS_ATTR,		"Set BTS Attributes" },
++	{ NM_MT_SET_BTS_ATTR_ACK,	"Set BTS Attributes ACK" },
++	{ NM_MT_SET_BTS_ATTR_NACK,	"Set BTS Attributes NACK" },
++	{ NM_MT_SET_RADIO_ATTR,		"Set Radio Carrier Attributes" },
++	{ NM_MT_SET_RADIO_ATTR_ACK,	"Set Radio Carrier Attributes ACK" },
++	{ NM_MT_SET_RADIO_ATTR_NACK,	"Set Radio Carrier Attributes NACK" },
++	{ NM_MT_SET_CHAN_ATTR,		"Set Channel Attributes" },
++	{ NM_MT_SET_CHAN_ATTR_ACK,	"Set Channel Attributes ACK" },
++	{ NM_MT_SET_CHAN_ATTR_NACK,	"Set Channel Attributes NACK" },
++	{ NM_MT_PERF_TEST,		"Perform Test" },
++	{ NM_MT_PERF_TEST_ACK,		"Perform Test ACK" },
++	{ NM_MT_PERF_TEST_NACK,		"Perform Test NACK" },
++	{ NM_MT_TEST_REP,		"Test Report" },
++	{ NM_MT_SEND_TEST_REP,		"Send Test Report" },
++	{ NM_MT_SEND_TEST_REP_ACK,	"Send Test Report ACK" },
++	{ NM_MT_SEND_TEST_REP_NACK,	"Send Test Report NACK" },
++	{ NM_MT_STOP_TEST,		"Stop Test" },
++	{ NM_MT_STOP_TEST_ACK,		"Stop Test ACK" },
++	{ NM_MT_STOP_TEST_NACK,		"Stop Test NACK" },
++	{ NM_MT_STATECHG_EVENT_REP,	"State Changed Event Report" },
++	{ NM_MT_FAILURE_EVENT_REP,	"Failure Event Report" },
++	{ NM_MT_STOP_EVENT_REP,		"Stop Sending Event Reports" },
++	{ NM_MT_STOP_EVENT_REP_ACK,	"Stop Sending Event Reports ACK" },
++	{ NM_MT_STOP_EVENT_REP_NACK,	"Stop Sending Event Reports NACK" },
++	{ NM_MT_REST_EVENT_REP,		"Restart Sending Event Reports" },
++	{ NM_MT_REST_EVENT_REP_ACK,	"Restart Sending Event Reports ACK" },
++	{ NM_MT_REST_EVENT_REP_NACK,	"Restart Sending Event Reports NACK" },
++	{ NM_MT_CHG_ADM_STATE,		"Change Administrative State" },
++	{ NM_MT_CHG_ADM_STATE_ACK,	"Change Administrative State ACK" },
++	{ NM_MT_CHG_ADM_STATE_NACK,	"Change Administrative State NACK" },
++	{ NM_MT_CHG_ADM_STATE_REQ,	"Change Administrative State Request" },
++	{ NM_MT_CHG_ADM_STATE_REQ_ACK,	"Change Administrative State Request ACK" },
++	{ NM_MT_CHG_ADM_STATE_REQ_NACK,	"Change Administrative State Request NACK" },
++	{ NM_MT_REP_OUTST_ALARMS,	"Report Outstanding Alarms" },
++	{ NM_MT_REP_OUTST_ALARMS_ACK,	"Report Outstanding Alarms ACK" },
++	{ NM_MT_REP_OUTST_ALARMS_NACK,	"Report Outstanding Alarms NACK" },
++	{ NM_MT_CHANGEOVER,		"Changeover" },
++	{ NM_MT_CHANGEOVER_ACK,		"Changeover ACK" },
++	{ NM_MT_CHANGEOVER_NACK,	"Changeover NACK" },
++	{ NM_MT_OPSTART,		"Opstart" },
++	{ NM_MT_OPSTART_ACK,		"Opstart ACK" },
++	{ NM_MT_OPSTART_NACK,		"Opstart NACK" },
++	{ NM_MT_REINIT,			"Reinitialize" },
++	{ NM_MT_REINIT_ACK,		"Reinitialize ACK" },
++	{ NM_MT_REINIT_NACK,		"Reinitialize NACK" },
++	{ NM_MT_SET_SITE_OUT,		"Set Site Outputs" },
++	{ NM_MT_SET_SITE_OUT_ACK,	"Set Site Outputs ACK" },
++	{ NM_MT_SET_SITE_OUT_NACK,	"Set Site Outputs NACK" },
++	{ NM_MT_CHG_HW_CONF,		"Change HW Configuration" },
++	{ NM_MT_CHG_HW_CONF_ACK,	"Change HW Configuration ACK" },
++	{ NM_MT_CHG_HW_CONF_NACK,	"Change HW Configuration NACK" },
++	{ NM_MT_MEAS_RES_REQ,		"Measurement Result Request" },
++	{ NM_MT_MEAS_RES_RESP,		"Measurement Result Response" },
++	{ NM_MT_STOP_MEAS,		"Stop Measurement" },
++	{ NM_MT_START_MEAS,		"Start Measurement" },
++	{ NM_MT_GET_ATTR,		"Get Attributes" },
++	{ NM_MT_GET_ATTR_RESP,		"Get Attributes Response" },
++	{ NM_MT_GET_ATTR_NACK,		"Get Attributes NACK" },
++	{ NM_MT_SET_ALARM_THRES,	"Set Alarm Threshold" },
++	{ NM_MT_SET_ALARM_THRES_ACK,	"Set Alarm Threshold ACK" },
++	{ NM_MT_SET_ALARM_THRES_NACK,	"Set Alarm Threshold NACK" },
++	/* proprietary, not in the standard */
++	{ NM_MT_IPACC_RESTART,		"IPA Restart" },
++	{ NM_MT_IPACC_RESTART_ACK,	"IPA Restart ACK" },
++	{ NM_MT_IPACC_RSL_CONNECT,	"IPA RSL Connect" },
++	{ NM_MT_IPACC_RSL_CONNECT_ACK,	"IPA RSL Connect ACK" },
++	{ NM_MT_IPACC_RSL_CONNECT_NACK,	"IPA RSL Connect NACK" },
++	{ NM_MT_IPACC_RSL_DISCONNECT,	"IPA RSL Disconnect" },
++	{ NM_MT_IPACC_RSL_DISCONNECT_ACK, "IPA RSL Disconnect ACK" },
++	{ NM_MT_IPACC_RSL_DISCONNECT_NACK, "IPA RSL Disconnect NACK" },
++	{ NM_MT_IPACC_CONN_TRAF,	"IPA Connect Traffic" },
++	{ NM_MT_IPACC_CONN_TRAF_ACK,	"IPA Connect Traffic ACK" },
++	{ NM_MT_IPACC_CONN_TRAF_NACK,	"IPA Connect Traffic NACK" },
++	{ NM_MT_IPACC_DISC_TRAF,	"IPA Disconnect Traffic" },
++	{ NM_MT_IPACC_DISC_TRAF_ACK,	"IPA Disconnect Traffic ACK" },
++	{ NM_MT_IPACC_DISC_TRAF_NACK,	"IPA Disconnect Traffic NACK" },
++	{ NM_MT_IPACC_DEF_BOOT_SW,	"IPA Default Boot Software" },
++	{ NM_MT_IPACC_DEF_BOOT_SW_ACK,	"IPA Default Boot Software ACK" },
++	{ NM_MT_IPACC_DEF_BOOT_SW_NACK,	"IPA Default Boot Software NACK" },
++	{ NM_MT_IPACC_SET_NVATTR,	"IPA Set NVRAM Attributes" },
++	{ NM_MT_IPACC_SET_NVATTR_ACK,	"IPA Set NVRAM Attributes ACK" },
++	{ NM_MT_IPACC_SET_NVATTR_NACK,	"IPA Set NVRAM Attributes NACK" },
++	{ NM_MT_IPACC_GET_NVATTR,	"IPA Get NVRAM Attributes" },
++	{ NM_MT_IPACC_GET_NVATTR_ACK,	"IPA Get NVRAM Attributes ACK" },
++	{ NM_MT_IPACC_GET_NVATTR_NACK,	"IPA Get NVRAM Attributes NACK" },
++	{ NM_MT_IPACC_SET_ATTR,		"IPA Set Attributes" },
++	{ NM_MT_IPACC_SET_ATTR_ACK,	"IPA Set Attributes ACK" },
++	{ NM_MT_IPACC_SET_ATTR_NACK,	"IPA Set Attributes NACK" },
++	{ NM_MT_IPACC_ATTR_CHG_EVT,	"IPA Attribute Change Event" },
++	{ NM_MT_IPACC_SW_DEACT,		"IPA Software Deactivate" },
++	{ NM_MT_IPACC_SW_DEACT_ACK,	"IPA Software Deactivate ACK" },
++	{ NM_MT_IPACC_SW_DEACT_NACK,	"IPA Software Deactivate NACK" },
++	{ NM_MT_IPACC_MEAS_RES_REQ_NACK,"IPA Measurement Result Request NACK" },
++	{ NM_MT_IPACC_START_MEAS_NACK,	"IPA Start Measurement NACK" },
++	{ NM_MT_IPACC_STOP_MEAS_NACK,	"IPA Stop Measurement NACK" },
++	{ NM_MT_BS11_RESET_RESOURCE,	"SIE Reset Resource" },
++	{ NM_MT_BS11_BEGIN_DB_TX,	"SIE Begin Database Transmission" },
++	{ NM_MT_BS11_BEGIN_DB_TX_ACK,	"SIE Begin Database Transmission ACK" },
++	{ NM_MT_BS11_BEGIN_DB_TX_NACK,	"SIE Begin Database Transmission NACK" },
++	{ NM_MT_BS11_END_DB_TX,		"SIE End Database Transmission" },
++	{ NM_MT_BS11_END_DB_TX_ACK,	"SIE End Database Transmission ACK" },
++	{ NM_MT_BS11_END_DB_TX_NACK,	"SIE End Database Transmission NACK" },
++	{ NM_MT_BS11_CREATE_OBJ,	"SIE Create Object" },
++	{ NM_MT_BS11_CREATE_OBJ_ACK,	"SIE Create Object ACK" },
++	{ NM_MT_BS11_CREATE_OBJ_NACK,	"SIE Create Object NACK" },
++	{ NM_MT_BS11_DELETE_OBJ,	"SIE Delete Object" },
++	{ NM_MT_BS11_DELETE_OBJ_ACK,	"SIE Delete Object ACK" },
++	{ NM_MT_BS11_DELETE_OBJ_NACK,	"SIE Delete Object NACK" },
++	{ NM_MT_BS11_GET_STATE,		"SIE Get State" },
++	{ NM_MT_BS11_GET_STATE_ACK,	"SIE Get State ACK" },
++	{ NM_MT_BS11_LMT_LOGON,		"SIE LMT Logon" },
++	{ NM_MT_BS11_LMT_LOGON_ACK,	"SIE LMT Logon ACK" },
++	{ NM_MT_BS11_RESTART,		"SIE Restart" },
++	{ NM_MT_BS11_RESTART_ACK,	"SIE Restart ACK" },
++	{ NM_MT_BS11_DISCONNECT,	"SIE Disconnect BTS" },
++	{ NM_MT_BS11_DISCONNECT_ACK,	"SIE Disconnect BTS ACK" },
++	{ NM_MT_BS11_LMT_LOGOFF,	"SIE LMT Logoff" },
++	{ NM_MT_BS11_LMT_LOGOFF_ACK,	"SIE LMT Logoff ACK" },
++	{ NM_MT_BS11_RECONNECT,		"SIE Reconnect BTS" },
++	{ NM_MT_BS11_RECONNECT_ACK,	"SIE Reconnect BTS ACK" },
++};
++
++/* TS 12.21 Section 9.2: Object Class */
++static const value_string oml_fom_objclass_vals[] = {
++	{ NM_OC_SITE_MANAGER,		"BTS Site Manager" },
++	{ NM_OC_BTS,			"BTS" },
++	{ NM_OC_RADIO_CARRIER,		"Radio Carrier" },
++	{ NM_OC_CHANNEL,		"Radio Channel" },
++	{ NM_OC_BASEB_TRANSC,		"Baseband Transceiver" },
++	/* proprietary, vendor specific */
++	{ NM_OC_BS11_ADJC,		"SIE Adjacend Channel" },
++	{ NM_OC_BS11_HANDOVER,		"SIE Handover" },
++	{ NM_OC_BS11_PWR_CTRL,		"SIE Power Control" },
++	{ NM_OC_BS11_BTSE,		"SIE BTSE" },
++	{ NM_OC_BS11_RACK,		"SIE Rack" },
++	{ NM_OC_BS11,			"SIE SiemensHW" },
++	{ NM_OC_BS11_TEST,		"SIE Test" },
++	{ NM_OC_BS11_ENVABTSE,		"SIE EnvaBTSE" },
++	{ NM_OC_BS11_BPORT,		"SIE BPort" },
++	{ NM_OC_GPRS_NSE,		"GPRS NSE" },
++	{ NM_OC_GPRS_CELL,		"GPRS Cell" },
++	{ NM_OC_GPRS_NSVC0,		"GPRS NSVC0" },
++	{ NM_OC_GPRS_NSVC1,		"GPRS NSVC1" },
++	{ NM_OC_NULL,			"NULL" },
++};
++
++/* TS 12.21 Section 9.4: Attributes */
++static const value_string oml_fom_attr_vals[] = {
++	{ NM_ATT_ABIS_CHANNEL,		"A-bis Channel" },
++	{ NM_ATT_ADD_INFO,		"Additional Information" },
++	{ NM_ATT_ADD_TEXT,		"Additional Text" },
++	{ NM_ATT_ADM_STATE,		"Administrative State" },
++	{ NM_ATT_ARFCN_LIST,		"ARFCN List" },
++	{ NM_ATT_AUTON_REPORT,		"Autonomously Report" },
++	{ NM_ATT_AVAIL_STATUS,		"Availability Status" },
++	{ NM_ATT_BCCH_ARFCN,		"BCCH ARFCN" },
++	{ NM_ATT_BSIC,			"BSIC" },
++	{ NM_ATT_BTS_AIR_TIMER,		"BTS Air Timer" },
++	{ NM_ATT_CCCH_L_I_P,		"CCCH Load Indication Period" },
++	{ NM_ATT_CCCH_L_T,		"CCCH Load Threshold" },
++	{ NM_ATT_CHAN_COMB,		"Channel Combination" },
++	{ NM_ATT_CONN_FAIL_CRIT,	"Connection Fail Criterion" },
++	{ NM_ATT_DEST,			"Destination" },
++	{ NM_ATT_EVENT_TYPE,		"Event Type" },
++	{ NM_ATT_FILE_ID,		"File ID" },
++	{ NM_ATT_FILE_VERSION,		"File Version" },
++	{ NM_ATT_GSM_TIME,		"GSM Time" },
++	{ NM_ATT_HSN,			"HSN" },
++	{ NM_ATT_HW_CONFIG,		"HW Configuration" },
++	{ NM_ATT_HW_DESC,		"HW Description" },
++	{ NM_ATT_INTAVE_PARAM,		"Intave Parameter" },
++	{ NM_ATT_INTERF_BOUND,		"Interference Boundaries" },
++	{ NM_ATT_LIST_REQ_ATTR,		"List of required Attributes" },
++	{ NM_ATT_MAIO,			"MAIO" },
++	{ NM_ATT_MANUF_STATE,		"Manufacturer Dependent State" },
++	{ NM_ATT_MANUF_THRESH,		"Manufacturer Dependent Thresholds" },
++	{ NM_ATT_MANUF_ID,		"Manufacturer Id" },
++	{ NM_ATT_MAX_TA,		"Maximum Timing Advance" },
++	{ NM_ATT_MDROP_LINK,		"Multi-drop BSC Link" },
++	{ NM_ATT_MDROP_NEXT,		"Multi-drop next BTS Link" },
++	{ NM_ATT_NACK_CAUSES,		"NACK Causes" },
++	{ NM_ATT_NY1,			"Ny1" },
++	{ NM_ATT_OPER_STATE,		"Operational State" },
++	{ NM_ATT_OVERL_PERIOD,		"Overload Period" },
++	{ NM_ATT_PHYS_CONF,		"Physical Config" },
++	{ NM_ATT_POWER_CLASS,		"Power Class" },
++	{ NM_ATT_POWER_THRESH,		"Power Output Thresholds" },
++	{ NM_ATT_PROB_CAUSE,		"Probable Cause" },
++	{ NM_ATT_RACH_B_THRESH,		"RACH Busy Threshold" },
++	{ NM_ATT_LDAVG_SLOTS,		"RACH Load Averaging Slots" },
++	{ NM_ATT_RAD_SUBC,		"Radio Sub Channel" },
++	{ NM_ATT_RF_MAXPOWR_R,		"RF Max Power Reduction" },
++	{ NM_ATT_SITE_INPUTS,		"Site Inputs" },
++	{ NM_ATT_SITE_OUTPUTS,		"Site Outputs" },
++	{ NM_ATT_SOURCE,		"Source" },
++	{ NM_ATT_SPEC_PROB,		"Specific Problems" },
++	{ NM_ATT_START_TIME,		"Starting Time" },
++	{ NM_ATT_T200,			"T200" },
++	{ NM_ATT_TEI,			"TEI" },
++	{ NM_ATT_TEST_DUR,		"Test Duration" },
++	{ NM_ATT_TEST_NO,		"Test No" },
++	{ NM_ATT_TEST_REPORT,		"Test Report Info" },
++	{ NM_ATT_VSWR_THRESH,		"VSWR Thresholds " },
++	{ NM_ATT_WINDOW_SIZE,		"Window Size" },
++	{ NM_ATT_BS11_RSSI_OFFS,	"SIE RSSI Offset" },
++	{ NM_ATT_BS11_TXPWR,		"SIE TX Power" },
++	{ NM_ATT_BS11_DIVERSITY,	"SIE Diversity" },
++	{ NM_ATT_TSC,			"Training Sequence Code" },
++	{ NM_ATT_SW_CONFIG,		"SW Configuration" },
++	{ NM_ATT_SW_DESCR,		"SW Description" },
++	{ NM_ATT_SEVERITY,		"Perceived Severity" },
++	{ NM_ATT_GET_ARI,		"Get ARI" },
++	{ NM_ATT_HW_CONF_CHG,		"HW Configuration Change" },
++	{ NM_ATT_OUTST_ALARM,		"Outstanding Alarm" },
++	{ NM_ATT_FILE_DATA,		"File Data" },
++	{ NM_ATT_MEAS_RES,		"Measurement Result" },
++	{ NM_ATT_MEAS_TYPE,		"Measurement Type" },
++	{ NM_ATT_BS11_ESN_FW_CODE_NO,	"SIE ESN FW Code Number" },
++	{ NM_ATT_BS11_ESN_HW_CODE_NO,	"SIE ESN HW Code Number" },
++	{ NM_ATT_BS11_ESN_PCB_SERIAL,	"SIE ESN PCB Serial Number" },
++	{ NM_ATT_BS11_EXCESSIVE_DISTANCE, "SIE Excessive Distance" },
++	{ NM_ATT_BS11_ALL_TEST_CATG,	"SIE All Test Categories" },
++	{ NM_ATT_BS11_BTSLS_HOPPING,	"SIE BTS LS Hopping" },
++	{ NM_ATT_BS11_CELL_ALLOC_NR,	"SIE Cell Allocation Number" },
++	{ NM_ATT_BS11_CELL_GLOBAL_ID,	"SIE Cell Global ID" },
++	{ NM_ATT_BS11_ENA_INTERF_CLASS,	"SIE Enable Interference Class" },
++	/* FIXME */
++	{ NM_ATT_BS11_ENA_MS_PWR_CTRL,	"SIE Enable MS Power Control" },
++	{ NM_ATT_BS11_ENA_PWR_BDGT_HO,	"SIE Enable Power Budget HO" },
++	{ NM_ATT_BS11_ENA_RXLEV_HO,	"SIE Enable RxLevel HO" },
++	{ NM_ATT_BS11_ENA_RXQUAL_HO,	"SIE Enable RxQual HO" },
++	{ NM_ATT_BS11_FACCH_QUAL,	"SIE FACCH Quality" },
++	{ NM_ATT_IPACC_DST_IP,		"IPA Destination IP Address" },
++	{ NM_ATT_IPACC_DST_IP_PORT,	"IPA Destionation IP Port" },
++	{ NM_ATT_IPACC_SSRC,		"IPA RTP SSRC" },
++	{ NM_ATT_IPACC_RTP_PAYLD_TYPE,	"IPA RTP Payload Type" },
++	{ NM_ATT_IPACC_BASEB_ID,	"IPA Baseband Identifier" },
++	{ NM_ATT_IPACC_STREAM_ID,	"IPA Stream Identifier" },
++	{ NM_ATT_IPACC_NV_FLAGS,	"IPA NVRAM Flags" },
++	{ NM_ATT_IPACC_FREQ_CTRL,	"IPA Frequency Control" },
++	{ NM_ATT_IPACC_PRIM_OML_CFG,	"IPA Primary OML Config" },
++	{ NM_ATT_IPACC_SEC_OML_CFG,	"IPA Secondary OML Config" },
++	{ NM_ATT_IPACC_IP_IF_CFG,	"IPA IP Interface Config" },
++	{ NM_ATT_IPACC_IP_GW_CFG,	"IPA IP Gateway Config" },
++	{ NM_ATT_IPACC_IN_SERV_TIME,	"IPA In Service Time" },
++	{ NM_ATT_IPACC_TRX_BTS_ASS,	"IPA TRX BTS Assignment" },
++	{ NM_ATT_IPACC_LOCATION,	"IPA BTS Location Name" },
++	{ NM_ATT_IPACC_PAGING_CFG,	"IPA Paging Configuration" },
++	{ NM_ATT_IPACC_FILE_DATA,	"IPA File Data" },
++	{ NM_ATT_IPACC_UNIT_ID,		"IPA Unit ID" },
++	{ NM_ATT_IPACC_PARENT_UNIT_ID,	"IPA Parent Unit ID" },
++	{ NM_ATT_IPACC_UNIT_NAME,	"IPA Unit Name" },
++	{ NM_ATT_IPACC_SNMP_CFG,	"IPA SNMP Config" },
++	{ NM_ATT_IPACC_PRIM_OML_CFG_LIST, "IPA Primary OML Config List" },
++	{ NM_ATT_IPACC_PRIM_OML_FB_TOUT,"IPA Primary OML Fallback Timeout" },
++	{ NM_ATT_IPACC_CUR_SW_CFG,	"IPA Current Software Config" },
++	{ NM_ATT_IPACC_TIMING_BUS,	"IPA Timing Bus" },
++	{ NM_ATT_IPACC_CGI,		"IPA CGI" },
++	{ NM_ATT_IPACC_RAC,		"IPA RAC" },
++	{ NM_ATT_IPACC_OBJ_VERSION,	"IPA Object Version" },
++	{ NM_ATT_IPACC_GPRS_PAGING_CFG,	"IPA GPRS Paging Configuration" },
++	{ NM_ATT_IPACC_NSEI,		"IPA NSEI" },
++	{ NM_ATT_IPACC_BVCI,		"IPA BVCI" },
++	{ NM_ATT_IPACC_NSVCI,		"IPA NSVCI" },
++	{ NM_ATT_IPACC_NS_CFG,		"IPA NS Configuration" },
++	{ NM_ATT_IPACC_BSSGP_CFG,	"IPA BSSGP Configuration" },
++	{ NM_ATT_IPACC_NS_LINK_CFG,	"IPA NS Link Configuration" },
++	{ NM_ATT_IPACC_RLC_CFG,		"IPA RLC Configuration" },
++	{ NM_ATT_IPACC_ALM_THRESH_LIST,	"IPA Alarm Threshold List" },
++	{ NM_ATT_IPACC_MONIT_VAL_LIST,	"IPA Monitored Value List" },
++	{ NM_ATT_IPACC_TIB_CONTROL,	"IPA Timing Interface Bus Control" },
++	{ NM_ATT_IPACC_SUPP_FEATURES,	"IPA Supported Features" },
++	{ NM_ATT_IPACC_CODING_SCHEMES,	"IPA Coding Schemes" },
++	{ NM_ATT_IPACC_RLC_CFG_2,	"IPA RLC Configuration 2" },
++	{ NM_ATT_IPACC_HEARTB_TOUT,	"IPA Heartbeat Timeout" },
++	{ NM_ATT_IPACC_UPTIME,		"IPA Uptime" },
++	{ NM_ATT_IPACC_RLC_CFG_3,	"IPA RLC Configuration 3" },
++	{ NM_ATT_IPACC_SSL_CFG,		"IPA SSL Configuration" },
++	{ NM_ATT_IPACC_SEC_POSSIBLE,	"IPA Security Possible" },
++	{ NM_ATT_IPACC_IML_SSL_STATE,	"IPA IML SSL State" },
++	{ NM_ATT_IPACC_REVOC_DATE,	"IPA Revocation Date" },
++	/* FIXME: More SIE */
++};
++
++/* Section 9.4.4: Administrative State */
++static const value_string oml_adm_state_vals[] = {
++	{ NM_STATE_LOCKED,		"Locked" },
++	{ NM_STATE_UNLOCKED,		"Unlocked" },
++	{ NM_STATE_SHUTDOWN,		"Shutdown" },
++	{ NM_STATE_NULL,		"Null" },
++};
++
++static const value_string oml_oper_state_vals[] = {
++	{ 1,	"Disabled" },
++	{ 2,	"Enabled" },
++	{ 0xff,	"NULL" },
++};
++
++/* Section 9.4.7 Availability Status */
++static const value_string oml_avail_state_vals[] = {
++	{ 0,	"In test" },
++	{ 1,	"Failed" },
++	{ 2,	"Power off" },
++	{ 3,	"Off line" },
++	{ 5,	"Dependency" },
++	{ 6,	"Degraded" },
++	{ 7, 	"Not installed" },
++	{ 0xff,	"OK" },
++};
++
++/* Section 9.4.13: Channel Combination */
++static const value_string oml_chan_comb_vals[] = {
++	{ NM_CHANC_TCHFull,		"TCH/F" },
++	{ NM_CHANC_TCHHalf,		"TCH/H" },
++	{ NM_CHANC_TCHHalf2,		"TCH/H 2" },
++	{ NM_CHANC_SDCCH,		"SDCCH" },
++	{ NM_CHANC_mainBCCH,		"Main BCCH" },
++	{ NM_CHANC_BCCHComb,		"Combined BCCH" },
++	{ NM_CHANC_BCCH,		"BCCH" },
++	{ NM_CHANC_BCCH_CBCH,		"BCCH+CBCH" },
++	{ NM_CHANC_SDCCH_CBCH,		"SDCCH+CBCH" },
++};
++
++/* Section 9.4.16: Event Type */
++static const value_string oml_event_type_vals[] = {
++	{ NM_EVT_COMM_FAIL,		"Communication Failure" },
++	{ NM_EVT_QOS_FAIL,		"QoS Failure" },
++	{ NM_EVT_PROC_FAIL,		"Processor Failure" },
++	{ NM_EVT_EQUIP_FAIL,		"Equipment Failure" },
++	{ NM_EVT_ENV_FAIL,		"Environment Failure" },
++};
++
++/* Section 9.4.63: Perceived Severity */
++static const value_string oml_severity_vals[] = {
++	{ NM_SEVER_CEASED,		"Ceased" },
++	{ NM_SEVER_CRITICAL,		"Critical" },
++	{ NM_SEVER_MAJOR,		"Major" },
++	{ NM_SEVER_MINOR,		"Minor" },
++	{ NM_SEVER_WARNING,		"Warning" },
++	{ NM_SEVER_INDETERMINATE,	"Indeterminate" },
++};
++
++/* Section 9.4.36: NACK Causes */
++static const value_string oml_nack_cause[] = {
++	{ NM_NACK_INCORR_STRUCT,	"Incorrect message structure" },
++	{ NM_NACK_MSGTYPE_INVAL,	"Invalid message type value" },
++	{ NM_NACK_OBJCLASS_INVAL,	"Invalid Object class value" },
++	{ NM_NACK_OBJCLASS_NOTSUPP,	"Object Class not supported" },
++	{ NM_NACK_BTSNR_UNKN,		"BTS Number unknown" },
++	{ NM_NACK_TRXNR_UNKN,		"TRX Number unknown" },
++	{ NM_NACK_OBJINST_UNKN,		"Object Instance unknown" },
++	{ NM_NACK_ATTRID_INVAL,		"Invalid Attribute ID value" },
++	{ NM_NACK_ATTRID_NOTSUPP,	"Attribute ID not supported" },
++	{ NM_NACK_PARAM_RANGE,		"Parameter value out of range" },
++	{ NM_NACK_ATTRLIST_INCONSISTENT, "Inconsistency in Attribute list" },
++	{ NM_NACK_SPEC_IMPL_NOTSUPP,	"Specified Implementation not supported" },
++	{ NM_NACK_CANT_PERFORM,		"Message cannot be performed" },
++	{ NM_NACK_RES_NOTIMPL,		"Resource not implemented" },
++	{ NM_NACK_RES_NOTAVAIL,		"Resource not available" },
++	{ NM_NACK_FREQ_NOTAVAIL,	"Frequency not available" },
++	{ NM_NACK_TEST_NOTSUPP,		"Test not supported" },
++	{ NM_NACK_CAPACITY_RESTR,	"Capacity restrictions" },
++	{ NM_NACK_PHYSCFG_NOTPERFORM,	"Phys config cannot be performed" },
++	{ NM_NACK_TEST_NOTINIT,		"Test not initiated" },
++	{ NM_NACK_PHYSCFG_NOTRESTORE,	"Phys config cannot be restored" },
++	{ NM_NACK_TEST_NOSUCH,		"No such Test" },
++	{ NM_NACK_TEST_NOSTOP,		"Test cannot be stopped" },
++	{ NM_NACK_MSGINCONSIST_PHYSCFG,	"Message inconsisten with physical config" },
++	{ NM_NACK_FILE_INCOMPLETE,	"Complete file not received" },
++	{ NM_NACK_FILE_NOTAVAIL,	"File not available at destination" },
++	{ NM_NACK_FILE_NOTACTIVATE,	"File cannot be activated" },
++	{ NM_NACK_REQ_NOT_GRANT,	"Request not granted" },
++	{ NM_NACK_WAIT,			"Wait" },
++	{ NM_NACK_NOTH_REPORT_EXIST,	"Nothing reportable existing" },
++	{ NM_NACK_MEAS_NOTSUPP,		"Measurement not supported" },
++	{ NM_NACK_MEAS_NOTSTART,	"Measurement not started" },
++	{ 0xff,				"NULL" },
++};
++
++static const value_string oml_test_no_vals[] = {
++	{ NM_IPACC_TESTNO_RLOOP_ANT,	"Radio Loop test via antenna" },
++	{ NM_IPACC_TESTNO_RLOOP_XCVR,	"Radio Loop test via transceiver" },
++	{ NM_IPACC_TESTNO_FUNC_OBJ,	"BTS Functional object self test" },
++	{ NM_IPACC_TESTNO_CHAN_USAGE,	"Channel Usage" },
++	{ NM_IPACC_TESTNO_BCCH_CHAN_USAGE, "BCCH Channel Usage" },
++	{ NM_IPACC_TESTNO_FREQ_SYNC,	"Frequency Synchronization" },
++	{ NM_IPACC_TESTNO_BCCH_INFO,	"BCCH Information" },
++	{ NM_IPACC_TESTNO_TX_BEACON,	"Transmit Beacon" },
++	{ NM_IPACC_TESTNO_SYSINFO_MONITOR, "SysInfo Monitor" },
++	{ NM_IPACC_TESTNO_BCCCH_MONITOR, "BCCH & CCCH Monitor" },
++};
++
++static const value_string ipacc_test_res_vals[] = {
++	{ NM_IPACC_TESTRES_SUCCESS,	"Success" },
++	{ NM_IPACC_TESTRES_TIMEOUT,	"Timeout" },
++	{ NM_IPACC_TESTRES_NO_CHANS,	"No suitable channels available" },
++	{ NM_IPACC_TESTRES_PARTIAL,	"Partial" },
++	{ NM_IPACC_TESTRES_STOPPED,	"Stopped" },
++};
++
++static const value_string ipacc_testres_ie_vals[] = {
++	{ NM_IPACC_TR_IE_FREQ_ERR_LIST,	"Frequency Error List" },
++	{ NM_IPACC_TR_IE_CHAN_USAGE,	"Channel Usage" },
++	{ NM_IPACC_TR_IE_BCCH_INFO,	"BCCH Information" },
++	{ NM_IPACC_TR_IE_RESULT_DETAILS,"Result Details" },
++	{ NM_IPACC_TR_IE_FREQ_ERR,	"Frequency Error" },
++};
++
++/* Parse the ip.access specific BCCH Information IE embedded into the Test
++ * Report IE */
++static gint
++ipacc_tr_ie_bcch(tvbuff_t *tvb, proto_tree *att_tree, int offset)
++{
++	guint16 binfo_type, tmp;
++
++	binfo_type = tvb_get_ntohs(tvb, offset);
++	offset += 2;
++
++	tmp = tvb_get_ntohs(tvb, offset);
++
++	/* FIXME: there are still some bugs remaining here */
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_arfcn,
++			    tvb, offset, 2, TRUE);
++
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_f_qual,
++			    tvb, offset, 2, TRUE);
++	offset += 2;
++
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_b_rxlev,
++			    tvb, offset++, 1, TRUE);
++
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_rxqual,
++			    tvb, offset++, 1, TRUE);
++
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_f_err,
++			    tvb, offset, 2, TRUE);
++	offset += 2;
++
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_frame_offs,
++			    tvb, offset, 2, TRUE);
++	offset += 2;
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_framenr_offs,
++			    tvb, offset, 4, TRUE);
++	offset += 4;
++
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_bsic,
++			    tvb, offset++, 1, TRUE);
++
++	de_lai(tvb, att_tree, offset, 5, NULL, 0);
++	offset += 5;
++
++	proto_tree_add_item(att_tree, hf_attr_ipa_tr_cell_id,
++			    tvb, offset, 2, TRUE);
++	offset += 2;
++
++	if (binfo_type & 0x8000) {
++		/* System Information 2 */
++		/* FIXME: Parse 04.18 Neighbour Cell Description */
++		proto_tree_add_item(att_tree, hf_attr_ipa_tr_si2,
++				    tvb, offset, 16, TRUE);
++		offset += 16;
++	}
++	if (binfo_type & 0x0001) {
++		/* System Information 2bis */
++		/* FIXME: Parse 04.18 Neighbour Cell Description */
++		proto_tree_add_item(att_tree, hf_attr_ipa_tr_si2bis,
++				    tvb, offset, 16, TRUE);
++		offset += 16;
++	}
++	if (binfo_type & 0x0002) {
++		/* System Information 2ter */
++		/* FIXME: Parse 04.18 Neighbour Cell Description */
++		proto_tree_add_item(att_tree, hf_attr_ipa_tr_si2ter,
++				    tvb, offset, 16, TRUE);
++		offset += 16;
++	}
++	if (binfo_type & 0x0004) {
++		/* FIXME: Parse 04.18 Cell Channel Description */
++		proto_tree_add_item(att_tree, hf_attr_ipa_tr_chan_desc,
++				    tvb, offset, 16, TRUE);
++		offset += 16;
++	}
++
++	return offset;
++}
++
++/* Parse the ip.access specific Channel Usage IE embedded into the Test
++ * Report IE */
++static gint
++ipacc_tr_ie_chan_usage(tvbuff_t *tvb, proto_tree *att_tree, int offset)
++{
++	while (tvb_reported_length_remaining(tvb, offset) != 0) {
++		guint16 result = tvb_get_ntohs(tvb, offset);
++		proto_tree_add_uint(att_tree, hf_attr_ipa_tr_arfcn,
++				    tvb, offset, 2, result);
++		proto_tree_add_uint(att_tree, hf_attr_ipa_tr_rxlev,
++				    tvb, offset, 2, result);
++		offset += 2;
++	}
++	return offset;
++}
++
++/* Parse the ip.access specific format of the standard test report IE */
++static gint
++dissect_ipacc_test_rep(proto_tree *tree, tvbuff_t *tvb)
++{
++	gint offset = 0;
++
++	proto_tree_add_item(tree, hf_attr_ipa_test_res, tvb, offset++,
++			    1, FALSE);
++
++	while (tvb_reported_length_remaining(tvb, offset) != 0) {
++		guint8 ie = tvb_get_guint8(tvb, offset);
++		guint16 len = tvb_get_ntohs(tvb, offset+1);
++		proto_item *ti;
++		proto_tree *att_tree;
++
++		ti = proto_tree_add_item(tree, hf_oml_ipa_tres_attr_tag, tvb,
++					 offset++, 1, FALSE);
++		att_tree = proto_item_add_subtree(ti, ett_oml_fom_att);
++		proto_tree_add_uint(att_tree, hf_oml_ipa_tres_attr_len, tvb,
++				    offset, 2, len);
++		offset += 2;
++
++		switch (ie) {
++		case NM_IPACC_TR_IE_CHAN_USAGE:
++			offset = ipacc_tr_ie_chan_usage(tvb,
++						 	att_tree, offset);
++			break;
++		case NM_IPACC_TR_IE_BCCH_INFO:
++			offset = ipacc_tr_ie_bcch(tvb,
++						  att_tree, offset);
++			break;
++		default:
++			break;
++		}
++	}
++	return offset;
++}
++
++/* Dissect OML FOM Attributes after OML + FOM header */
++static gint
++dissect_oml_attrs(tvbuff_t *tvb, int base_offs, packet_info *pinfo,
++		  proto_tree *tree)
++{
++	int offset = base_offs;
++
++	while (tvb_reported_length_remaining(tvb, offset) != 0) {
++		guint i;
++		guint8 tag, val8;
++		guint16 val16;
++		guint32 val32;
++		unsigned int len, len_len, hlen;
++		const struct tlv_def *tdef;
++		proto_item *ti;
++		proto_tree *att_tree;
++		tvbuff_t *sub_tvb;
++
++		tag = tvb_get_guint8(tvb, offset);
++		tdef = &nm_att_tlvdef.def[tag];
++
++		switch (tdef->type) {
++		case TLV_TYPE_FIXED:
++			hlen = 1;
++			len_len = 0;
++			len = tdef->fixed_len;
++			break;
++		case TLV_TYPE_T:
++			hlen = 1;
++			len_len = 0;
++			len = 0;
++			break;
++		case TLV_TYPE_TV:
++			hlen = 1;
++			len_len = 0;
++			len = 1;
++			break;
++		case TLV_TYPE_TLV:
++			hlen = 2;
++			len_len = 1;
++			len = tvb_get_guint8(tvb, offset+1);
++			break;
++		case TLV_TYPE_TL16V:
++			hlen = 3;
++			len_len = 2;
++			len = tvb_get_guint8(tvb, offset+1) << 8 |
++						tvb_get_guint8(tvb, offset+2);
++			break;
++		default:
++			hlen = len_len = len = 0;
++			DISSECTOR_ASSERT_NOT_REACHED();
++			break;
++		}
++
++		ti = proto_tree_add_item(tree, hf_oml_fom_attr_tag, tvb,
++					 offset, 1, FALSE);
++		att_tree = proto_item_add_subtree(ti, ett_oml_fom_att);
++		proto_tree_add_uint(att_tree, hf_oml_fom_attr_len, tvb,
++				    offset+1, len_len, len);
++		offset += hlen;
++
++		sub_tvb = tvb_new_subset(tvb, offset, len, len);
++
++		switch (tag) {
++		/* parse only the most common IE for now */
++		case NM_ATT_ABIS_CHANNEL:
++			proto_tree_add_item(att_tree, hf_attr_ach_btsp, tvb,
++					    offset, 1, TRUE);
++			proto_tree_add_item(att_tree, hf_attr_ach_tslot, tvb,
++					    offset+1, 1, TRUE);
++			proto_tree_add_item(att_tree, hf_attr_ach_sslot, tvb,
++					    offset+2, 1, TRUE);
++			break;
++		case NM_ATT_ADM_STATE:
++			proto_tree_add_item(att_tree, hf_attr_adm_state, tvb,
++					    offset, len, FALSE);
++			val8 = tvb_get_guint8(tvb, offset);
++			col_append_fstr(pinfo->cinfo, COL_INFO, "%s ",
++					val_to_str(val8, oml_adm_state_vals,
++						   "%02x"));
++			break;
++		case NM_ATT_ARFCN_LIST:
++			for (i = 0; i < len; i += 2) {
++				val16 = tvb_get_ntohs(tvb, offset + i);
++				proto_tree_add_uint(att_tree, hf_attr_arfcn,
++						    tvb, offset + i, 2, val16);
++			}
++			break;
++		case NM_ATT_AVAIL_STATUS:
++			/* Availability status can have length 0 */
++			if (len) {
++				val8 = tvb_get_guint8(tvb, offset);
++				proto_tree_add_item(att_tree,
++						    hf_attr_avail_state, tvb,
++					    	    offset, len, FALSE);
++			} else
++				val8 = 0xff;
++			col_append_fstr(pinfo->cinfo, COL_INFO, "%s ",
++					val_to_str(val8, oml_avail_state_vals,
++						   "%02x"));
++			break;
++		case NM_ATT_BCCH_ARFCN:
++			proto_tree_add_item(att_tree, hf_attr_bcch_arfcn, tvb,
++					    offset, len, TRUE);
++			break;
++		case NM_ATT_BSIC:
++			proto_tree_add_item(att_tree, hf_attr_bsic, tvb,
++					    offset, len, TRUE);
++			break;
++		case NM_ATT_CHAN_COMB:
++			proto_tree_add_item(att_tree, hf_attr_chan_comb, tvb,
++					    offset, len, TRUE);
++			break;
++		case NM_ATT_EVENT_TYPE:
++			proto_tree_add_item(att_tree, hf_attr_event_type, tvb,
++					    offset, len, TRUE);
++			break;
++		case NM_ATT_GSM_TIME:
++			proto_tree_add_item(att_tree, hf_attr_gsm_time, tvb,
++					    offset, len, TRUE);
++			break;
++		case NM_ATT_OPER_STATE:
++			proto_tree_add_item(att_tree, hf_attr_oper_state, tvb,
++					    offset, len, FALSE);
++			val8 = tvb_get_guint8(tvb, offset);
++			col_append_fstr(pinfo->cinfo, COL_INFO, "%s ",
++					val_to_str(val8, oml_oper_state_vals,
++						   "%02x"));
++			break;
++		case NM_ATT_TEI:
++			proto_tree_add_item(att_tree, hf_attr_tei, tvb,
++					    offset, len, TRUE);
++			break;
++		case NM_ATT_TSC:
++			proto_tree_add_item(att_tree, hf_attr_tsc, tvb,
++					    offset, len, TRUE);
++			break;
++		case NM_ATT_SEVERITY:
++			proto_tree_add_item(att_tree, hf_attr_severity, tvb,
++					    offset, len, TRUE);
++			break;
++		case NM_ATT_TEST_REPORT:
++			dissect_ipacc_test_rep(att_tree, sub_tvb);
++			break;
++		case NM_ATT_TEST_NO:
++			proto_tree_add_item(att_tree, hf_attr_test_no, tvb,
++					    offset, len, TRUE);
++			val8 = tvb_get_guint8(tvb, offset);
++			col_append_fstr(pinfo->cinfo, COL_INFO, "%s ",
++					val_to_str(val8, oml_test_no_vals,
++						   "%02x"));
++			break;
++
++		/* proprietary ip.access extensions */
++		case NM_ATT_IPACC_DST_IP:
++			val32 = tvb_get_ntohl(tvb, offset);
++			proto_tree_add_ipv4(att_tree, hf_attr_ipa_rsl_ip, tvb,
++					    offset, len, val32);
++			break;
++		case NM_ATT_IPACC_DST_IP_PORT:
++			val16 = tvb_get_ntohs(tvb, offset);
++			proto_tree_add_uint(att_tree, hf_attr_ipa_rsl_port, tvb,
++					    offset, len, val16);
++			break;
++		case NM_ATT_IPACC_LOCATION:
++			proto_tree_add_item(att_tree, hf_attr_ipa_location_name,
++					    tvb, offset, len, TRUE);
++			break;
++		case NM_ATT_IPACC_UNIT_ID:
++			proto_tree_add_item(att_tree, hf_attr_ipa_unit_id,
++					    tvb, offset, len, TRUE);
++			break;
++		case NM_ATT_IPACC_UNIT_NAME:
++			proto_tree_add_item(att_tree, hf_attr_ipa_unit_name,
++					    tvb, offset, len, TRUE);
++			break;
++		case NM_ATT_IPACC_PRIM_OML_CFG_LIST:
++			proto_tree_add_item(att_tree, hf_attr_ipa_prim_oml_ip,
++					    tvb, offset+1, 4, TRUE);
++			proto_tree_add_item(att_tree, hf_attr_ipa_prim_oml_port,
++					    tvb, offset+1+4, 2, TRUE);
++			break;
++		case NM_ATT_IPACC_NV_FLAGS:
++			{
++				guint flags, mask;
++				flags = tvb_get_guint8(tvb, offset);
++				mask = tvb_get_guint8(tvb, offset+1);
++				flags |= tvb_get_guint8(tvb, offset+2) << 8;
++				mask |= tvb_get_guint8(tvb, offset+3) << 8;
++				proto_tree_add_uint(att_tree, hf_attr_ipa_nv_flags,
++						    tvb, offset, 3, flags);
++				proto_tree_add_uint(att_tree, hf_attr_ipa_nv_mask,
++						    tvb, offset+1, 3, mask);
++			}
++			break;
++		case NM_ATT_IPACC_RAC:
++			proto_tree_add_item(att_tree, hf_attr_ipa_rac,
++					    tvb, offset, 1, TRUE);
++			break;
++		case NM_ATT_IPACC_NSEI:
++			val16 = tvb_get_ntohs(tvb, offset);
++			proto_tree_add_uint(att_tree, hf_attr_ipa_nsei,
++					   tvb, offset, 2, val16);
++			break;
++		case NM_ATT_IPACC_NSVCI:
++			val16 = tvb_get_ntohs(tvb, offset);
++			proto_tree_add_uint(att_tree, hf_attr_ipa_nsvci,
++					   tvb, offset, 2, val16);
++			break;
++		case NM_ATT_IPACC_BVCI:
++			val16 = tvb_get_ntohs(tvb, offset);
++			proto_tree_add_uint(att_tree, hf_attr_ipa_bvci,
++					   tvb, offset, 2, val16);
++			break;
++		case NM_ATT_IPACC_NS_LINK_CFG:
++			val16 = tvb_get_ntohs(tvb, offset);
++			proto_tree_add_uint(att_tree, hf_attr_ipa_nsl_sport,
++					   tvb, offset, 2, val16);
++			val32 = tvb_get_ipv4(tvb, offset+2);
++			proto_tree_add_ipv4(att_tree, hf_attr_ipa_nsl_daddr,
++					   tvb, offset+2, 4, val32);
++			val16 = tvb_get_ntohs(tvb, offset+6);
++			proto_tree_add_uint(att_tree, hf_attr_ipa_nsl_dport,
++					   tvb, offset+6, 2, val16);
++			break;
++		default:
++			proto_tree_add_item(att_tree, hf_oml_fom_attr_val, tvb,
++					    offset, len, FALSE);
++		}
++		offset += len;
++	}
++	return offset;
++}
++
++static int
++dissect_oml_fom(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
++		int offset, proto_item *top_ti)
++{
++	guint8 msg_type, obj_class, bts_nr, trx_nr, ts_nr;
++	proto_item *ti;
++	proto_tree *fom_tree;
++
++	msg_type = tvb_get_guint8(tvb, offset);
++	obj_class = tvb_get_guint8(tvb, offset+1);
++	bts_nr = tvb_get_guint8(tvb, offset+2);
++	trx_nr = tvb_get_guint8(tvb, offset+3);
++	ts_nr = tvb_get_guint8(tvb, offset+4);
++	proto_item_append_text(top_ti, ", %s(%02x,%02x,%02x) %s ",
++			val_to_str(obj_class, oml_fom_objclass_vals, "%02x"),
++			bts_nr, trx_nr, ts_nr,
++			val_to_str(msg_type, oml_fom_msgtype_vals,
++				   "unknown 0x%x"));
++	col_append_fstr(pinfo->cinfo, COL_INFO, "%s(%02x,%02x,%02x) %s ",
++			val_to_str(obj_class, oml_fom_objclass_vals, "%02x"),
++			bts_nr, trx_nr, ts_nr,
++			val_to_str(msg_type, oml_fom_msgtype_vals,
++				   "unknown 0x%x"));
++	ti = proto_tree_add_item(tree, hf_oml_fom_msgtype, tvb, offset++, 1, FALSE);
++	fom_tree = proto_item_add_subtree(ti, ett_oml_fom);
++	proto_tree_add_item(fom_tree, hf_oml_fom_objclass, tvb, offset++, 1, FALSE);
++	proto_tree_add_item(fom_tree, hf_oml_fom_inst_bts, tvb, offset++, 1, FALSE);
++	proto_tree_add_item(fom_tree, hf_oml_fom_inst_trx, tvb, offset++, 1, FALSE);
++	proto_tree_add_item(fom_tree, hf_oml_fom_inst_ts, tvb, offset++, 1, FALSE);
++
++
++	/* dissect the TLV objects in the message body */
++	offset = dissect_oml_attrs(tvb, offset, pinfo, fom_tree);
++
++	return offset;
++}
++
++static const guint8 ipaccess_magic[] = "com.ipaccess";
++
++static int
++dissect_oml_manuf(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
++		  int offset, proto_item *top_ti)
++{
++	if (tvb_get_guint8(tvb, offset) != 0x0d ||
++	    tvb_memeql(tvb, offset+1, ipaccess_magic, sizeof(ipaccess_magic)))
++		return offset;
++
++	offset += sizeof(ipaccess_magic) + 1;
++
++	return dissect_oml_fom(tvb, pinfo, tree, offset, top_ti);
++}
++
++static void
++dissect_abis_oml(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
++{
++	proto_item *ti;
++	proto_tree *oml_tree;
++
++	int offset = 0;
++
++	col_set_str(pinfo->cinfo, COL_PROTOCOL, "OML");
++
++	top_tree = tree;
++	if (tree) {
++		u_int8_t msg_disc = tvb_get_guint8(tvb, offset);
++
++		ti = proto_tree_add_item(tree, proto_abis_oml, tvb, 0, -1, FALSE);
++		oml_tree = proto_item_add_subtree(ti, ett_oml);
++
++		proto_tree_add_item(oml_tree, hf_oml_msg_disc, tvb, offset++,
++				    1, TRUE);
++		proto_tree_add_item(oml_tree, hf_oml_placement, tvb, offset++,
++				    1, TRUE);
++		proto_tree_add_item(oml_tree, hf_oml_sequence, tvb, offset++,
++				    1, TRUE);
++		proto_tree_add_item(oml_tree, hf_oml_length, tvb, offset++,
++				    1, TRUE);
++
++		switch (msg_disc) {
++		case ABIS_OM_MDISC_FOM:
++			offset = dissect_oml_fom(tvb, pinfo, oml_tree,
++						 offset, ti);
++			break;
++		case ABIS_OM_MDISC_MANUF:
++			offset = dissect_oml_manuf(tvb, pinfo, oml_tree,							       offset, ti);
++			break;
++		case ABIS_OM_MDISC_MMI:
++		case ABIS_OM_MDISC_TRAU:
++		default:
++			break;
++		}
++	}
++}
++
++void
++proto_reg_handoff_abis_oml(void);
++
++void
++proto_register_abis_oml(void)
++{
++	static hf_register_info hf[] = {
++		{ &hf_oml_msg_disc,
++			{ "Message Discriminator", "oml.msg_dsc",
++			  FT_UINT8, BASE_HEX, VALS(oml_msg_disc_vals), 0,
++			  "GSM 12.21 Message Discriminator", HFILL }
++		},
++		{ &hf_oml_placement,
++			{ "Placement Indicator", "oml.placement",
++			  FT_UINT8, BASE_HEX, VALS(oml_placement_vals), 0,
++			  "GSM 12.21 Placement Indicator", HFILL }
++		},
++		{ &hf_oml_sequence,
++			{ "Sequence Number", "oml.sequence",
++			  FT_UINT8, BASE_HEX, NULL, 0,
++			  "Sequence Number (if multi-part msg)", HFILL }
++		},
++		{ &hf_oml_length,
++			{ "Length Indicator", "oml.length",
++			  FT_UINT8, BASE_DEC, NULL, 0,
++			  "Total length of payload", HFILL }
++		},
++		{ &hf_oml_fom_msgtype,
++			{ "FOM Message Type", "oml.fom.msg_type",
++			  FT_UINT8, BASE_HEX, VALS(oml_fom_msgtype_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_oml_fom_objclass,
++			{ "FOM Object Class", "oml.fom.obj_class",
++			  FT_UINT8, BASE_HEX, VALS(oml_fom_objclass_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_oml_fom_inst_bts,
++			{ "FOM Object Instance BTS", "oml.fom.obj_inst.bts",
++			  FT_UINT8, BASE_DEC, NULL, 0,
++			  NULL, HFILL }
++		},
++		{ &hf_oml_fom_inst_trx,
++			{ "FOM Object Instance TRX", "oml.fom.obj_inst.trx",
++			  FT_UINT8, BASE_DEC, NULL, 0,
++			  NULL, HFILL }
++		},
++		{ &hf_oml_fom_inst_ts,
++			{ "FOM Object Instance TS", "oml.fom.obj_inst.ts",
++			  FT_UINT8, BASE_DEC, NULL, 0,
++			  NULL, HFILL }
++		},
++		{ &hf_oml_fom_attr_tag,
++			{ "FOM Attribute ID", "oml.fom.attr_id",
++			  FT_UINT8, BASE_HEX, VALS(oml_fom_attr_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_oml_fom_attr_len,
++			{ "FOM Attribute Length", "oml.fom.attr_len",
++			  FT_UINT16, BASE_DEC, NULL, 0,
++			  NULL, HFILL }
++		},
++		{ &hf_oml_fom_attr_val,
++			{ "FOM Attribute Value", "oml.fom.attr_val",
++			  FT_BYTES, BASE_NONE, NULL, 0,
++			  NULL, HFILL }
++		},
++
++
++
++		/* OML Attributes */
++		{ &hf_attr_adm_state,
++			{ "Administrative State", "oml.fom.attr.adm_state",
++			  FT_UINT8, BASE_HEX, VALS(oml_adm_state_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_arfcn,
++			{ "ARFCN", "oml.fom.attr.arfcn",
++			  FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_oper_state,
++			{ "Operational State", "oml.fom.attr.oper_state",
++			  FT_UINT8, BASE_HEX, VALS(oml_oper_state_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_avail_state,
++			{ "Availability Status", "oml.fom.attr.avail_state",
++			  FT_UINT8, BASE_HEX, VALS(oml_avail_state_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_event_type,
++			{ "Event Type", "oml.fom.attr.event_type",
++			  FT_UINT8, BASE_HEX, VALS(oml_event_type_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_severity,
++			{ "Severity", "oml.fom.attr.severity",
++			  FT_UINT8, BASE_HEX, VALS(oml_severity_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_bcch_arfcn,
++			{ "BCCH ARFCN", "oml.fom.attr.bcch_arfcn",
++			  FT_UINT16, BASE_DEC, NULL, 0,
++			  "ARFCN of the BCCH", HFILL }
++		},
++		{ &hf_attr_bsic,
++			{ "BSIC", "oml.fom.attr.bsic",
++			  FT_UINT16, BASE_HEX, NULL, 0,
++			  "Base Station Identity Cdoe", HFILL }
++		},
++		{ &hf_attr_test_no,
++			{ "Test Number", "oml.fom.attr.test_no",
++			  FT_UINT8, BASE_HEX, VALS(oml_test_no_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_tsc,
++			{ "TSC", "oml.fom.attr.tsc",
++			  FT_UINT8, BASE_HEX, NULL, 0,
++			  "Training Sequence Code", HFILL }
++		},
++		{ &hf_attr_tei,
++			{ "TEI", "oml.fom.attr.tei",
++			  FT_UINT8, BASE_DEC, NULL, 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_ach_btsp,
++			{ "BTS E1 Port", "oml.fom.attr.abis_ch.bts_port",
++			  FT_UINT8, BASE_DEC, NULL, 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_ach_tslot,
++			{ "E1 Timeslot", "oml.fom.attr.abis_ch.timeslot",
++			  FT_UINT8, BASE_DEC, NULL, 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_ach_sslot,
++			{ "E1 Subslot", "oml.fom.attr.abis_ch.subslot",
++			  FT_UINT8, BASE_DEC, NULL, 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_gsm_time,
++			{ "GSM Time", "oml.fom.attr.gsm_time",
++			  FT_UINT16, BASE_DEC, NULL, 0,
++			  "GSM Time", HFILL }
++		},
++		{ &hf_attr_chan_comb,
++			{ "Channel Combination", "oml.fom.attr.chan_comb",
++			  FT_UINT8, BASE_HEX, VALS(oml_chan_comb_vals), 0,
++			  NULL, HFILL }
++		},
++		/* IP Access */
++		{ &hf_oml_ipa_tres_attr_tag,
++			{ "IPA Test Result Embedded IE",
++						"oml.fom.testrep.ipa_tag",
++			  FT_UINT8, BASE_HEX, VALS(ipacc_testres_ie_vals), 0,
++			  "Information Element embedded into the Test Result "
++			  "of ip.access BTS", HFILL },
++		},
++		{ &hf_oml_ipa_tres_attr_len,
++			{ "IPA Test Result Embedded IE Length",
++						"oml.fom.testrep.ipa_len",
++			  FT_UINT16, BASE_DEC, NULL, 0,
++			  "Length of ip.access Test Result Embedded IE", HFILL }
++		},
++		{ &hf_attr_ipa_test_res,
++			{ "IPA Test Result", "oml.fom.testrep.result",
++			  FT_UINT8, BASE_DEC, VALS(ipacc_test_res_vals), 0,
++			  NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_rxlev,
++			{ "Rx Level", "oml.fom.testrep.ipa_rxlev",
++			  FT_UINT16, BASE_DEC, NULL, 0xfc00, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_b_rxlev,
++			{ "Rx Level", "oml.fom.testrep.ipa_rxlev_b",
++			  FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_arfcn,
++			{ "ARFCN", "oml.fom.testrep.ipa_arfcn",
++			  FT_UINT16, BASE_DEC, NULL, 0x03ff, "ARFCN", HFILL }
++		},
++		{ &hf_attr_ipa_tr_f_qual,
++			{ "Frequency Quality", "oml.fom.testrep.ipa.freq_qual",
++			  FT_UINT8, BASE_DEC, NULL, 0xfc, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_f_err,
++			{ "Frequency Error", "oml.fom.testrep.ipa.freq_err",
++			  FT_INT16, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_rxqual,
++			{ "Rx Quality", "oml.fom.testrep.ipa.rx_qual",
++			  FT_UINT8, BASE_DEC, NULL, 0x7, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_frame_offs,
++			{ "Frame Offset", "oml.fom.testrep.ipa.frame_offset",
++			  FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_framenr_offs,
++			{ "Frame Number Offset",
++					"oml.fom.testrep.ipa.framenr_offset",
++			  FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_bsic,
++			{ "BSIC", "oml.fom.testrep.ipa.bsic",
++			  FT_UINT8, BASE_DEC, NULL, 0x3f,
++			  "Base Station Identity Code", HFILL }
++		},
++		{ &hf_attr_ipa_tr_cell_id,
++			{ "Cell ID", "oml.fom.testrep.ipa.cell_id",
++			  FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_rsl_ip,
++			{ "BSC RSL IP Address", "oml.fom.attr.ipa.rsl_ip",
++			  FT_IPv4, BASE_NONE, NULL, 0,
++			  "IP Address to which the BTS establishes "
++			  "the RSL link", HFILL }
++		},
++		{ &hf_attr_ipa_rsl_port,
++			{ "BSC RSL TCP Port", "oml.fom.attr.ipa.rsl_port",
++			  FT_UINT16, BASE_DEC, NULL, 0,
++			  "Port number to which the BST establishes "
++			  "the RSL link", HFILL }
++		},
++		{ &hf_attr_ipa_prim_oml_ip,
++			{ "Primary OML IP Address",
++					"oml.fom.attr.ipa.prim_oml_ip",
++			  FT_IPv4, BASE_NONE, NULL, 0,
++			  "IP Address of the BSC for the primary OML link",
++			  HFILL }
++		},
++		{ &hf_attr_ipa_prim_oml_port,
++			{ "Primary OML TCP Port",
++					"oml.fom.attr.ipa.prim_oml_port",
++			  FT_UINT16, BASE_DEC, NULL, 0,
++			  "TCP Port of the BSC for the primarly OML link",
++			  HFILL }
++		},
++		{ &hf_attr_ipa_location_name,
++			{ "Location Name", "oml.fom.attr.ipa.loc_name",
++			  FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_unit_name,
++			{ "Unit Name", "oml.fom.attr.ipa.unit_name",
++			  FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_unit_id,
++			{ "Unit ID", "oml.fom.attr.ipa.unit_id",
++			  FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_nv_flags,
++			{ "NVRAM Config Flags", "oml.fom.attr.ipa.nv_flags",
++			  FT_UINT16, BASE_HEX, NULL, 0xffff, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_nv_mask,
++			{ "NVRAM Config Mask", "oml.fom.attr.ipa.nv_mask",
++			  FT_UINT16, BASE_HEX, NULL, 0xffff, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_si2,
++			{ "System Information 2", "oml.fom.attr.ipa.si2",
++			  FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_si2bis,
++			{ "System Information 2bis", "oml.fom.attr.ipa.si2bis",
++			  FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_si2ter,
++			{ "System Information 2ter", "oml.fom.attr.ipa.si2ter",
++			  FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_tr_chan_desc,
++			{ "Cell Channel Description",
++						"oml.fom.attr.ipa.chan_desc",
++			  FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_nsl_sport,
++			{ "NS Link IP Source Port",
++						"oml.fom.attr.ipa.nsl_sport",
++			  FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_nsl_daddr,
++			{ "NS Link IP Destination Addr",
++						"oml.fom.attr.ipa.nsl_daddr",
++			  FT_IPv4, BASE_NONE, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_nsl_dport,
++			{ "NS Link IP Destination Port",
++						"oml.fom.attr.ipa.nsl_dport",
++			  FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_nsei,
++			{ "NSEI", "oml.fom.attr.ipa.nsei",
++			  FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_nsvci,
++			{ "NSVCI", "oml.fom.attr.ipa.nsvci",
++			  FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_bvci,
++			{ "BVCI", "oml.fom.attr.ipa.bvci",
++			  FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_attr_ipa_rac,
++			{ "RAC", "oml.fom.attr.ipa.rac",
++			  FT_UINT8, BASE_HEX, NULL, 0,
++			  "Routing Area Code", HFILL }
++		},
++	};
++	static gint *ett[] = {
++		&ett_oml,
++		&ett_oml_fom,
++		&ett_oml_fom_att,
++	};
++
++	module_t *oml_module;
++
++	proto_abis_oml = proto_register_protocol("GSM A-bis OML", "OML",
++						 "gsm_abis_oml");
++
++	proto_register_field_array(proto_abis_oml, hf, array_length(hf));
++
++	proto_register_subtree_array(ett, array_length(ett));
++
++	register_dissector("gsm_abis_oml", dissect_abis_oml, proto_abis_oml);
++
++
++	oml_module = prefs_register_protocol(proto_abis_oml, proto_reg_handoff_abis_oml);
++	prefs_register_bool_preference(oml_module, "use_ipaccess_oml",
++		    "Use nanoBTS definitions",
++		    "Use ipaccess nanoBTS specific definitions for OML",
++		    &global_oml_use_nano_bts);
++}
++
++void
++proto_reg_handoff_abis_oml(void)
++{
++	dissector_handle_t abis_oml_handle;
++
++	abis_oml_handle = create_dissector_handle(dissect_abis_oml, proto_abis_oml);
++	dissector_add("lapd.gsm.sapi", LAPD_GSM_SAPI_OM_PROC, abis_oml_handle);
++}
+diff --git a/epan/dissectors/packet-gsm_abis_oml.h b/epan/dissectors/packet-gsm_abis_oml.h
+new file mode 100644
+index 0000000..d523e96
+--- /dev/null
++++ b/epan/dissectors/packet-gsm_abis_oml.h
+@@ -0,0 +1,787 @@
++/* GSM Network Management messages on the A-bis interface
++ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
++
++/* (C) 2008-2009 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 General Public License as published by
++ * the Free Software Foundation; either version 2 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 General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License along
++ * with this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ */
++
++#ifndef __PACKET_ABIS_OML_H__
++#define __PACKET_ABIS_OML_H__
++
++#include <sys/types.h>
++
++/* From openbsc/include/openbsc/abis_nm.h */
++
++/* generic header in front of every OML message according to TS 08.59 */
++struct abis_om_hdr {
++	guint8	mdisc;
++	guint8	placement;
++	guint8	sequence;
++	guint8	length;
++	guint8	data[0];
++} __attribute__ ((packed));
++
++#define ABIS_OM_MDISC_FOM		0x80
++#define ABIS_OM_MDISC_MMI		0x40
++#define ABIS_OM_MDISC_TRAU		0x20
++#define ABIS_OM_MDISC_MANUF		0x10
++#define ABIS_OM_PLACEMENT_ONLY		0x80
++#define ABIS_OM_PLACEMENT_FIRST 	0x40
++#define ABIS_OM_PLACEMENT_MIDDLE	0x20
++#define ABIS_OM_PLACEMENT_LAST		0x10
++
++struct abis_om_obj_inst {
++	guint8	bts_nr;
++	guint8	trx_nr;
++	guint8	ts_nr;
++} __attribute__ ((packed));
++
++struct abis_om_fom_hdr {
++	guint8	msg_type;
++	guint8	obj_class;
++	struct abis_om_obj_inst	obj_inst;
++	guint8	data[0];
++} __attribute__ ((packed));
++
++#define ABIS_OM_FOM_HDR_SIZE	(sizeof(struct abis_om_hdr) + sizeof(struct abis_om_fom_hdr))
++
++/* Section 9.1: Message Types */
++enum abis_nm_msgtype {
++	/* SW Download Management Messages */
++	NM_MT_LOAD_INIT			= 0x01,
++	NM_MT_LOAD_INIT_ACK,
++	NM_MT_LOAD_INIT_NACK,
++	NM_MT_LOAD_SEG,
++	NM_MT_LOAD_SEG_ACK,
++	NM_MT_LOAD_ABORT,
++	NM_MT_LOAD_END,
++	NM_MT_LOAD_END_ACK,
++	NM_MT_LOAD_END_NACK,
++	NM_MT_SW_ACT_REQ,		/* BTS->BSC */
++	NM_MT_SW_ACT_REQ_ACK,
++	NM_MT_SW_ACT_REQ_NACK,
++	NM_MT_ACTIVATE_SW,		/* BSC->BTS */
++	NM_MT_ACTIVATE_SW_ACK,
++	NM_MT_ACTIVATE_SW_NACK,
++	NM_MT_SW_ACTIVATED_REP,		/* 0x10 */
++	/* A-bis Interface Management Messages */
++	NM_MT_ESTABLISH_TEI		= 0x21,
++	NM_MT_ESTABLISH_TEI_ACK,
++	NM_MT_ESTABLISH_TEI_NACK,
++	NM_MT_CONN_TERR_SIGN,
++	NM_MT_CONN_TERR_SIGN_ACK,
++	NM_MT_CONN_TERR_SIGN_NACK,
++	NM_MT_DISC_TERR_SIGN,
++	NM_MT_DISC_TERR_SIGN_ACK,
++	NM_MT_DISC_TERR_SIGN_NACK,
++	NM_MT_CONN_TERR_TRAF,
++	NM_MT_CONN_TERR_TRAF_ACK,
++	NM_MT_CONN_TERR_TRAF_NACK,
++	NM_MT_DISC_TERR_TRAF,
++	NM_MT_DISC_TERR_TRAF_ACK,
++	NM_MT_DISC_TERR_TRAF_NACK,
++	/* Transmission Management Messages */
++	NM_MT_CONN_MDROP_LINK		= 0x31,
++	NM_MT_CONN_MDROP_LINK_ACK,
++	NM_MT_CONN_MDROP_LINK_NACK,
++	NM_MT_DISC_MDROP_LINK,
++	NM_MT_DISC_MDROP_LINK_ACK,
++	NM_MT_DISC_MDROP_LINK_NACK,
++	/* Air Interface Management Messages */
++	NM_MT_SET_BTS_ATTR		= 0x41,
++	NM_MT_SET_BTS_ATTR_ACK,
++	NM_MT_SET_BTS_ATTR_NACK,
++	NM_MT_SET_RADIO_ATTR,
++	NM_MT_SET_RADIO_ATTR_ACK,
++	NM_MT_SET_RADIO_ATTR_NACK,
++	NM_MT_SET_CHAN_ATTR,
++	NM_MT_SET_CHAN_ATTR_ACK,
++	NM_MT_SET_CHAN_ATTR_NACK,
++	/* Test Management Messages */
++	NM_MT_PERF_TEST			= 0x51,
++	NM_MT_PERF_TEST_ACK,
++	NM_MT_PERF_TEST_NACK,
++	NM_MT_TEST_REP,
++	NM_MT_SEND_TEST_REP,
++	NM_MT_SEND_TEST_REP_ACK,
++	NM_MT_SEND_TEST_REP_NACK,
++	NM_MT_STOP_TEST,
++	NM_MT_STOP_TEST_ACK,
++	NM_MT_STOP_TEST_NACK,
++	/* State Management and Event Report Messages */
++	NM_MT_STATECHG_EVENT_REP	= 0x61,
++	NM_MT_FAILURE_EVENT_REP,
++	NM_MT_STOP_EVENT_REP,
++	NM_MT_STOP_EVENT_REP_ACK,
++	NM_MT_STOP_EVENT_REP_NACK,
++	NM_MT_REST_EVENT_REP,
++	NM_MT_REST_EVENT_REP_ACK,
++	NM_MT_REST_EVENT_REP_NACK,
++	NM_MT_CHG_ADM_STATE,
++	NM_MT_CHG_ADM_STATE_ACK,
++	NM_MT_CHG_ADM_STATE_NACK,
++	NM_MT_CHG_ADM_STATE_REQ,
++	NM_MT_CHG_ADM_STATE_REQ_ACK,
++	NM_MT_CHG_ADM_STATE_REQ_NACK,
++	NM_MT_REP_OUTST_ALARMS		= 0x93,
++	NM_MT_REP_OUTST_ALARMS_ACK,
++	NM_MT_REP_OUTST_ALARMS_NACK,
++	/* Equipment Management Messages */
++	NM_MT_CHANGEOVER		= 0x71,
++	NM_MT_CHANGEOVER_ACK,
++	NM_MT_CHANGEOVER_NACK,
++	NM_MT_OPSTART,
++	NM_MT_OPSTART_ACK,
++	NM_MT_OPSTART_NACK,
++	NM_MT_REINIT,
++	NM_MT_REINIT_ACK,
++	NM_MT_REINIT_NACK,
++	NM_MT_SET_SITE_OUT,		/* BS11: get alarm ?!? */
++	NM_MT_SET_SITE_OUT_ACK,
++	NM_MT_SET_SITE_OUT_NACK,
++	NM_MT_CHG_HW_CONF		= 0x90,
++	NM_MT_CHG_HW_CONF_ACK,
++	NM_MT_CHG_HW_CONF_NACK,
++	/* Measurement Management Messages */
++	NM_MT_MEAS_RES_REQ		= 0x8a,
++	NM_MT_MEAS_RES_RESP,
++	NM_MT_STOP_MEAS,
++	NM_MT_START_MEAS,
++	/* Other Messages */
++	NM_MT_GET_ATTR			= 0x81,
++	NM_MT_GET_ATTR_RESP,
++	NM_MT_GET_ATTR_NACK,
++	NM_MT_SET_ALARM_THRES,
++	NM_MT_SET_ALARM_THRES_ACK,
++	NM_MT_SET_ALARM_THRES_NACK,
++
++	NM_MT_IPACC_RESTART		= 0x87,
++	NM_MT_IPACC_RESTART_ACK,
++};
++
++enum abis_nm_msgtype_bs11 {
++	NM_MT_BS11_RESET_RESOURCE	= 0x74,
++
++	NM_MT_BS11_BEGIN_DB_TX		= 0xa3,
++	NM_MT_BS11_BEGIN_DB_TX_ACK,
++	NM_MT_BS11_BEGIN_DB_TX_NACK,
++	NM_MT_BS11_END_DB_TX		= 0xa6,
++	NM_MT_BS11_END_DB_TX_ACK,
++	NM_MT_BS11_END_DB_TX_NACK,
++	NM_MT_BS11_CREATE_OBJ		= 0xa9,
++	NM_MT_BS11_CREATE_OBJ_ACK,
++	NM_MT_BS11_CREATE_OBJ_NACK,
++	NM_MT_BS11_DELETE_OBJ		= 0xac,
++	NM_MT_BS11_DELETE_OBJ_ACK,
++	NM_MT_BS11_DELETE_OBJ_NACK,
++
++	NM_MT_BS11_SET_ATTR		= 0xd0,
++	NM_MT_BS11_SET_ATTR_ACK,
++	NM_MT_BS11_SET_ATTR_NACK,
++	NM_MT_BS11_LMT_SESSION		= 0xdc,
++
++	NM_MT_BS11_GET_STATE		= 0xe3,
++	NM_MT_BS11_GET_STATE_ACK,
++	NM_MT_BS11_LMT_LOGON		= 0xe5,
++	NM_MT_BS11_LMT_LOGON_ACK,
++	NM_MT_BS11_RESTART		= 0xe7,
++	NM_MT_BS11_RESTART_ACK,
++	NM_MT_BS11_DISCONNECT		= 0xe9,
++	NM_MT_BS11_DISCONNECT_ACK,
++	NM_MT_BS11_LMT_LOGOFF		= 0xec,
++	NM_MT_BS11_LMT_LOGOFF_ACK,
++	NM_MT_BS11_RECONNECT		= 0xf1,
++	NM_MT_BS11_RECONNECT_ACK,
++};
++
++enum abis_nm_msgtype_ipacc {
++	NM_MT_IPACC_RSL_CONNECT		= 0xe0,
++	NM_MT_IPACC_RSL_CONNECT_ACK,
++	NM_MT_IPACC_RSL_CONNECT_NACK,
++	NM_MT_IPACC_RSL_DISCONNECT	= 0xe3,
++	NM_MT_IPACC_RSL_DISCONNECT_ACK,
++	NM_MT_IPACC_RSL_DISCONNECT_NACK,
++	NM_MT_IPACC_CONN_TRAF		= 0xe6,
++	NM_MT_IPACC_CONN_TRAF_ACK,
++	NM_MT_IPACC_CONN_TRAF_NACK,
++	NM_MT_IPACC_DISC_TRAF		= 0xe9,
++	NM_MT_IPACC_DISC_TRAF_ACK,
++	NM_MT_IPACC_DISC_TRAF_NACK,
++	NM_MT_IPACC_DEF_BOOT_SW		= 0xec,
++	NM_MT_IPACC_DEF_BOOT_SW_ACK,
++	NM_MT_IPACC_DEF_BOOT_SW_NACK,
++	NM_MT_IPACC_SET_NVATTR		= 0xef,
++	NM_MT_IPACC_SET_NVATTR_ACK,
++	NM_MT_IPACC_SET_NVATTR_NACK,
++	NM_MT_IPACC_GET_NVATTR		= 0xf2,
++	NM_MT_IPACC_GET_NVATTR_ACK,
++	NM_MT_IPACC_GET_NVATTR_NACK,
++	NM_MT_IPACC_SET_ATTR		= 0xf5,
++	NM_MT_IPACC_SET_ATTR_ACK,
++	NM_MT_IPACC_SET_ATTR_NACK,
++	NM_MT_IPACC_ATTR_CHG_EVT	= 0xf8,
++	NM_MT_IPACC_SW_DEACT		= 0xf9,
++	NM_MT_IPACC_SW_DEACT_ACK,
++	NM_MT_IPACC_SW_DEACT_NACK,
++	NM_MT_IPACC_MEAS_RES_REQ_NACK	= 0xfc,
++	NM_MT_IPACC_START_MEAS_NACK,
++	NM_MT_IPACC_STOP_MEAS_NACK,
++};
++
++enum abis_nm_bs11_cell_alloc {
++	NM_BS11_CANR_GSM	= 0x00,
++	NM_BS11_CANR_DCS1800	= 0x01,
++};
++
++/* Section 9.2: Object Class */
++enum abis_nm_obj_class {
++	NM_OC_SITE_MANAGER		= 0x00,
++	NM_OC_BTS,
++	NM_OC_RADIO_CARRIER,
++	NM_OC_CHANNEL,
++	NM_OC_BASEB_TRANSC,
++	/* RFU: 05-FE */
++	NM_OC_BS11_ADJC			= 0xa0,
++	NM_OC_BS11_HANDOVER		= 0xa1,
++	NM_OC_BS11_PWR_CTRL		= 0xa2,
++	NM_OC_BS11_BTSE			= 0xa3,		/* LMT? */
++	NM_OC_BS11_RACK			= 0xa4,
++	NM_OC_BS11			= 0xa5,		/* 01: ALCO */
++	NM_OC_BS11_TEST			= 0xa6,
++	NM_OC_BS11_ENVABTSE		= 0xa8,
++	NM_OC_BS11_BPORT		= 0xa9,
++
++	NM_OC_GPRS_NSE			= 0xf0,
++	NM_OC_GPRS_CELL			= 0xf1,
++	NM_OC_GPRS_NSVC0		= 0xf2,
++	NM_OC_GPRS_NSVC1		= 0xf3,
++
++	NM_OC_NULL			= 0xff,
++};
++
++/* Section 9.4: Attributes */
++enum abis_nm_attr {
++	NM_ATT_ABIS_CHANNEL	= 0x01,
++	NM_ATT_ADD_INFO,
++	NM_ATT_ADD_TEXT,
++	NM_ATT_ADM_STATE,
++	NM_ATT_ARFCN_LIST,
++	NM_ATT_AUTON_REPORT,
++	NM_ATT_AVAIL_STATUS,
++	NM_ATT_BCCH_ARFCN,
++	NM_ATT_BSIC,
++	NM_ATT_BTS_AIR_TIMER,
++	NM_ATT_CCCH_L_I_P,
++	NM_ATT_CCCH_L_T,
++	NM_ATT_CHAN_COMB,
++	NM_ATT_CONN_FAIL_CRIT,
++	NM_ATT_DEST,
++	/* res */
++	NM_ATT_EVENT_TYPE	= 0x11, /* BS11: file data ?!? */
++	NM_ATT_FILE_ID,
++	NM_ATT_FILE_VERSION,
++	NM_ATT_GSM_TIME,
++	NM_ATT_HSN,
++	NM_ATT_HW_CONFIG,
++	NM_ATT_HW_DESC,
++	NM_ATT_INTAVE_PARAM,
++	NM_ATT_INTERF_BOUND,
++	NM_ATT_LIST_REQ_ATTR,
++	NM_ATT_MAIO,
++	NM_ATT_MANUF_STATE,
++	NM_ATT_MANUF_THRESH,
++	NM_ATT_MANUF_ID,
++	NM_ATT_MAX_TA,
++	NM_ATT_MDROP_LINK,	/* 0x20 */
++	NM_ATT_MDROP_NEXT,
++	NM_ATT_NACK_CAUSES,
++	NM_ATT_NY1,
++	NM_ATT_OPER_STATE,
++	NM_ATT_OVERL_PERIOD,
++	NM_ATT_PHYS_CONF,
++	NM_ATT_POWER_CLASS,
++	NM_ATT_POWER_THRESH,
++	NM_ATT_PROB_CAUSE,
++	NM_ATT_RACH_B_THRESH,
++	NM_ATT_LDAVG_SLOTS,
++	NM_ATT_RAD_SUBC,
++	NM_ATT_RF_MAXPOWR_R,
++	NM_ATT_SITE_INPUTS,
++	NM_ATT_SITE_OUTPUTS,
++	NM_ATT_SOURCE,		/* 0x30 */
++	NM_ATT_SPEC_PROB,
++	NM_ATT_START_TIME,
++	NM_ATT_T200,
++	NM_ATT_TEI,
++	NM_ATT_TEST_DUR,
++	NM_ATT_TEST_NO,
++	NM_ATT_TEST_REPORT,
++	NM_ATT_VSWR_THRESH,
++	NM_ATT_WINDOW_SIZE,
++	/* Res  */
++	NM_ATT_BS11_RSSI_OFFS	= 0x3d,
++	NM_ATT_BS11_TXPWR	= 0x3e,
++	NM_ATT_BS11_DIVERSITY	= 0x3f,
++	/* Res  */
++	NM_ATT_TSC		= 0x40,
++	NM_ATT_SW_CONFIG,
++	NM_ATT_SW_DESCR,
++	NM_ATT_SEVERITY,
++	NM_ATT_GET_ARI,
++	NM_ATT_HW_CONF_CHG,
++	NM_ATT_OUTST_ALARM,
++	NM_ATT_FILE_DATA,
++	NM_ATT_MEAS_RES,
++	NM_ATT_MEAS_TYPE,
++
++	NM_ATT_BS11_ESN_FW_CODE_NO	= 0x4c,
++	NM_ATT_BS11_ESN_HW_CODE_NO	= 0x4f,
++
++	NM_ATT_BS11_ESN_PCB_SERIAL	= 0x55,
++	NM_ATT_BS11_EXCESSIVE_DISTANCE	= 0x58,
++
++	NM_ATT_BS11_ALL_TEST_CATG	= 0x60,
++	NM_ATT_BS11_BTSLS_HOPPING,
++	NM_ATT_BS11_CELL_ALLOC_NR,
++	NM_ATT_BS11_CELL_GLOBAL_ID,
++	NM_ATT_BS11_ENA_INTERF_CLASS	= 0x66,
++	NM_ATT_BS11_ENA_INT_INTEC_HANDO	= 0x67,
++	NM_ATT_BS11_ENA_INT_INTRC_HANDO	= 0x68,
++	NM_ATT_BS11_ENA_MS_PWR_CTRL	= 0x69,
++	NM_ATT_BS11_ENA_PWR_BDGT_HO	= 0x6a,
++	NM_ATT_BS11_ENA_PWR_CTRL_RLFW	= 0x6b,
++	NM_ATT_BS11_ENA_RXLEV_HO	= 0x6c,
++	NM_ATT_BS11_ENA_RXQUAL_HO	= 0x6d,
++	NM_ATT_BS11_FACCH_QUAL		= 0x6e,
++
++	NM_ATT_IPACC_DST_IP		= 0x80,
++	NM_ATT_IPACC_DST_IP_PORT	= 0x81,
++	NM_ATT_IPACC_SSRC		= 0x82,		/* RTP Sync Source */
++	NM_ATT_IPACC_RTP_PAYLD_TYPE	= 0x83,
++	NM_ATT_IPACC_BASEB_ID		= 0x84,
++	NM_ATT_IPACC_STREAM_ID		= 0x85,
++	NM_ATT_IPACC_NV_FLAGS		= 0x86,
++	NM_ATT_IPACC_FREQ_CTRL		= 0x87,
++	NM_ATT_IPACC_PRIM_OML_CFG	= 0x88,
++	NM_ATT_IPACC_SEC_OML_CFG	= 0x89,
++	NM_ATT_IPACC_IP_IF_CFG		= 0x8a,		/* IP interface */
++	NM_ATT_IPACC_IP_GW_CFG		= 0x8b,		/* IP gateway */
++	NM_ATT_IPACC_IN_SERV_TIME	= 0x8c,
++	NM_ATT_IPACC_TRX_BTS_ASS	= 0x8d,
++	NM_ATT_IPACC_LOCATION		= 0x8e,		/* string describing location */
++	NM_ATT_IPACC_PAGING_CFG		= 0x8f,
++	NM_ATT_IPACC_FILE_DATA		= 0x90,
++	NM_ATT_IPACC_UNIT_ID		= 0x91,		/* Site/BTS/TRX */
++	NM_ATT_IPACC_PARENT_UNIT_ID	= 0x92,
++	NM_ATT_IPACC_UNIT_NAME		= 0x93,		/* default: nbts-<mac-as-string> */
++	NM_ATT_IPACC_SNMP_CFG		= 0x94,
++	NM_ATT_IPACC_PRIM_OML_CFG_LIST	= 0x95,
++	NM_ATT_IPACC_PRIM_OML_FB_TOUT	= 0x96,		/* fallback timeout */
++	NM_ATT_IPACC_CUR_SW_CFG		= 0x97,
++	NM_ATT_IPACC_TIMING_BUS		= 0x98,
++	NM_ATT_IPACC_CGI		= 0x99,		/* Cell Global ID */
++	NM_ATT_IPACC_RAC		= 0x9a,
++	NM_ATT_IPACC_OBJ_VERSION	= 0x9b,
++	NM_ATT_IPACC_GPRS_PAGING_CFG	= 0x9c,
++	NM_ATT_IPACC_NSEI		= 0x9d,
++	NM_ATT_IPACC_BVCI		= 0x9e,
++	NM_ATT_IPACC_NSVCI		= 0x9f,
++	NM_ATT_IPACC_NS_CFG		= 0xa0,
++	NM_ATT_IPACC_BSSGP_CFG		= 0xa1,
++	NM_ATT_IPACC_NS_LINK_CFG	= 0xa2,
++	NM_ATT_IPACC_RLC_CFG		= 0xa3,
++	NM_ATT_IPACC_ALM_THRESH_LIST	= 0xa4,
++	NM_ATT_IPACC_MONIT_VAL_LIST	= 0xa5,
++	NM_ATT_IPACC_TIB_CONTROL	= 0xa6,
++	NM_ATT_IPACC_SUPP_FEATURES	= 0xa7,
++	NM_ATT_IPACC_CODING_SCHEMES	= 0xa8,
++	NM_ATT_IPACC_RLC_CFG_2		= 0xa9,
++	NM_ATT_IPACC_HEARTB_TOUT	= 0xaa,
++	NM_ATT_IPACC_UPTIME		= 0xab,
++	NM_ATT_IPACC_RLC_CFG_3		= 0xac,
++	NM_ATT_IPACC_SSL_CFG		= 0xad,
++	NM_ATT_IPACC_SEC_POSSIBLE	= 0xae,
++	NM_ATT_IPACC_IML_SSL_STATE	= 0xaf,
++	NM_ATT_IPACC_REVOC_DATE		= 0xb0,
++
++
++	NM_ATT_BS11_RF_RES_IND_PER	= 0x8f,
++
++	NM_ATT_BS11_RX_LEV_MIN_CELL	= 0x90,
++	NM_ATT_BS11_ABIS_EXT_TIME	= 0x91,
++	NM_ATT_BS11_TIMER_HO_REQUEST	= 0x92,
++	NM_ATT_BS11_TIMER_NCELL		= 0x93,
++	NM_ATT_BS11_TSYNC		= 0x94,
++	NM_ATT_BS11_TTRAU		= 0x95,
++	NM_ATT_BS11_EMRG_CFG_MEMBER	= 0x9b,
++	NM_ATT_BS11_TRX_AREA		= 0x9f,
++
++	NM_ATT_BS11_BCCH_RECONF		= 0xd7,
++	NM_ATT_BS11_BIT_ERR_THESH	= 0xa0,
++	NM_ATT_BS11_BOOT_SW_VERS	= 0xa1,
++	NM_ATT_BS11_CCLK_ACCURACY	= 0xa3,
++	NM_ATT_BS11_CCLK_TYPE		= 0xa4,
++	NM_ATT_BS11_INP_IMPEDANCE	= 0xaa,
++	NM_ATT_BS11_L1_PROT_TYPE	= 0xab,
++	NM_ATT_BS11_LINE_CFG		= 0xac,
++	NM_ATT_BS11_LI_PORT_1		= 0xad,
++	NM_ATT_BS11_LI_PORT_2		= 0xae,
++
++	NM_ATT_BS11_L1_REM_ALM_TYPE	= 0xb0,
++	NM_ATT_BS11_SW_LOAD_INTENDED	= 0xbb,
++	NM_ATT_BS11_SW_LOAD_SAFETY	= 0xbc,
++	NM_ATT_BS11_SW_LOAD_STORED	= 0xbd,
++
++	NM_ATT_BS11_VENDOR_NAME		= 0xc1,
++	NM_ATT_BS11_HOPPING_MODE	= 0xc5,
++	NM_ATT_BS11_LMT_LOGON_SESSION	= 0xc6,
++	NM_ATT_BS11_LMT_LOGIN_TIME	= 0xc7,
++	NM_ATT_BS11_LMT_USER_ACC_LEV	= 0xc8,
++	NM_ATT_BS11_LMT_USER_NAME	= 0xc9,
++
++	NM_ATT_BS11_L1_CONTROL_TS	= 0xd8,
++	NM_ATT_BS11_RADIO_MEAS_GRAN	= 0xdc,	/* in SACCH multiframes */
++	NM_ATT_BS11_RADIO_MEAS_REP	= 0xdd,
++
++	NM_ATT_BS11_SH_LAPD_INT_TIMER	= 0xe8,
++
++	NM_ATT_BS11_BTS_STATE		= 0xf0,
++	NM_ATT_BS11_E1_STATE		= 0xf1,
++	NM_ATT_BS11_PLL			= 0xf2,
++	NM_ATT_BS11_RX_OFFSET		= 0xf3,
++	NM_ATT_BS11_ANT_TYPE		= 0xf4,
++	NM_ATT_BS11_PLL_MODE		= 0xfc,
++	NM_ATT_BS11_PASSWORD		= 0xfd,
++};
++#define NM_ATT_BS11_FILE_DATA	NM_ATT_EVENT_TYPE
++
++/* Section 9.4.4: Administrative State */
++enum abis_nm_adm_state {
++	NM_STATE_LOCKED		= 0x01,
++	NM_STATE_UNLOCKED	= 0x02,
++	NM_STATE_SHUTDOWN	= 0x03,
++	NM_STATE_NULL		= 0xff,
++};
++
++/* Section 9.4.13: Channel Combination */
++enum abis_nm_chan_comb {
++	NM_CHANC_TCHFull	= 0x00,
++	NM_CHANC_TCHHalf	= 0x01,
++	NM_CHANC_TCHHalf2	= 0x02,
++	NM_CHANC_SDCCH		= 0x03,
++	NM_CHANC_mainBCCH	= 0x04,
++	NM_CHANC_BCCHComb	= 0x05,
++	NM_CHANC_BCCH		= 0x06,
++	NM_CHANC_BCCH_CBCH	= 0x07,
++	NM_CHANC_SDCCH_CBCH	= 0x08,
++};
++
++/* Section 9.4.16: Event Type */
++enum abis_nm_event_type {
++	NM_EVT_COMM_FAIL	= 0x00,
++	NM_EVT_QOS_FAIL		= 0x01,
++	NM_EVT_PROC_FAIL	= 0x02,
++	NM_EVT_EQUIP_FAIL	= 0x03,
++	NM_EVT_ENV_FAIL		= 0x04,
++};
++
++/* Section: 9.4.63: Perceived Severity */
++enum abis_nm_severity {
++	NM_SEVER_CEASED		= 0x00,
++	NM_SEVER_CRITICAL	= 0x01,
++	NM_SEVER_MAJOR		= 0x02,
++	NM_SEVER_MINOR		= 0x03,
++	NM_SEVER_WARNING	= 0x04,
++	NM_SEVER_INDETERMINATE	= 0x05,
++};
++
++/* Section 9.4.43: Probable Cause Type */
++enum abis_nm_pcause_type {
++	NM_PCAUSE_T_X721	= 0x01,
++	NM_PCAUSE_T_GSM		= 0x02,
++	NM_PCAUSE_T_MANUF	= 0x03,
++};
++
++/* Section 9.4.36: NACK Causes */
++enum abis_nm_nack_cause {
++	/* General Nack Causes */
++	NM_NACK_INCORR_STRUCT		= 0x01,
++	NM_NACK_MSGTYPE_INVAL		= 0x02,
++	NM_NACK_OBJCLASS_INVAL		= 0x05,
++	NM_NACK_OBJCLASS_NOTSUPP	= 0x06,
++	NM_NACK_BTSNR_UNKN		= 0x07,
++	NM_NACK_TRXNR_UNKN		= 0x08,
++	NM_NACK_OBJINST_UNKN		= 0x09,
++	NM_NACK_ATTRID_INVAL		= 0x0c,
++	NM_NACK_ATTRID_NOTSUPP		= 0x0d,
++	NM_NACK_PARAM_RANGE		= 0x0e,
++	NM_NACK_ATTRLIST_INCONSISTENT	= 0x0f,
++	NM_NACK_SPEC_IMPL_NOTSUPP	= 0x10,
++	NM_NACK_CANT_PERFORM		= 0x11,
++	/* Specific Nack Causes */
++	NM_NACK_RES_NOTIMPL		= 0x19,
++	NM_NACK_RES_NOTAVAIL		= 0x1a,
++	NM_NACK_FREQ_NOTAVAIL		= 0x1b,
++	NM_NACK_TEST_NOTSUPP		= 0x1c,
++	NM_NACK_CAPACITY_RESTR		= 0x1d,
++	NM_NACK_PHYSCFG_NOTPERFORM	= 0x1e,
++	NM_NACK_TEST_NOTINIT		= 0x1f,
++	NM_NACK_PHYSCFG_NOTRESTORE	= 0x20,
++	NM_NACK_TEST_NOSUCH		= 0x21,
++	NM_NACK_TEST_NOSTOP		= 0x22,
++	NM_NACK_MSGINCONSIST_PHYSCFG	= 0x23,
++	NM_NACK_FILE_INCOMPLETE		= 0x25,
++	NM_NACK_FILE_NOTAVAIL		= 0x26,
++	NM_NACK_FILE_NOTACTIVATE	= 0x27,
++	NM_NACK_REQ_NOT_GRANT		= 0x28,
++	NM_NACK_WAIT			= 0x29,
++	NM_NACK_NOTH_REPORT_EXIST	= 0x2a,
++	NM_NACK_MEAS_NOTSUPP		= 0x2b,
++	NM_NACK_MEAS_NOTSTART		= 0x2c,
++};
++
++/* Section 9.4.1 */
++struct abis_nm_channel {
++	guint8	attrib;
++	guint8	bts_port;
++	guint8	timeslot;
++	guint8	subslot;
++} __attribute__ ((packed));
++
++/* Siemens BS-11 specific objects in the SienemsHW (0xA5) object class */
++enum abis_bs11_objtype {
++	BS11_OBJ_ALCO		= 0x01,
++	BS11_OBJ_BBSIG		= 0x02,	/* obj_class: 0,1 */
++	BS11_OBJ_TRX1		= 0x03,	/* only DEACTIVATE TRX1 */
++	BS11_OBJ_CCLK		= 0x04,
++	BS11_OBJ_GPSU		= 0x06,
++	BS11_OBJ_LI		= 0x07,
++	BS11_OBJ_PA		= 0x09,	/* obj_class: 0, 1*/
++};
++
++enum abis_bs11_trx_power {
++	BS11_TRX_POWER_GSM_2W	= 0x06,
++	BS11_TRX_POWER_GSM_250mW= 0x07,
++	BS11_TRX_POWER_GSM_80mW	= 0x08,
++	BS11_TRX_POWER_GSM_30mW	= 0x09,
++	BS11_TRX_POWER_DCS_3W	= 0x0a,
++	BS11_TRX_POWER_DCS_1W6	= 0x0b,
++	BS11_TRX_POWER_DCS_500mW= 0x0c,
++	BS11_TRX_POWER_DCS_160mW= 0x0d,
++};
++
++enum abis_bs11_li_pll_mode {
++	BS11_LI_PLL_LOCKED	= 2,
++	BS11_LI_PLL_STANDALONE	= 3,
++};
++
++enum abis_bs11_phase {
++	BS11_STATE_SOFTWARE_RQD		= 0x01,
++	BS11_STATE_LOAD_SMU_INTENDED	= 0x11,
++	BS11_STATE_LOAD_SMU_SAFETY	= 0x21,
++	BS11_STATE_LOAD_FAILED		= 0x31,
++	BS11_STATE_LOAD_DIAGNOSTIC	= 0x41,
++	BS11_STATE_WARM_UP		= 0x51,
++	BS11_STATE_WARM_UP_2		= 0x52,
++	BS11_STATE_WAIT_MIN_CFG		= 0x62,
++	BS11_STATE_MAINTENANCE		= 0x72,
++	BS11_STATE_LOAD_MBCCU		= 0x92,
++	BS11_STATE_WAIT_MIN_CFG_2	= 0xA2,
++	BS11_STATE_NORMAL		= 0x03,
++	BS11_STATE_ABIS_LOAD		= 0x13,
++};
++
++/* From openbsc/include/openbsc/tlv.h */
++enum tlv_type {
++	TLV_TYPE_FIXED,
++	TLV_TYPE_T,
++	TLV_TYPE_TV,
++	TLV_TYPE_TLV,
++	TLV_TYPE_TL16V,
++};
++
++struct tlv_def {
++	enum tlv_type type;
++	u_int8_t fixed_len;
++};
++
++struct tlv_definition {
++	struct tlv_def def[0xff];
++};
++
++enum abis_nm_ipacc_test_no {
++	NM_IPACC_TESTNO_RLOOP_ANT	= 0x01,
++	NM_IPACC_TESTNO_RLOOP_XCVR	= 0x02,
++	NM_IPACC_TESTNO_FUNC_OBJ	= 0x03,
++	NM_IPACC_TESTNO_CHAN_USAGE	= 0x40,
++	NM_IPACC_TESTNO_BCCH_CHAN_USAGE	= 0x41,
++	NM_IPACC_TESTNO_FREQ_SYNC	= 0x42,
++	NM_IPACC_TESTNO_BCCH_INFO	= 0x43,
++	NM_IPACC_TESTNO_TX_BEACON	= 0x44,
++	NM_IPACC_TESTNO_SYSINFO_MONITOR	= 0x45,
++	NM_IPACC_TESTNO_BCCCH_MONITOR	= 0x46,
++};
++
++/* first byte after length inside NM_ATT_TEST_REPORT */
++enum abis_nm_ipacc_test_res {
++	NM_IPACC_TESTRES_SUCCESS	= 0,
++	NM_IPACC_TESTRES_TIMEOUT	= 1,
++	NM_IPACC_TESTRES_NO_CHANS	= 2,
++	NM_IPACC_TESTRES_PARTIAL	= 3,
++	NM_IPACC_TESTRES_STOPPED	= 4,
++};
++
++/* internal IE inside NM_ATT_TEST_REPORT */
++enum abis_nm_ipacc_testres_ie {
++	NM_IPACC_TR_IE_FREQ_ERR_LIST	= 3,
++	NM_IPACC_TR_IE_CHAN_USAGE	= 4,
++	NM_IPACC_TR_IE_BCCH_INFO	= 6,
++	NM_IPACC_TR_IE_RESULT_DETAILS	= 8,
++	NM_IPACC_TR_IE_FREQ_ERR		= 18,
++};
++
++/* From openbsc/src/abis_nm.c */
++static const struct tlv_definition nm_att_tlvdef = {
++	.def = {
++		[NM_ATT_ABIS_CHANNEL] =		{ TLV_TYPE_FIXED, 3 },
++		[NM_ATT_ADD_INFO] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_ADD_TEXT] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_ADM_STATE] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_ARFCN_LIST]=		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_AUTON_REPORT] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_BCCH_ARFCN] =		{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_BSIC] =			{ TLV_TYPE_TV, 0 },
++		[NM_ATT_BTS_AIR_TIMER] =	{ TLV_TYPE_TV, 0 },
++		[NM_ATT_CCCH_L_I_P] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_CCCH_L_T] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_CHAN_COMB] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_CONN_FAIL_CRIT] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_DEST] =			{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_EVENT_TYPE] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_GSM_TIME] =		{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_HSN] =			{ TLV_TYPE_TV, 0 },
++		[NM_ATT_HW_CONFIG] =		{ TLV_TYPE_TL16V, 0 },
++		//BS11 [NM_ATT_HW_DESC] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_HW_DESC] =		{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_INTAVE_PARAM] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_INTERF_BOUND] =		{ TLV_TYPE_FIXED, 6 },
++		[NM_ATT_LIST_REQ_ATTR] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_MAIO] =			{ TLV_TYPE_TV, 0 },
++		[NM_ATT_MANUF_STATE] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_MANUF_THRESH] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_MANUF_ID] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_MAX_TA] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_MDROP_LINK] =		{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_MDROP_NEXT] =		{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_NACK_CAUSES] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_NY1] =			{ TLV_TYPE_TV, 0 },
++		[NM_ATT_OPER_STATE] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_OVERL_PERIOD] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_PHYS_CONF] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_POWER_CLASS] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_POWER_THRESH] =		{ TLV_TYPE_FIXED, 3 },
++		[NM_ATT_PROB_CAUSE] =		{ TLV_TYPE_FIXED, 3 },
++		[NM_ATT_RACH_B_THRESH] =	{ TLV_TYPE_TV, 0 },
++		[NM_ATT_LDAVG_SLOTS] =		{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_RAD_SUBC] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_RF_MAXPOWR_R] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_SITE_INPUTS] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_SITE_OUTPUTS] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_SOURCE] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_SPEC_PROB] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_START_TIME] =		{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_T200] =			{ TLV_TYPE_FIXED, 7 },
++		[NM_ATT_TEI] =			{ TLV_TYPE_TV, 0 },
++		[NM_ATT_TEST_DUR] =		{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_TEST_NO] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_TEST_REPORT] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_VSWR_THRESH] =		{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_WINDOW_SIZE] = 		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_TSC] =			{ TLV_TYPE_TV, 0 },
++		[NM_ATT_SW_CONFIG] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_SEVERITY] = 		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_GET_ARI] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_HW_CONF_CHG] = 		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_OUTST_ALARM] =		{ TLV_TYPE_TV, 0 },
++		[NM_ATT_FILE_DATA] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_MEAS_RES] =		{ TLV_TYPE_TL16V, 0 },
++#if 0
++		/* BS11 specifics */
++		[NM_ATT_BS11_ESN_FW_CODE_NO] =	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_ESN_HW_CODE_NO] =	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_ESN_PCB_SERIAL] =	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_BOOT_SW_VERS] =	{ TLV_TYPE_TLV, 0 },
++		[0xd5] =			{ TLV_TYPE_TLV, 0 },
++		[0xa8] =			{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_PASSWORD] =	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_TXPWR] =		{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_RSSI_OFFS] =	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_LINE_CFG] = 	{ TLV_TYPE_TV, 0 },
++		[NM_ATT_BS11_L1_PROT_TYPE] =	{ TLV_TYPE_TV, 0 },
++		[NM_ATT_BS11_BIT_ERR_THESH] =	{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_BS11_DIVERSITY] =	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_LMT_LOGIN_TIME] =	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_LMT_USER_NAME] =	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_BTS_STATE]	=	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_E1_STATE]	=	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_PLL_MODE]	=	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_PLL]	=	{ TLV_TYPE_TLV, 0 },
++		[NM_ATT_BS11_CCLK_ACCURACY] =	{ TLV_TYPE_TV, 0 },
++		[NM_ATT_BS11_CCLK_TYPE] =	{ TLV_TYPE_TV, 0 },
++		[0x95] =			{ TLV_TYPE_FIXED, 2 },
++#endif
++		/* ip.access specifics */
++		[NM_ATT_IPACC_DST_IP] =		{ TLV_TYPE_FIXED, 4 },
++		[NM_ATT_IPACC_DST_IP_PORT] =	{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_IPACC_PRIM_OML_CFG] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_NV_FLAGS] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_FREQ_CTRL] =	{ TLV_TYPE_FIXED, 2 },
++		[NM_ATT_IPACC_SEC_OML_CFG] =	{ TLV_TYPE_FIXED, 6 },
++		[NM_ATT_IPACC_IP_IF_CFG] =	{ TLV_TYPE_FIXED, 8 },
++		[NM_ATT_IPACC_IP_GW_CFG] =	{ TLV_TYPE_FIXED, 12 },
++		[NM_ATT_IPACC_LOCATION] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_UNIT_ID] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_UNIT_NAME] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_SNMP_CFG] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_ALM_THRESH_LIST]= { TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_CUR_SW_CFG] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_STREAM_ID] =	{ TLV_TYPE_TV, 0 },
++		[NM_ATT_IPACC_RAC] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_OBJ_VERSION] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_GPRS_PAGING_CFG] ={ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_NSEI] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_BVCI] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_NSVCI] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_NS_CFG] =		{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_BSSGP_CFG] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_NS_LINK_CFG] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_RLC_CFG] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_SUPP_FEATURES] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_CODING_SCHEMES] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_RLC_CFG_2] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_RLC_CFG_3] =	{ TLV_TYPE_TL16V, 0 },
++		[NM_ATT_IPACC_FILE_DATA] =	{ TLV_TYPE_TL16V, 0 },
++	},
++};
++
++#endif /* _NM_H */
+-- 
+1.7.0.1
+
diff --git a/wireshark/lucent-hnb.patch b/wireshark/lucent-hnb.patch
new file mode 100644
index 0000000..4c85195
--- /dev/null
+++ b/wireshark/lucent-hnb.patch
@@ -0,0 +1,120 @@
+Index: wireshark/epan/dissectors/packet-lucent_hnb.c
+===================================================================
+--- /dev/null
++++ wireshark/epan/dissectors/packet-lucent_hnb.c
+@@ -0,0 +1,103 @@
++/* packet-lucent_hnb.c
++ * Routines for packet dissection of Alcatel/Lucent HomeNodeB
++ * Copyright 2009 by Harald Welte <laforge@gnumonks.org>
++ *
++ * This protocol decoder is based entirely on reverse engineering, i.e.
++ * on educated guesses.
++ *
++ * $Id: packet-lucent_hnb.c 29254 2009-07-31 19:19:25Z gerald $
++ *
++ * Wireshark - Network traffic analyzer
++ * By Gerald Combs <gerald@wireshark.org>
++ * Copyright 1998 Gerald Combs
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * 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 General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++ */
++
++#ifdef HAVE_CONFIG_H
++# include "config.h"
++#endif
++
++#define LHNB_SCTP_PPI_MM	1
++#define LHNB_SCTP_PPI_GMM	6
++
++#define LHNB_SCTP_PORT		6005
++
++#include <glib.h>
++
++#include <epan/packet.h>
++
++/* Initialize the protocol and registered fields */
++static int proto_lhnb = -1;
++
++static int hf_lhnb_length = -1;
++
++/* Initialize the subtree pointers */
++static gint ett_lhnb = -1;
++
++static dissector_handle_t ranap_handle;
++
++/* Code to actually dissect the packets */
++static void
++dissect_lhnb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
++{
++
++	int offset = 0;
++	u_int16_t len;
++	tvbuff_t *next_tvb;
++
++	col_set_str(pinfo->cinfo, COL_PROTOCOL, "LHNB");
++	col_clear(pinfo->cinfo, COL_INFO);
++
++	proto_tree_add_item(tree, hf_lhnb_length, tvb, offset+2, 2, FALSE);
++	len = tvb_get_ntohs(tvb, offset+2);
++	next_tvb = tvb_new_subset(tvb, offset+2+6, len-4, -1);
++
++	call_dissector(ranap_handle, next_tvb, pinfo, tree);
++}
++
++void proto_register_lucent_hnb(void)
++{
++	static hf_register_info hf[] = {
++		{&hf_lhnb_length,
++		 {"Length", "lhnb.len",
++		  FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL}
++		 },
++	};
++
++	static gint *ett[] = {
++		&ett_lhnb,
++	};
++
++	proto_lhnb =
++	    proto_register_protocol("Alcatel/Lucent HomeNodeB",
++				    "Lucent HNB", "lhnb");
++
++	proto_register_field_array(proto_lhnb, hf, array_length(hf));
++	proto_register_subtree_array(ett, array_length(ett));
++}
++
++void proto_reg_handoff_lucent_hnb(void)
++{
++	dissector_handle_t lhnb_handle;
++
++	ranap_handle = find_dissector("ranap");
++
++	lhnb_handle = create_dissector_handle(dissect_lhnb, proto_lhnb);
++
++	dissector_add("sctp.ppi", LHNB_SCTP_PPI_MM, lhnb_handle);
++	dissector_add("sctp.ppi", LHNB_SCTP_PPI_GMM, lhnb_handle);
++	dissector_add("sctp.port", LHNB_SCTP_PORT, lhnb_handle);
++}
+Index: wireshark/epan/dissectors/Makefile.common
+===================================================================
+--- wireshark.orig/epan/dissectors/Makefile.common
++++ wireshark/epan/dissectors/Makefile.common
+@@ -605,6 +605,7 @@
+ 	packet-loop.c		\
+ 	packet-lpd.c		\
+ 	packet-lsc.c		\
++	packet-lucent_hnb.c	\
+ 	packet-lwapp.c		\
+ 	packet-lwres.c		\
+ 	packet-m2pa.c		\
diff --git a/wireshark/rsl-ipaccess.patch b/wireshark/rsl-ipaccess.patch
new file mode 100644
index 0000000..29220b8
--- /dev/null
+++ b/wireshark/rsl-ipaccess.patch
@@ -0,0 +1,664 @@
+From 8f35d623641dbba90e6186604c11e892bf515ecc Mon Sep 17 00:00:00 2001
+From: Holger Hans Peter Freyther <zecke@selfish.org>
+Date: Mon, 19 Apr 2010 13:32:58 +0800
+Subject: [PATCH 2/2] RSL patch
+
+---
+ epan/dissectors/packet-rsl.c |  522 +++++++++++++++++++++++++++++++++++++++++-
+ 1 files changed, 515 insertions(+), 7 deletions(-)
+
+diff --git a/epan/dissectors/packet-rsl.c b/epan/dissectors/packet-rsl.c
+index b10a671..a455cf3 100644
+--- a/epan/dissectors/packet-rsl.c
++++ b/epan/dissectors/packet-rsl.c
+@@ -2,6 +2,7 @@
+  * Routines for Radio Signalling Link (RSL) dissection.
+  *
+  * Copyright 2007, Anders Broman <anders.broman@ericsson.com>
++ * Copyright 2009, Harald Welte <laforge@gnumonks.org>
+  *
+  * $Id$
+  *
+@@ -42,6 +43,8 @@
+ #include <epan/lapd_sapi.h>
+ 
+ #include "packet-gsm_a_common.h"
++#include "packet-rtp.h"
++#include "packet-rtcp.h"
+ 
+ /* Initialize the protocol and registered fields */
+ static int proto_rsl		= -1;
+@@ -115,6 +118,24 @@ static int hf_rsl_emlpp_prio		= -1;
+ static int hf_rsl_rtd				= -1;
+ static int hf_rsl_delay_ind			= -1;
+ static int hf_rsl_tfo				= -1;
++static int hf_rsl_speech_mode_s			= -1;
++static int hf_rsl_speech_mode_m			= -1;
++static int hf_rsl_conn_stat			= -1;
++static int hf_rsl_conn_id			= -1;
++static int hf_rsl_rtp_payload			= -1;
++static int hf_rsl_rtp_csd_fmt_d			= -1;
++static int hf_rsl_rtp_csd_fmt_ir		= -1;
++static int hf_rsl_local_port			= -1;
++static int hf_rsl_remote_port			= -1;
++static int hf_rsl_local_ip			= -1;
++static int hf_rsl_remote_ip			= -1;
++static int hf_rsl_cstat_tx_pkts			= -1;
++static int hf_rsl_cstat_tx_octs			= -1;
++static int hf_rsl_cstat_rx_pkts			= -1;
++static int hf_rsl_cstat_rx_octs			= -1;
++static int hf_rsl_cstat_lost_pkts		= -1;
++static int hf_rsl_cstat_ia_jitter		= -1;
++static int hf_rsl_cstat_avg_tx_dly		= -1;
+ 
+ /* Initialize the subtree pointers */
+ static int ett_rsl = -1;
+@@ -172,6 +193,15 @@ static int ett_ie_cause = -1;
+ static int ett_ie_meas_res_no = -1;
+ static int ett_ie_message_id = -1;
+ static int ett_ie_sys_info_type = -1;
++static int ett_ie_speech_mode = -1;
++static int ett_ie_conn_stat = -1;
++static int ett_ie_conn_id = -1;
++static int ett_ie_remote_ip = -1;
++static int ett_ie_remote_port = -1;
++static int ett_ie_local_port = -1;
++static int ett_ie_local_ip = -1;
++static int ett_ie_rtp_payload = -1;
++
+ 
+ proto_tree *top_tree;
+ dissector_handle_t gsm_a_ccch_handle;
+@@ -207,8 +237,11 @@ static const value_string rsl_msg_disc_vals[] = {
+ 	{  0x06,		"Common Channel Management messages" },
+ 	{  0x08,		"TRX Management messages" },
+ 	{  0x16,		"Location Services messages" },
++	{  0x3f,		"ip.access Vendor Specific messages" },
+ 	{ 0,			NULL }
+ };
++#define RSL_MSGDISC_IPACCESS	0x3f
++
+ /*
+  * 9.2 MESSAGE TYPE
+  */
+@@ -275,6 +308,49 @@ static const value_string rsl_msg_disc_vals[] = {
+ 	/* 	0 1 - - - - - - Location Services messages: */
+ #define RSL_MSG_LOC_INF					65	/* 8.7.1 */
+ 
++/* Vendor-Specific messages of ip.access nanoBTS. There is no public documentation
++ * about those extensions, all information in this dissector is based on lawful
++ * protocol reverse enginering by Harald Welte <laforge@gnumonks.org> */
++#define RSL_MSG_TYPE_IPAC_MEAS_PP_DEF	0x60
++#define RSL_MSG_TYPE_IPAC_HO_CAND_INQ	0x61
++#define RSL_MSG_TYPE_IPAC_HO_CAND_RESP	0x62
++
++#define RSL_MSG_TYPE_IPAC_PDCH_ACT	0x48
++#define RSL_MSG_TYPE_IPAC_PDCH_ACT_ACK	0x49
++#define RSL_MSG_TYPE_IPAC_PDCH_ACT_NACK	0x4a
++#define RSL_MSG_TYPE_IPAC_PDCH_DEACT	0x4b
++#define RSL_MSG_TYPE_IPAC_PDCH_DEACT_ACK 0x4c
++#define RSL_MSG_TYPE_IPAC_PDCH_DEACT_NACK 0x4d
++
++#define RSL_MSG_TYPE_IPAC_CRCX		0x70
++#define RSL_MSG_TYPE_IPAC_CRCX_ACK	0x71
++#define RSL_MSG_TYPE_IPAC_CRCX_NACK	0x72
++#define RSL_MSG_TYPE_IPAC_MDCX		0x73
++#define RSL_MSG_TYPE_IPAC_MDCX_ACK	0x74
++#define RSL_MSG_TYPE_IPAC_MDCX_NACK	0x75
++#define RSL_MSG_TYPE_IPAC_DLCX_IND	0x76
++#define RSL_MSG_TYPE_IPAC_DLCX		0x77
++#define RSL_MSG_TYPE_IPAC_DLCX_ACK	0x78
++#define RSL_MSG_TYPE_IPAC_DLCX_NACK	0x79
++
++#define RSL_IE_IPAC_SRTP_CONFIG		0xe0
++#define RSL_IE_IPAC_PROXY_UDP		0xe1
++#define RSL_IE_IPAC_BSCMPL_TOUT		0xe2
++#define RSL_IE_IPAC_REMOTE_IP		0xf0
++#define RSL_IE_IPAC_REMOTE_PORT		0xf1
++#define RSL_IE_IPAC_RTP_PAYLOAD		0xf2
++#define RSL_IE_IPAC_LOCAL_PORT		0xf3
++#define RSL_IE_IPAC_SPEECH_MODE		0xf4
++#define RSL_IE_IPAC_LOCAL_IP		0xf5
++#define RSL_IE_IPAC_CONN_STAT		0xf6
++#define RSL_IE_IPAC_HO_C_PARMS		0xf7
++#define RSL_IE_IPAC_CONN_ID		0xf8
++#define RSL_IE_IPAC_RTP_CSD_FMT		0xf9
++#define RSL_IE_IPAC_RTP_JIT_BUF		0xfa
++#define RSL_IE_IPAC_RTP_COMPR		0xfb
++#define RSL_IE_IPAC_RTP_PAYLOAD2	0xfc
++#define RSL_IE_IPAC_RTP_MPLEX		0xfd
++#define RSL_IE_IPAC_RTP_MPLEX_ID	0xfe
+ 
+ static const value_string rsl_msg_type_vals[] = {
+ 	  /* 	0 0 0 0 - - - - Radio Link Layer Management messages: */
+@@ -337,6 +413,26 @@ static const value_string rsl_msg_type_vals[] = {
+ 	{  0x3f,	"TFO MODification REQuest" },					/* 8.4.31 */
+ 	/* 	0 1 - - - - - - Location Services messages: */
+ 	{  0x41,	"Location Information" },						/* 8.7.1 */
++	/* ip.access */
++	{  0x48,	"ip.access PDCH ACTIVATION" },
++	{  0x49,	"ip.access PDCH ACTIVATION ACK" },
++	{  0x4a,	"ip.access PDCH ACTIVATION NACK" },
++	{  0x4b,	"ip.access PDCH DEACTIVATION" },
++	{  0x4c,	"ip.access PDCH DEACTIVATION ACK" },
++	{  0x4d,	"ip.access PDCH DEACTIVATION NACK" },
++	{  0x60,	"ip.access MEASurement PREPROCessing DeFauLT" },
++	{  0x61,	"ip.access HANDOover CANDidate ENQuiry" },
++	{  0x62,	"ip.access HANDOover CANDidate RESPonse" },
++	{  0x70,	"ip.access CRCX" },
++	{  0x71,	"ip.access CRCX ACK" },
++	{  0x72,	"ip.access CRCX NACK" },
++	{  0x73,	"ip.access MDCX" },
++	{  0x74,	"ip.access MDCX ACK" },
++	{  0x75,	"ip.access MDCX NACK" },
++	{  0x76,	"ip.access DLCX INDication" },
++	{  0x77,	"ip.access DLCX" },
++	{  0x78,	"ip.access DLCX ACK" },
++	{  0x79,	"ip.access DLCX NACK" },
+ 	{ 0,		NULL }
+ };
+ 
+@@ -370,10 +466,10 @@ static const value_string rsl_msg_type_vals[] = {
+ #define RSL_IE_MESSAGE_ID		28
+ 
+ #define RSL_IE_SYS_INFO_TYPE	30
+-
+-
+-
+-
++#define RSL_IE_MS_POWER_PARAM		31
++#define RSL_IE_BS_POWER_PARAM		32
++#define RSL_IE_PREPROC_PARAM		33
++#define RSL_IE_PREPROC_MEAS		34
+ #define RSL_IE_FULL_IMM_ASS_INF			35
+ #define RSL_IE_SMSCB_INF				36
+ #define RSL_IE_FULL_MS_TIMING_OFFSET	37
+@@ -476,6 +572,24 @@ static const value_string rsl_ie_type_vals[] = {
+ 			Not used
+ 
+ 	*/
++	{ 0xe0,		"SRTP Configuration" },
++	{ 0xe1,		"BSC Proxy UDP Port" },
++	{ 0xe2,		"BSC Multiplex Timeout" },
++	{ 0xf0,		"Remote IP Address" },
++	{ 0xf1,		"Remote RTP Port" },
++	{ 0xf2,		"RTP Payload Type" },
++	{ 0xf3,		"Local RTP Port" },
++	{ 0xf4,		"Speech Mode" },
++	{ 0xf5,		"Local IP Address" },
++	{ 0xf6,		"Connection Statistics" },
++	{ 0xf7,		"Handover C Parameters" },
++	{ 0xf8,		"Connection Identifier" },
++	{ 0xf9,		"RTP CSD Format" },
++	{ 0xfa,		"RTP Jitter Buffer" },
++	{ 0xfb,		"RTP Compression" },
++	{ 0xfc,		"RTP Payload Type 2" },
++	{ 0xfd,		"RTP Multiplex" },
++	{ 0xfe,		"RTP Multiplex Identifier" },
+ 	{ 0,			NULL }
+ };
+ 
+@@ -512,6 +626,96 @@ static const value_string rsl_ch_no_Cbits_vals[] = {
+ 	{ 0,			NULL }
+ };
+ 
++/* From openbsc/include/openbsc/tlv.h */
++enum tlv_type {
++	TLV_TYPE_FIXED,
++	TLV_TYPE_T,
++	TLV_TYPE_TV,
++	TLV_TYPE_TLV,
++	TLV_TYPE_TL16V,
++};
++
++struct tlv_def {
++	enum tlv_type type;
++	u_int8_t fixed_len;
++};
++
++struct tlv_definition {
++	struct tlv_def def[0xff];
++};
++
++static const struct tlv_definition rsl_att_tlvdef = {
++	.def = {
++		[RSL_IE_CH_NO]			= { TLV_TYPE_TV, 0 },
++		[RSL_IE_LINK_ID]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_ACT_TYPE]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_BS_POW]			= { TLV_TYPE_TV, 0 },
++		[RSL_IE_CH_ID]			= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_CH_MODE]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_ENC_INF]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_FRAME_NO]		= { TLV_TYPE_FIXED, 2 },
++		[RSL_IE_HO_REF]			= { TLV_TYPE_TV, 0 },
++		[RSL_IE_L1_INF]			= { TLV_TYPE_FIXED, 2 },
++		[RSL_IE_L3_INF]			= { TLV_TYPE_TL16V, 0 },
++		[RSL_IE_MS_ID]			= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_MS_POW]			= { TLV_TYPE_TV, 0 },
++		[RSL_IE_PAGING_GRP]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_PAGING_LOAD]		= { TLV_TYPE_FIXED, 2 },
++		[RSL_IE_PHY_CTX]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_ACCESS_DELAY]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_RACH_LOAD]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_REQ_REF]		= { TLV_TYPE_FIXED, 3 },
++		[RSL_IE_REL_MODE]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_RESOURCE_INF]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_RLM_CAUSE]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_STARTING_TIME]		= { TLV_TYPE_FIXED, 2 },
++		[RSL_IE_TIMING_ADV]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_UPLINK_MEAS]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_CAUSE]			= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_MEAS_RES_NO]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_MESSAGE_ID]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_SYS_INFO_TYPE]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_MS_POWER_PARAM]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_BS_POWER_PARAM]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_PREPROC_PARAM]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_PREPROC_MEAS]		= { TLV_TYPE_TLV, 0 },
++		//[RSL_IE_IMM_ASS_INFO]		= { TLV_TYPE_TLV, 0 },
++		//[RSL_IE_SMSCB_INFO]		= { TLV_TYPE_FIXED, 23 },
++		//[RSL_IE_MS_TIMING_OFFSET]	= { TLV_TYPE_TV, 0 },
++		[RSL_IE_ERR_MSG]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_FULL_BCCH_INF]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_CH_NEEDED]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_CB_CMD_TYPE]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_SMSCB_MESS]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_FULL_IMM_ASS_INF]	= { TLV_TYPE_TLV, 0 },
++		//[RSL_IE_SACCH_INFO]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_CBCH_LOAD_INF]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_SMSCB_CH_IND]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_GRP_CALL_REF]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_CH_DESC]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_NCH_DRX_INF]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_CMD_IND]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_EMLPP_PRIO]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_UIC]			= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_MAIN_CH_REF]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_MULTIRATE_CONF]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_MULTIRATE_CNTRL]	= { TLV_TYPE_TV, 0 },
++		[RSL_IE_SUP_CODEC_TYPES]	= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_CODEC_CONF]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_RTD]			= { TLV_TYPE_TV, 0 },
++		[RSL_IE_TFO_STATUS]		= { TLV_TYPE_TV, 0 },
++		[RSL_IE_LLP_APDU]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_IPAC_REMOTE_IP]		= { TLV_TYPE_FIXED, 4 },
++		[RSL_IE_IPAC_REMOTE_PORT]	= { TLV_TYPE_FIXED, 2 },
++		[RSL_IE_IPAC_LOCAL_IP]		= { TLV_TYPE_FIXED, 4 },
++		[RSL_IE_IPAC_CONN_STAT]		= { TLV_TYPE_TLV, 0 },
++		[RSL_IE_IPAC_LOCAL_PORT]	= { TLV_TYPE_FIXED, 2 },
++		[RSL_IE_IPAC_SPEECH_MODE]	= { TLV_TYPE_TV, 0 },
++		[RSL_IE_IPAC_CONN_ID]		= { TLV_TYPE_FIXED, 2 },
++		[RSL_IE_IPAC_RTP_PAYLOAD2]	= { TLV_TYPE_TV, 0 },
++	},
++};
++
+ /* 9.3.1 Channel number			9.3.1	M TV 2 */
+ static int
+ dissect_rsl_ie_ch_no(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, gboolean is_mandatory)
+@@ -2042,7 +2246,6 @@ dissect_rsl_ie_err_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int
+ 	proto_item_set_len(ti, length+2);
+ 
+ 	proto_tree_add_item(ie_tree, hf_rsl_ie_length, tvb, offset, 1, FALSE);
+-	offset++;
+ 
+ 	/* Received Message */
+ 	offset = dissct_rsl_msg(tvb, pinfo, ie_tree, offset);
+@@ -2907,12 +3110,184 @@ dissect_rsl_ie_tfo_transp_cont(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree
+ }
+ 
+ static int
++dissct_rsl_ipaccess_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,				    int offset)
++{
++	guint8 msg_type;
++	guint32 local_addr = 0;
++	guint16 local_port = 0;
++	address src_addr;
++
++	msg_type = tvb_get_guint8(tvb, offset)&0x7f;
++	offset++;
++
++#if 0
++	switch (msg_type) {
++	case RSL_MSG_TYPE_IPAC_CRCX:
++	case RSL_MSG_TYPE_IPAC_CRCX_ACK:
++	case RSL_MSG_TYPE_IPAC_CRCX_NACK:
++	case RSL_MSG_TYPE_IPAC_MDCX:
++	case RSL_MSG_TYPE_IPAC_MDCX_ACK:
++	case RSL_MSG_TYPE_IPAC_MDCX_NACK:
++	case RSL_MSG_TYPE_IPAC_DLCX_IND:
++	case RSL_MSG_TYPE_IPAC_DLCX:
++	case RSL_MSG_TYPE_IPAC_DLCX_ACK:
++	case RSL_MSG_TYPE_IPAC_DLCX_NACK:
++	case RSL_MSG_TYPE_IPAC_PDCH_ACT:
++	case RSL_MSG_TYPE_IPAC_PDCH_ACT_ACK:
++	case RSL_MSG_TYPE_IPAC_PDCH_ACT_NACK:
++	case RSL_MSG_TYPE_IPAC_PDCH_DEACT:
++	case RSL_MSG_TYPE_IPAC_PDCH_DEACT_ACK:
++	case RSL_MSG_TYPE_IPAC_PDCH_DEACT_NACK:
++		/* Channel number			9.3.1	M TV 2		*/
++		offset = dissect_rsl_ie_ch_no(tvb, pinfo, tree, offset, TRUE);
++		break;
++	}
++#endif
++	/* parse TLV attributes */
++	while (tvb_reported_length_remaining(tvb, offset) != 0) {
++		guint8 tag;
++		unsigned int len, hlen, len_len;
++		const struct tlv_def *tdef;
++		proto_item *ti;
++		proto_tree *ie_tree;
++
++		tag = tvb_get_guint8(tvb, offset);
++		tdef = &rsl_att_tlvdef.def[tag];
++
++		switch (tdef->type) {
++		case TLV_TYPE_FIXED:
++			hlen = 1;
++			len_len = 0;
++			len = tdef->fixed_len;
++			break;
++		case TLV_TYPE_T:
++			hlen = 1;
++			len_len = 0;
++			len = 0;
++			break;
++		case TLV_TYPE_TV:
++			hlen = 1;
++			len_len = 0;
++			len = 1;
++			break;
++		case TLV_TYPE_TLV:
++			hlen = 2;
++			len_len = 1;
++			len = tvb_get_guint8(tvb, offset+1);
++			break;
++		case TLV_TYPE_TL16V:
++			hlen = 3;
++			len_len = 2;
++			len = tvb_get_guint8(tvb, offset+1) << 8 |
++					tvb_get_guint8(tvb, offset+2);
++			break;
++		default:
++			hlen = len_len = len = 0;
++			DISSECTOR_ASSERT_NOT_REACHED();
++			break;
++		}
++
++		ti = proto_tree_add_item(tree, hf_rsl_ie_id, tvb, offset, 1, FALSE);
++		ie_tree = proto_item_add_subtree(ti, ett_ie_local_port);
++		offset += hlen;
++
++		switch (tag) {
++		case RSL_IE_CH_NO:
++			dissect_rsl_ie_ch_no(tvb, pinfo, ie_tree, offset, FALSE);
++			break;
++		case RSL_IE_FRAME_NO:
++			dissect_rsl_ie_frame_no(tvb, pinfo, ie_tree, offset, FALSE);
++			break;
++		case RSL_IE_MS_POW:
++			dissect_rsl_ie_ms_pow(tvb, pinfo, ie_tree, offset, FALSE);
++			break;
++		case RSL_IE_IPAC_REMOTE_IP:
++			proto_tree_add_item(ie_tree, hf_rsl_remote_ip, tvb,
++					    offset, len, FALSE);
++			break;
++		case RSL_IE_IPAC_REMOTE_PORT:
++			proto_tree_add_item(ie_tree, hf_rsl_remote_port, tvb,
++					    offset, len, FALSE);
++			break;
++		case RSL_IE_IPAC_LOCAL_IP:
++			proto_tree_add_item(ie_tree, hf_rsl_local_ip, tvb,
++					    offset, len, FALSE);
++			local_addr = tvb_get_ipv4(tvb, offset);
++			break;
++		case RSL_IE_IPAC_LOCAL_PORT:
++			proto_tree_add_item(ie_tree, hf_rsl_local_port, tvb,
++					    offset, len, FALSE);
++			local_port = tvb_get_ntohs(tvb, offset);
++			break;
++		case RSL_IE_IPAC_SPEECH_MODE:
++			proto_tree_add_item(ie_tree, hf_rsl_speech_mode_s, tvb,
++					    offset, len, FALSE);
++			proto_tree_add_item(ie_tree, hf_rsl_speech_mode_m, tvb,
++					    offset, len, FALSE);
++			break;
++		case RSL_IE_IPAC_RTP_PAYLOAD:
++		case RSL_IE_IPAC_RTP_PAYLOAD2:
++			proto_tree_add_item(ie_tree, hf_rsl_rtp_payload, tvb,
++					    offset, len, FALSE);
++			break;
++		case RSL_IE_IPAC_RTP_CSD_FMT:
++			proto_tree_add_item(ie_tree, hf_rsl_rtp_csd_fmt_d, tvb,
++					    offset, len, FALSE);
++			proto_tree_add_item(ie_tree, hf_rsl_rtp_csd_fmt_ir, tvb,
++					    offset, len, FALSE);
++			break;
++		case RSL_IE_IPAC_CONN_ID:
++			proto_tree_add_item(ie_tree, hf_rsl_conn_id, tvb,
++					    offset, len, FALSE);
++			break;
++		case RSL_IE_IPAC_CONN_STAT:
++			proto_tree_add_item(ie_tree, hf_rsl_cstat_tx_pkts, tvb,
++					    offset, 4, FALSE);
++			proto_tree_add_item(ie_tree, hf_rsl_cstat_tx_octs, tvb,
++					    offset+4, 4, FALSE);
++			proto_tree_add_item(ie_tree, hf_rsl_cstat_rx_pkts, tvb,
++					    offset+8, 4, FALSE);
++			proto_tree_add_item(ie_tree, hf_rsl_cstat_rx_octs, tvb,
++					    offset+12, 4, FALSE);
++			proto_tree_add_item(ie_tree, hf_rsl_cstat_lost_pkts, tvb,
++					    offset+16, 4, FALSE);
++			proto_tree_add_item(ie_tree, hf_rsl_cstat_ia_jitter, tvb,
++					    offset+20, 4, FALSE);
++			proto_tree_add_item(ie_tree, hf_rsl_cstat_avg_tx_dly, tvb,
++					    offset+24, 4, FALSE);
++			break;
++		}
++		offset += len;
++	}
++
++	switch (msg_type) {
++	case RSL_MSG_TYPE_IPAC_CRCX_ACK:
++		/* Notify the RTP and RTCP dissectors about a new RTP stream */
++		src_addr.type = AT_IPv4;
++		src_addr.len = 4;
++		src_addr.data = (guint8 *)&local_addr;
++		rtp_add_address(pinfo, &src_addr, local_port, 0,
++				"GSM A-bis/IP", pinfo->fd->num, 0, NULL);
++		rtcp_add_address(pinfo, &src_addr, local_port+1, 0,
++				 "GSM A-bis/IP", pinfo->fd->num);
++		break;
++	}
++	return offset;
++}
++
++static int
+ dissct_rsl_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
+ {
+-	guint8	msg_type;
++	guint8	msg_disc, msg_type;
+ 
++	msg_disc = tvb_get_guint8(tvb, offset++) >> 1;
+ 	msg_type = tvb_get_guint8(tvb,offset)&0x7f;
+ 	proto_tree_add_item(tree, hf_rsl_msg_type, tvb, offset, 1, FALSE);
++
++	if (msg_disc == RSL_MSGDISC_IPACCESS) {
++		offset = dissct_rsl_ipaccess_msg(tvb, pinfo, tree, offset);
++		return offset;
++	}
+ 	offset++;
+ 
+ 	switch (msg_type){
+@@ -3480,6 +3855,18 @@ dissct_rsl_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
+ 		/* LLP APDU 9.3.58 M LV 2-N */
+ 		offset = dissect_rsl_ie_llp_apdu(tvb, pinfo, tree, offset, TRUE);
+ 		break;
++	/* the following messages are ip.access specific but sent without
++	 * ip.access memssage discriminator */
++	case RSL_MSG_TYPE_IPAC_MEAS_PP_DEF:
++	case RSL_MSG_TYPE_IPAC_HO_CAND_INQ:
++	case RSL_MSG_TYPE_IPAC_HO_CAND_RESP:
++	case RSL_MSG_TYPE_IPAC_PDCH_ACT:
++	case RSL_MSG_TYPE_IPAC_PDCH_ACT_ACK:
++	case RSL_MSG_TYPE_IPAC_PDCH_ACT_NACK:
++	case RSL_MSG_TYPE_IPAC_PDCH_DEACT:
++	case RSL_MSG_TYPE_IPAC_PDCH_DEACT_ACK:
++	case RSL_MSG_TYPE_IPAC_PDCH_DEACT_NACK:
++		offset = dissct_rsl_ipaccess_msg(tvb, pinfo, tree, offset-1);
+ 	default:
+ 		break;
+ 	}
+@@ -3487,6 +3874,40 @@ dissct_rsl_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
+ 	return offset;
+ 
+ }
++
++static const value_string rsl_ipacc_spm_s_vals[] = {
++	{ 0,	"GSM FR codec (GSM type 1, FS)" },
++	{ 1,	"GSM EFR codec (GSM type 2, FS)" },
++	{ 2, 	"GSM AMR/FR codec (GSM type 3, FS)" },
++	{ 3,	"GSM HR codec (GSM type 1, HS)" },
++	{ 5,	"GSM AMR/HR codec (GSM type 3, HS)" },
++	{ 0xf,	"As specified by RTP Payload Type IE" },
++	{ 0,	NULL }
++};
++
++static const value_string rsl_ipacc_spm_m_vals[] = {
++	{ 0,	"Send and Receive" },
++	{ 1,	"Receive Only" },
++	{ 2,	"Send Only" },
++	{ 0, 	NULL }
++};
++
++static const value_string rsl_ipacc_rtp_csd_fmt_d_vals[] = {
++	{ 0,	"External TRAU format" },
++	{ 1,	"Non-TRAU Packed format" },
++	{ 2,	"TRAU within the BTS" },
++	{ 3,	"IWF-Free BTS-BTS Data" },
++	{ 0, 	NULL }
++};
++
++static const value_string rsl_ipacc_rtp_csd_fmt_ir_vals[] = {
++	{ 0,	"8kb/s" },
++	{ 1,	"16kb/s" },
++	{ 2,	"32kb/s" },
++	{ 3,	"64kb/s" },
++	{ 0,	NULL }
++};
++
+ static void
+ dissect_rsl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+ {
+@@ -3514,7 +3935,6 @@ dissect_rsl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+ 		/* 9.1 Message discriminator */
+ 		proto_tree_add_item(rsl_tree, hf_rsl_msg_dsc, tvb, offset, 1, FALSE);
+ 		proto_tree_add_item(rsl_tree, hf_rsl_T_bit, tvb, offset, 1, FALSE);
+-		offset++;
+ 
+ 		offset = dissct_rsl_msg(tvb, pinfo, rsl_tree, offset);
+ 
+@@ -3884,6 +4304,86 @@ void proto_register_rsl(void)
+ 			FT_UINT8, BASE_DEC, VALS(rsl_emlpp_prio_vals), 0x03,
+ 			NULL, HFILL }
+ 		},
++		{ &hf_rsl_speech_mode_s,
++			{ "ip.access Speech Mode S", "rsl.ipacc.speech_mode_s",
++			  FT_UINT8, BASE_HEX, VALS(rsl_ipacc_spm_s_vals),
++			  0xf, NULL, HFILL }
++		},
++		{ &hf_rsl_speech_mode_m,
++			{ "ip.access Speech Mode M", "rsl.ipacc.speech_mode_m",
++			  FT_UINT8, BASE_HEX, VALS(rsl_ipacc_spm_m_vals),
++			  0xf0, NULL, HFILL }
++		},
++		{ &hf_rsl_conn_stat,
++			{ "ip.access Connection Statistics","rsl.ipacc.conn_stat",
++			  FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }
++		},
++		{ &hf_rsl_conn_id,
++			{ "ip.access Connection ID",	"rsl.ipacc.conn_id",
++			  FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }
++		},
++		{ &hf_rsl_rtp_payload,
++			{ "ip.access RTP Payload Type",	"rsl.ipacc.rtp_payload",
++			  FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }
++		},
++		{ &hf_rsl_rtp_csd_fmt_d,
++			{ "ip.access RTP CSD Format D", "rsl.ipacc.rtp_csd_fmt_d",
++			  FT_UINT8, BASE_HEX, VALS(rsl_ipacc_rtp_csd_fmt_d_vals),
++			  0x0f, NULL, HFILL },
++		},
++		{ &hf_rsl_rtp_csd_fmt_ir,
++			{ "ip.access RTP CSD Format IR", "rsl.ipacc.rtp_csd_fmt_ir",
++			  FT_UINT8, BASE_HEX, VALS(rsl_ipacc_rtp_csd_fmt_ir_vals),
++			  0xf0, NULL, HFILL },
++		},
++		{ &hf_rsl_local_port,
++			{ "ip.access Local RTP Port",	"rsl.ipacc.local_port",
++			  FT_UINT16, BASE_DEC, NULL, 0x0,
++			  "ip.access Local RTP Port", HFILL },
++		},
++		{ &hf_rsl_remote_port,
++			{ "ip.access Remote RTP Port",	"rsl.ipacc.remote_port",
++			  FT_UINT16, BASE_DEC, NULL, 0x0,
++			  "ip.access Remote RTP Port", HFILL },
++		},
++		{ &hf_rsl_local_ip,
++			{ "ip.access Local IP Address",	"rsl.ipacc.local_ip",
++			  FT_IPv4, BASE_NONE, NULL, 0x0,
++			  "ip.access Local IP Address", HFILL },
++		},
++		{ &hf_rsl_remote_ip,
++			{ "ip.access Remote IP Address", "rsl.ipacc.remote_ip",
++			  FT_IPv4, BASE_NONE, NULL, 0x0,
++			  "ip.access Remote IP Address", HFILL },
++		},
++		{ &hf_rsl_cstat_tx_pkts,
++			{ "Packets Sent", "rsl.ipacc.cstat.tx_pkts",
++			  FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_rsl_cstat_tx_octs,
++			{ "Octets Sent", "rsl.ipacc.cstat.tx_octets",
++			  FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_rsl_cstat_rx_pkts,
++			{ "Packets Received", "rsl.ipacc.cstat.rx_pkts",
++			  FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_rsl_cstat_rx_octs,
++			{ "Octets Received", "rsl.ipacc.cstat.rx_octets",
++			  FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_rsl_cstat_lost_pkts,
++			{ "Packets Lost", "rsl.ipacc.cstat.lost_pkts",
++			  FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_rsl_cstat_ia_jitter,
++			{ "Inter-arrival Jitter", "rsl.ipacc.cstat.ia_jitter",
++			  FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
++		{ &hf_rsl_cstat_avg_tx_dly,
++			{ "Average Tx Delay", "rsl.ipacc.cstat.avg_tx_delay",
++			  FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
++		},
+ 	};
+ 	static gint *ett[] = {
+ 		&ett_rsl,
+@@ -3941,6 +4441,14 @@ void proto_register_rsl(void)
+ 		&ett_ie_meas_res_no,
+ 		&ett_ie_message_id,
+ 		&ett_ie_sys_info_type,
++		&ett_ie_speech_mode,
++		&ett_ie_conn_stat,
++		&ett_ie_conn_id,
++		&ett_ie_remote_ip,
++		&ett_ie_remote_port,
++		&ett_ie_local_port,
++		&ett_ie_local_ip,
++		&ett_ie_rtp_payload,
+ 	};
+ 
+ 	/* Register the protocol name and description */
+-- 
+1.7.0.1
+
diff --git a/wireshark/rsl-system_info.patch b/wireshark/rsl-system_info.patch
new file mode 100644
index 0000000..2945c65
--- /dev/null
+++ b/wireshark/rsl-system_info.patch
@@ -0,0 +1,13 @@
+Index: wireshark/epan/dissectors/packet-rsl.c
+===================================================================
+--- wireshark.orig/epan/dissectors/packet-rsl.c
++++ wireshark/epan/dissectors/packet-rsl.c
+@@ -2291,7 +2291,7 @@
+ 
+ 	proto_tree_add_text(ie_tree, tvb,offset,length,"Layer 3 message");
+ 	next_tvb = tvb_new_subset(tvb, offset, length, length);
+-	/* call_dissector(gsm_a_dtap_handle, next_tvb, pinfo, top_tree);*/
++	call_dissector(gsm_a_ccch_handle, next_tvb, pinfo, top_tree);
+ 
+ 	offset = offset + length;
+