[PATCH] libosmo-sccp[master]: Add new SCCP implementation

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Harald Welte gerrit-no-reply at lists.osmocom.org
Mon Apr 10 09:51:59 UTC 2017


Hello Jenkins Builder,

I'd like you to reexamine a change.  Please visit

    https://gerrit.osmocom.org/2215

to look at the new patch set (#7).

Add new SCCP implementation

This is an implementation of SCCP as specified in ITO-T Q.71x,
particularly the SCRC (routing), SCLC (Connectionless) and SCOC
(Connection Oriented) portions.  the elaborate state machines of
SCOC are implemented using osmo_fsm, with one state machine for each
connection.

Interfaces to the top (user application) are the SCCP-USER-SAP and on
the bottom (network) side the MTP-USER-SAP as provided by osmo_ss7.

Contrary to a straight-forward implementation, the code internally
always uses a SUA representation of all messages (in struct xua_msg).
This enables us to have one common implementation of all related state
machines and use them for both SUA and SCCP.  If used with real SCCP
wire format, all messages are translated from SCCP to SUA on ingress and
translated from SUA to SCCP on egress.  As SUA is a super-set of SCCP,
this can be done "lossless".

Change-Id: I916e895d9a4914b05483fe12ab5251f206d10dee
---
M include/osmocom/sigtran/sccp_sap.h
M src/Makefile.am
M src/osmo_ss7.c
M src/sccp_internal.h
A src/sccp_sclc.c
A src/sccp_scoc.c
A src/sccp_scrc.c
A src/sccp_user.c
8 files changed, 2,938 insertions(+), 2 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/libosmo-sccp refs/changes/15/2215/7

diff --git a/include/osmocom/sigtran/sccp_sap.h b/include/osmocom/sigtran/sccp_sap.h
index 0cc1531..c1464f0 100644
--- a/include/osmocom/sigtran/sccp_sap.h
+++ b/include/osmocom/sigtran/sccp_sap.h
@@ -222,3 +222,23 @@
 #define msgb_scu_prim(msg) ((struct osmo_scu_prim *)(msg)->l1h)
 
 char *osmo_scu_prim_name(struct osmo_prim_hdr *oph);
+
+struct osmo_ss7_instance;
+struct osmo_sccp_instance;
+struct osmo_sccp_user;
+
+struct osmo_sccp_instance *
+osmo_sccp_instance_create(struct osmo_ss7_instance *ss7, void *priv);
+void osmo_sccp_instance_destroy(struct osmo_sccp_instance *inst);
+
+void osmo_sccp_user_unbind(struct osmo_sccp_user *scu);
+
+struct osmo_sccp_user *
+osmo_sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name,
+		       osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc);
+
+struct osmo_sccp_user *
+osmo_sccp_user_bind(struct osmo_sccp_instance *inst, const char *name,
+		    osmo_prim_cb prim_cb, uint16_t ssn);
+
+int osmo_sccp_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph);
diff --git a/src/Makefile.am b/src/Makefile.am
index 4455127..a4cfeeb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -27,7 +27,8 @@
 LIBVERSION=0:0:0
 
 libosmo_sigtran_la_SOURCES = sccp_sap.c sua.c m3ua.c xua_msg.c sccp_helpers.c \
-			     sccp2sua.c \
+			     sccp2sua.c sccp_scrc.c sccp_sclc.c sccp_scoc.c \
+			     sccp_user.c \
 			     osmo_ss7.c osmo_ss7_hmrt.c xua_asp_fsm.c xua_as_fsm.c
 libosmo_sigtran_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -export-symbols-regex '^osmo_'
 libosmo_sigtran_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMONETIF_LIBS) $(LIBSCTP_LIBS)
diff --git a/src/osmo_ss7.c b/src/osmo_ss7.c
index 74c54bb..6d0b446 100644
--- a/src/osmo_ss7.c
+++ b/src/osmo_ss7.c
@@ -41,6 +41,7 @@
 
 #include <osmocom/netif/stream.h>
 
+#include "sccp_internal.h"
 #include "xua_internal.h"
 #include "xua_asp_fsm.h"
 #include "xua_as_fsm.h"
@@ -1483,6 +1484,7 @@
 {
 	if (ss7_initialized)
 		return 1;
+	osmo_fsm_register(&sccp_scoc_fsm);
 	osmo_fsm_register(&xua_as_fsm);
 	osmo_fsm_register(&xua_asp_fsm);
 	ss7_initialized = true;
diff --git a/src/sccp_internal.h b/src/sccp_internal.h
index 7287a82..c35ef4b 100644
--- a/src/sccp_internal.h
+++ b/src/sccp_internal.h
@@ -1,5 +1,89 @@
 #pragma once
 
-#include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/prim.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/osmo_ss7.h>
+
+/* an instance of the SCCP stack */
+struct osmo_sccp_instance {
+	/* entry in global list of ss7 instances */
+	struct llist_head list;
+	/* list of 'struct sccp_connection' in this instance */
+	struct llist_head connections;
+	/* list of SCCP users in this instance */
+	struct llist_head users;
+	/* routing context to be used in all outbound messages */
+	uint32_t route_ctx;
+	/* next local reference to allocate */
+	uint32_t next_id;
+	struct osmo_ss7_instance *ss7;
+	void *priv;
+
+	struct osmo_ss7_user ss7_user;
+};
+
+struct osmo_sccp_user {
+	/*! \brief entry in list of sccp users of \ref osmo_sccp_instance */
+	struct llist_head list;
+	/*! \brief pointer back to SCCP instance */
+	struct osmo_sccp_instance *inst;
+	/*! \brief human-readable name of this user */
+	char *name;
+
+	/*! \brief SSN and/or point code to which we are bound */
+	uint16_t ssn;
+	uint32_t pc;
+	bool pc_valid;
+
+	/* set if we are a server */
+	struct llist_head links;
+
+	/* user call-back function in case of incoming primitives */
+	osmo_prim_cb prim_cb;
+	void *priv;
+
+	/* Application Server FSM Instance */
+	struct osmo_fsm_inst *as_fi;
+};
+
+extern int DSCCP;
+
+struct xua_msg;
+
+struct osmo_sccp_user *
+sccp_user_find(struct osmo_sccp_instance *inst, uint16_t ssn, uint32_t pc);
+
+/* Message from SCOC -> SCRC */
+int sccp_scrc_rx_scoc_conn_msg(struct osmo_sccp_instance *inst,
+				struct xua_msg *xua);
+
+/* Message from SCLC -> SCRC */
+int sccp_scrc_rx_sclc_msg(struct osmo_sccp_instance *inst, struct xua_msg *xua);
+
+/* Message from MTP (SUA) -> SCRC */
+int scrc_rx_mtp_xfer_ind_xua(struct osmo_sccp_instance *inst,
+			     struct xua_msg *xua);
+
+/* Message from SCRC -> SCOC */
+void sccp_scoc_rx_from_scrc(struct osmo_sccp_instance *inst,
+			    struct xua_msg *xua);
+void sccp_scoc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst,
+				 struct xua_msg *xua, uint32_t cause);
+
+void sccp_scoc_flush_connections(struct osmo_sccp_instance *inst);
+
+/* Message from SCRC -> SCLC */
+int sccp_sclc_rx_from_scrc(struct osmo_sccp_instance *inst,
+			   struct xua_msg *xua);
+void sccp_sclc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst,
+				 struct xua_msg *xua, uint32_t cause);
+
+int sccp_user_prim_up(struct osmo_sccp_user *scut, struct osmo_scu_prim *prim);
+
+/* SCU -> SCLC */
+int sccp_sclc_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph);
 
 struct msgb *sccp_msgb_alloc(const char *name);
+
+struct osmo_fsm sccp_scoc_fsm;
diff --git a/src/sccp_sclc.c b/src/sccp_sclc.c
new file mode 100644
index 0000000..dae2c36
--- /dev/null
+++ b/src/sccp_sclc.c
@@ -0,0 +1,337 @@
+/* SCCP Connectionless Control (SCLC) according to ITU-T Q.713/Q.714 */
+
+/* (C) 2015-2017 by Harald Welte <laforge at 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* This code is a bit of a hybrid between the ITU-T Q.71x specifications
+ * for SCCP (particularly its connection-oriented part), and the IETF
+ * RFC 3868 (SUA).  The idea here is to have one shared code base of the
+ * state machines for SCCP Connection Oriented, and use those both from
+ * SCCP and SUA.
+ *
+ * To do so, all SCCP messages are translated to SUA messages in the
+ * input side, and all generated SUA messages are translated to SCCP on
+ * the output side.
+ *
+ * The Choice of going for SUA messages as the "native" format was based
+ * on their easier parseability, and the fact that there are features in
+ * SUA which classic SCCP cannot handle (like IP addresses in GT).
+ * However, all SCCP features can be expressed in SUA.
+ *
+ * The code only supports Class 2.  No support for Class 3 is intended,
+ * but patches are of course alwys welcome.
+ *
+ * Missing other features:
+ *  * Segmentation/Reassembly support
+ *  * T(guard) after (re)start
+ *  * freezing of local references
+ *  * parsing/encoding of IPv4/IPv6 addresses
+ *  * use of multiple Routing Contexts in SUA case
+ */
+
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/protocol/sua.h>
+#include <sccp/sccp_types.h>
+
+#include "xua_internal.h"
+#include "sccp_internal.h"
+
+/* generate a 'struct xua_msg' of requested type from primitive data */
+static struct xua_msg *xua_gen_msg_cl(uint32_t event,
+				      struct osmo_scu_prim *prim, int msg_type)
+{
+	struct xua_msg *xua = xua_msg_alloc();
+	struct osmo_scu_unitdata_param *udpar = &prim->u.unitdata;
+
+	if (!xua)
+		return NULL;
+
+	switch (msg_type) {
+	case SUA_CL_CLDT:
+		xua->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDT);
+		xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, 0); /* FIXME */
+		xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, 0);
+		xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &udpar->calling_addr);
+		xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &udpar->called_addr);
+		xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, udpar->in_sequence_control);
+		/* optional: importance, ... correlation id? */
+		if (!prim)
+			goto prim_needed;
+		xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+				 msgb_l2(prim->oph.msg));
+		break;
+	default:
+		LOGP(DLSCCP, LOGL_ERROR, "Unknown msg_type %u\n", msg_type);
+		xua_msg_free(xua);
+		return NULL;
+	}
+	return xua;
+
+prim_needed:
+	xua_msg_free(xua);
+	LOGP(DLSCCP, LOGL_ERROR, "%s must be called with valid 'prim' "
+	     "pointer for msg_type=%u\n", __func__, msg_type);
+	return NULL;
+}
+
+/* generate xua_msg, encode it and send it to SCRC */
+static int xua_gen_encode_and_send(struct osmo_sccp_user *scu, uint32_t event,
+				   struct osmo_scu_prim *prim, int msg_type)
+{
+	struct xua_msg *xua;
+
+	xua = xua_gen_msg_cl(event, prim, msg_type);
+	if (!xua)
+		return -1;
+
+	return sccp_scrc_rx_sclc_msg(scu->inst, xua);
+}
+
+/*! \brief Main entrance function for primitives from SCCP User
+ *  \param[in] scu SCCP User who is sending the primitive
+ *  \param[on] oph Osmocom primitive header of the primitive
+ *  \returns 0 on success; negtive in case of error */
+int sccp_sclc_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph)
+{
+	struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
+	struct msgb *msg = prim->oph.msg;
+	int rc = 0;
+
+	/* we get called from osmo_sccp_user_sap_down() which already
+	 * has debug-logged the primitive */
+
+	switch (OSMO_PRIM_HDR(&prim->oph)) {
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST):
+		/* Connectionless by-passes this altogether */
+		rc = xua_gen_encode_and_send(scu, -1, prim, SUA_CL_CLDT);
+		goto out;
+	default:
+		LOGP(DLSCCP, LOGL_ERROR, "Received unknown SCCP User "
+		     "primitive %s from user\n",
+		     osmo_scu_prim_name(&prim->oph));
+		rc = -1;
+		goto out;
+	}
+
+out:
+	/* the SAP is supposed to consume the primitive/msgb */
+	msgb_free(msg);
+
+	return rc;
+}
+
+/* Process an incoming CLDT message (from a remote peer) */
+static int sclc_rx_cldt(struct osmo_sccp_instance *inst, struct xua_msg *xua)
+{
+	struct osmo_scu_prim *prim;
+	struct osmo_scu_unitdata_param *param;
+	struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+	struct msgb *upmsg = sccp_msgb_alloc(__func__);
+	struct osmo_sccp_user *scu;
+	uint32_t protocol_class;
+
+	/* fill primitive */
+	prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+	param = &prim->u.unitdata;
+	osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+			OSMO_SCU_PRIM_N_UNITDATA,
+			PRIM_OP_INDICATION, upmsg);
+	sua_addr_parse(&param->called_addr, xua, SUA_IEI_DEST_ADDR);
+	sua_addr_parse(&param->calling_addr, xua, SUA_IEI_SRC_ADDR);
+	param->in_sequence_control = xua_msg_get_u32(xua, SUA_IEI_SEQ_CTRL);
+	protocol_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS);
+	param->return_option = protocol_class & 0x80;
+	param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+
+	scu = sccp_user_find(inst, param->called_addr.ssn,
+			     param->called_addr.pc);
+
+	if (!scu) {
+		/* FIXME: Send destination unreachable? */
+		LOGP(DLSUA, LOGL_NOTICE, "Received SUA message for unequipped SSN %u\n",
+			param->called_addr.ssn);
+		msgb_free(upmsg);
+		return 0;
+	}
+
+	/* copy data */
+	upmsg->l2h = msgb_put(upmsg, data_ie->len);
+	memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+
+	/* send to user SAP */
+	sccp_user_prim_up(scu, prim);
+
+	/* xua_msg is free'd by our caller */
+	return 0;
+}
+
+static int sclc_rx_cldr(struct osmo_sccp_instance *inst, struct xua_msg *xua)
+{
+	struct osmo_scu_prim *prim;
+	struct osmo_scu_notice_param *param;
+	struct xua_msg_part *data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+	struct msgb *upmsg = sccp_msgb_alloc(__func__);
+	struct osmo_sccp_user *scu;
+
+	/* fill primitive */
+	prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+	param = &prim->u.notice;
+	osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+			OSMO_SCU_PRIM_N_NOTICE,
+			PRIM_OP_INDICATION, upmsg);
+
+	sua_addr_parse(&param->called_addr, xua, SUA_IEI_DEST_ADDR);
+	sua_addr_parse(&param->calling_addr, xua, SUA_IEI_SRC_ADDR);
+	param->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+	param->cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE);
+
+	scu = sccp_user_find(inst, param->called_addr.ssn,
+			     param->called_addr.pc);
+	if (!scu) {
+		/* FIXME: Send destination unreachable? */
+		LOGP(DLSUA, LOGL_NOTICE, "Received CLDR for unequipped SSN %u\n",
+			param->called_addr.ssn);
+		msgb_free(upmsg);
+		return 0;
+	}
+
+	/* copy data */
+	upmsg->l2h = msgb_put(upmsg, data_ie->len);
+	memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+
+	/* send to user SAP */
+	sccp_user_prim_up(scu, prim);
+
+	/* xua_msg is free'd by our caller */
+	return 0;
+}
+
+/*! \brief SCRC -> SCLC (connectionless message)
+ *  \param[in] inst SCCP Instance in which we operate
+ *  \param[in] xua SUA connectionless message
+ *  \returns 0 on success; negative on error */
+int sccp_sclc_rx_from_scrc(struct osmo_sccp_instance *inst,
+			   struct xua_msg *xua)
+{
+	int rc = -1;
+
+	OSMO_ASSERT(xua->hdr.msg_class == SUA_MSGC_CL);
+
+	switch (xua->hdr.msg_type) {
+	case SUA_CL_CLDT:
+		rc = sclc_rx_cldt(inst, xua);
+		break;
+	case SUA_CL_CLDR:
+		rc = sclc_rx_cldr(inst, xua);
+		break;
+	default:
+		LOGP(DLSUA, LOGL_NOTICE, "Received unknown/unsupported "
+		     "message %s\n", xua_hdr_dump(xua, &xua_dialect_sua));
+		break;
+	}
+
+	return rc;
+}
+
+/* generate a return/refusal message (SUA CLDR == SCCP UDTS) based on
+ * the incoming message.  We need to flip all identities between sender
+ * and receiver */
+static struct xua_msg *gen_ret_msg(struct osmo_sccp_instance *inst,
+				   const struct xua_msg *xua_in,
+				   uint32_t ret_cause)
+{
+	struct xua_msg *xua_out = xua_msg_alloc();
+	struct osmo_sccp_addr called;
+
+	xua_out->hdr = XUA_HDR(SUA_MSGC_CL, SUA_CL_CLDR);
+	xua_msg_add_u32(xua_out, SUA_IEI_ROUTE_CTX, inst->route_ctx);
+	xua_msg_add_u32(xua_out, SUA_IEI_CAUSE,
+			SUA_CAUSE_T_RETURN | ret_cause);
+	/* Swap Calling and Called Party */
+	xua_msg_copy_part(xua_out, SUA_IEI_SRC_ADDR, xua_in, SUA_IEI_DEST_ADDR);
+	xua_msg_copy_part(xua_out, SUA_IEI_DEST_ADDR, xua_in, SUA_IEI_SRC_ADDR);
+	/* TODO: Optional: Hop Count */
+	/* Optional: Importance */
+	xua_msg_copy_part(xua_out, SUA_IEI_IMPORTANCE,
+			  xua_in, SUA_IEI_IMPORTANCE);
+	/* Optional: Message Priority */
+	xua_msg_copy_part(xua_out, SUA_IEI_MSG_PRIO, xua_in, SUA_IEI_MSG_PRIO);
+	/* Optional: Correlation ID */
+	xua_msg_copy_part(xua_out, SUA_IEI_CORR_ID, xua_in, SUA_IEI_CORR_ID);
+	/* Optional: Segmentation */
+	xua_msg_copy_part(xua_out, SUA_IEI_SEGMENTATION,
+			  xua_in, SUA_IEI_SEGMENTATION);
+	/* Optional: Data */
+	xua_msg_copy_part(xua_out, SUA_IEI_DATA, xua_in, SUA_IEI_DATA);
+
+	sua_addr_parse(&called, xua_out, SUA_IEI_DEST_ADDR);
+	/* Route on PC + SSN ? */
+	if (called.ri == OSMO_SCCP_RI_SSN_PC) {
+		/* if no PC, copy OPC into called addr */
+		if (!(called.presence & OSMO_SCCP_ADDR_T_PC)) {
+			struct osmo_sccp_addr calling;
+			sua_addr_parse(&calling, xua_out, SUA_IEI_SRC_ADDR);
+			called.presence |= OSMO_SCCP_ADDR_T_PC;
+			called.pc = calling.pc;
+			/* Re-encode / replace called address */
+			xua_msg_free_tag(xua_out, SUA_IEI_DEST_ADDR);
+			xua_msg_add_sccp_addr(xua_out, SUA_IEI_DEST_ADDR,
+					      &called);
+		}
+	}
+	return xua_out;
+}
+
+/*! \brief SCRC -> SCLC (Routing Failure
+ *  \param[in] inst SCCP Instance in which we operate
+ *  \param[in] xua_in Message that failed to be routed
+ *  \param[in] cause SCCP Return Cause */
+void sccp_sclc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst,
+				 struct xua_msg *xua_in, uint32_t cause)
+{
+	struct xua_msg *xua_out;
+
+	/* Figure C.12/Q.714 (Sheet 8) Node 9 */
+	switch (xua_in->hdr.msg_type) {
+	case SUA_CL_CLDT:
+		xua_out = gen_ret_msg(inst, xua_in, cause);
+		/* TODO: Message Return Option? */
+		if (!osmo_ss7_pc_is_local(inst->ss7, xua_in->mtp.opc)) {
+			/* non-local originator: send UDTS */
+			/* TODO: Assign SLS */
+			sccp_scrc_rx_sclc_msg(inst, xua_out);
+		} else {
+			/* local originator: send N-NOTICE to user */
+			/* TODO: N-NOTICE.ind SCLC -> SCU */
+			sclc_rx_cldr(inst, xua_out);
+			xua_msg_free(xua_out);
+		}
+		break;
+	case SUA_CL_CLDR:
+		/* do nothing */
+		break;
+	}
+}
diff --git a/src/sccp_scoc.c b/src/sccp_scoc.c
new file mode 100644
index 0000000..f881872
--- /dev/null
+++ b/src/sccp_scoc.c
@@ -0,0 +1,1642 @@
+/* SCCP Connection Oriented (SCOC) according to ITU-T Q.713/Q.714 */
+
+/* (C) 2015-2017 by Harald Welte <laforge at 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* This code is a bit of a hybrid between the ITU-T Q.71x specifications
+ * for SCCP (particularly its connection-oriented part), and the IETF
+ * RFC 3868 (SUA).  The idea here is to have one shared code base of the
+ * state machines for SCCP Connection Oriented, and use those both from
+ * SCCP and SUA.
+ *
+ * To do so, all SCCP messages are translated to SUA messages in the
+ * input side, and all generated SUA messages are translated to SCCP on
+ * the output side.
+ *
+ * The Choice of going for SUA messages as the "native" format was based
+ * on their easier parseability, and the fact that there are features in
+ * SUA which classic SCCP cannot handle (like IP addresses in GT).
+ * However, all SCCP features can be expressed in SUA.
+ *
+ * The code only supports Class 2.  No support for Class 3 is intended,
+ * but patches are of course alwys welcome.
+ *
+ * Missing other features:
+ *  * Segmentation/Reassembly support
+ *  * T(guard) after (re)start
+ *  * freezing of local references
+ *  * parsing/encoding of IPv4/IPv6 addresses
+ *  * use of multiple Routing Contexts in SUA case
+ */
+
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/protocol/sua.h>
+#include <sccp/sccp_types.h>
+
+#include "xua_internal.h"
+#include "sccp_internal.h"
+
+#define S(x)	(1 << (x))
+#define SCU_MSGB_SIZE	1024
+
+/* Appendix C.4 of Q.714 (all in milliseconds) */
+#define CONNECTION_TIMER	( 1 * 60 * 100)
+#define TX_INACT_TIMER		( 7 * 60 * 100)	/* RFC 3868 Ch. 8. */
+#define RX_INACT_TIMER		(15 * 60 * 100) /* RFC 3868 Ch. 8. */
+#define RELEASE_TIMER		(     10 * 100)
+#define RELEASE_REP_TIMER	(     10 * 100)
+#define INT_TIMER		( 1 * 60 * 100)
+#define GUARD_TIMER		(23 * 60 * 100)
+#define RESET_TIMER		(     10 * 100)
+
+/* convert from single value in milliseconds to comma-separated
+ * "seconds, microseconds" format we use in osmocom/core/timers.h */
+#define MSEC_TO_S_US(x)		(x/100), ((x%100)*10)
+
+/***********************************************************************
+ * SCCP connection table
+ ***********************************************************************/
+
+/* a logical connection within the SCCP instance */
+struct sccp_connection {
+	/* part of osmo_sccp_instance.list */
+	struct llist_head list;
+	/* which instance are we part of? */
+	struct osmo_sccp_instance *inst;
+	/* which user owns us? */
+	struct osmo_sccp_user *user;
+
+	/* remote point code */
+	uint32_t remote_pc;
+
+	/* local/remote addresses and identiies */
+	struct osmo_sccp_addr calling_addr;
+	struct osmo_sccp_addr called_addr;
+	uint32_t conn_id;
+	uint32_t remote_ref;
+
+	uint32_t importance;
+	uint32_t sccp_class;
+	uint32_t release_cause; /* WAIT_CONN_CONF */
+
+	/* Osmo FSM Instance of sccp_scoc_fsm */
+	struct osmo_fsm_inst *fi;
+
+	/* Connect timer */
+	struct osmo_timer_list t_conn;
+
+	/* inactivity timers */
+	struct osmo_timer_list t_ias;
+	struct osmo_timer_list t_iar;
+
+	/* release timers */
+	struct osmo_timer_list t_rel;
+	struct osmo_timer_list t_int;
+	struct osmo_timer_list t_rep_rel;
+};
+
+/***********************************************************************
+ * various helper functions 
+ ***********************************************************************/
+
+enum sccp_connection_state {
+	S_IDLE,
+	S_CONN_PEND_IN,
+	S_CONN_PEND_OUT,
+	S_ACTIVE,
+	S_DISCONN_PEND,
+	S_RESET_IN,
+	S_RESET_OUT,
+	S_BOTHWAY_RESET,
+	S_WAIT_CONN_CONF,
+};
+
+/* Events that this FSM can process */
+enum sccp_scoc_event {
+	/* Primitives from SCCP-User */
+	SCOC_E_SCU_N_CONN_REQ,
+	SCOC_E_SCU_N_CONN_RESP,
+	SCOC_E_SCU_N_DISC_REQ,
+	SCOC_E_SCU_N_DATA_REQ,
+	SCOC_E_SCU_N_EXP_DATA_REQ,
+
+	/* Events from RCOC (Routing for Connection Oriented) */
+	SCOC_E_RCOC_CONN_IND,
+	SCOC_E_RCOC_ROUT_FAIL_IND,
+	SCOC_E_RCOC_RLSD_IND,
+	SCOC_E_RCOC_REL_COMPL_IND,
+	SCOC_E_RCOC_CREF_IND,
+	SCOC_E_RCOC_CC_IND,
+	SCOC_E_RCOC_DT1_IND,
+	SCOC_E_RCOC_DT2_IND,
+	SCOC_E_RCOC_IT_IND,
+	SCOC_E_RCOC_OTHER_NPDU,
+	SCOC_E_RCOC_ERROR_IND,
+
+	/* Timer Events */
+	SCOC_E_T_IAR_EXP,
+	SCOC_E_T_IAS_EXP,
+
+	SCOC_E_CONN_TMR_EXP,
+
+	SCOC_E_T_REL_EXP,
+	SCOC_E_T_INT_EXP,
+	SCOC_E_T_REP_REL_EXP,
+};
+
+static const struct value_string scoc_event_names[] = {
+	/* Primitives from SCCP-User */
+	{ SCOC_E_SCU_N_CONN_REQ,	"N-CONNECT.req" },
+	{ SCOC_E_SCU_N_CONN_RESP,	"N-CONNECT.resp" },
+	{ SCOC_E_SCU_N_DISC_REQ,	"N-DISCONNECT.req" },
+	{ SCOC_E_SCU_N_DATA_REQ,	"N-DATA.req" },
+	{ SCOC_E_SCU_N_EXP_DATA_REQ,	"N-EXPEDITED_DATA.req" },
+
+	/* Events from RCOC (Routing for Connection Oriented) */
+	{ SCOC_E_RCOC_CONN_IND,		"RCOC-CONNECT.ind" },
+	{ SCOC_E_RCOC_ROUT_FAIL_IND,	"RCOC-ROUT_FAIL.ind" },
+	{ SCOC_E_RCOC_RLSD_IND,		"RCOC-RELEASED.ind" },
+	{ SCOC_E_RCOC_REL_COMPL_IND,	"RCOC-RELEASE_COMPLETE.ind" },
+	{ SCOC_E_RCOC_CREF_IND,		"RCOC-CONNECT_REFUSED.ind" },
+	{ SCOC_E_RCOC_CC_IND,		"RCOC-CONNECT_CONFIRM.ind" },
+	{ SCOC_E_RCOC_DT1_IND,		"RCOC-DT1.ind" },
+	{ SCOC_E_RCOC_DT2_IND,		"RCOC-DT2.ind" },
+	{ SCOC_E_RCOC_IT_IND,		"RCOC-IT.ind" },
+	{ SCOC_E_RCOC_OTHER_NPDU,	"RCOC-OTHER_NPDU.ind" },
+	{ SCOC_E_RCOC_ERROR_IND,	"RCOC-ERROR.ind" },
+
+	{ SCOC_E_T_IAR_EXP,		"T(iar)_expired" },
+	{ SCOC_E_T_IAS_EXP,		"T(ias)_expired" },
+	{ SCOC_E_CONN_TMR_EXP,		"T(conn)_expired" },
+	{ SCOC_E_T_REL_EXP,		"T(rel)_expired" },
+	{ SCOC_E_T_INT_EXP,		"T(int)_expired" },
+	{ SCOC_E_T_REP_REL_EXP,		"T(rep_rel)_expired" },
+
+	{ 0, NULL }
+};
+
+/* how to map a SCCP CO message to an event */
+static const struct xua_msg_event_map sua_scoc_event_map[] = {
+	{ SUA_MSGC_CO, SUA_CO_CORE, SCOC_E_RCOC_CONN_IND },
+	{ SUA_MSGC_CO, SUA_CO_RELRE, SCOC_E_RCOC_RLSD_IND },
+	{ SUA_MSGC_CO, SUA_CO_RELCO, SCOC_E_RCOC_REL_COMPL_IND },
+	{ SUA_MSGC_CO, SUA_CO_COREF, SCOC_E_RCOC_CREF_IND },
+	{ SUA_MSGC_CO, SUA_CO_COAK, SCOC_E_RCOC_CC_IND },
+	{ SUA_MSGC_CO, SUA_CO_CODT, SCOC_E_RCOC_DT1_IND },
+	{ SUA_MSGC_CO, SUA_CO_COIT, SCOC_E_RCOC_IT_IND },
+	{ SUA_MSGC_CO, SUA_CO_COERR, SCOC_E_RCOC_ERROR_IND },
+};
+
+
+/* map from SCU-primitives to SCOC FSM events */
+static const struct osmo_prim_event_map scu_scoc_event_map[] = {
+	{ SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST,
+		SCOC_E_SCU_N_CONN_REQ },
+	{ SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_RESPONSE,
+		SCOC_E_SCU_N_CONN_RESP },
+	{ SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST,
+		SCOC_E_SCU_N_DATA_REQ },
+	{ SCCP_SAP_USER, OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_REQUEST,
+		SCOC_E_SCU_N_DISC_REQ },
+	{ SCCP_SAP_USER, OSMO_SCU_PRIM_N_EXPEDITED_DATA, PRIM_OP_REQUEST,
+		SCOC_E_SCU_N_EXP_DATA_REQ },
+	{ 0, 0, 0, OSMO_NO_EVENT }
+};
+
+/***********************************************************************
+ * Timer Handling
+ ***********************************************************************/
+
+/* T(ias) has expired, send a COIT message to the peer */
+static void tx_inact_tmr_cb(void *data)
+{
+	struct sccp_connection *conn = data;
+	osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_IAS_EXP, NULL);
+}
+
+/* T(iar) has expired, notify the FSM about it */
+static void rx_inact_tmr_cb(void *data)
+{
+	struct sccp_connection *conn = data;
+	osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_IAR_EXP, NULL);
+}
+
+/* T(rel) has expired, notify the FSM about it */
+static void rel_tmr_cb(void *data)
+{
+	struct sccp_connection *conn = data;
+	osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_REL_EXP, NULL);
+}
+
+/* T(int) has expired, notify the FSM about it */
+static void int_tmr_cb(void *data)
+{
+	struct sccp_connection *conn = data;
+	osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_INT_EXP, NULL);
+}
+
+/* T(repeat_rel) has expired, notify the FSM about it */
+static void rep_rel_tmr_cb(void *data)
+{
+	struct sccp_connection *conn = data;
+	osmo_fsm_inst_dispatch(conn->fi, SCOC_E_T_REP_REL_EXP, NULL);
+}
+
+/* T(conn) has expired, notify the FSM about it */
+static void conn_tmr_cb(void *data)
+{
+	struct sccp_connection *conn = data;
+	osmo_fsm_inst_dispatch(conn->fi, SCOC_E_CONN_TMR_EXP, NULL);
+}
+
+/* Re-start the Tx inactivity timer */
+static void conn_restart_tx_inact_timer(struct sccp_connection *conn)
+{
+	osmo_timer_schedule(&conn->t_ias, MSEC_TO_S_US(TX_INACT_TIMER));
+}
+
+/* Re-start the Rx inactivity timer */
+static void conn_restart_rx_inact_timer(struct sccp_connection *conn)
+{
+	osmo_timer_schedule(&conn->t_iar, MSEC_TO_S_US(RX_INACT_TIMER));
+}
+
+/* Re-start both Rx and Tx inactivity timers */
+static void conn_start_inact_timers(struct sccp_connection *conn)
+{
+	conn_restart_tx_inact_timer(conn);
+	conn_restart_rx_inact_timer(conn);
+}
+
+/* Stop both Rx and Tx inactivity timers */
+static void conn_stop_inact_timers(struct sccp_connection *conn)
+{
+	osmo_timer_del(&conn->t_ias);
+	osmo_timer_del(&conn->t_iar);
+}
+
+/* Start release timer T(rel) */
+static void conn_start_rel_timer(struct sccp_connection *conn)
+{
+	osmo_timer_schedule(&conn->t_rel, MSEC_TO_S_US(RELEASE_TIMER));
+}
+
+/* Start repeat release timer T(rep_rel) */
+static void conn_start_rep_rel_timer(struct sccp_connection *conn)
+{
+	osmo_timer_schedule(&conn->t_rep_rel, MSEC_TO_S_US(RELEASE_REP_TIMER));
+}
+
+/* Start interval timer T(int) */
+static void conn_start_int_timer(struct sccp_connection *conn)
+{
+	osmo_timer_schedule(&conn->t_int, MSEC_TO_S_US(INT_TIMER));
+}
+
+/* Stop all release related timers: T(rel), T(int) and T(rep_rel) */
+static void conn_stop_release_timers(struct sccp_connection *conn)
+{
+	osmo_timer_del(&conn->t_rel);
+	osmo_timer_del(&conn->t_int);
+	osmo_timer_del(&conn->t_rep_rel);
+}
+
+/* Start connect timer T(conn) */
+static void conn_start_connect_timer(struct sccp_connection *conn)
+{
+	osmo_timer_schedule(&conn->t_conn, MSEC_TO_S_US(CONNECTION_TIMER));
+}
+
+/* Stop connect timer T(conn) */
+static void conn_stop_connect_timer(struct sccp_connection *conn)
+{
+	osmo_timer_del(&conn->t_conn);
+}
+
+
+/***********************************************************************
+ * SUA Instance and Connection handling
+ ***********************************************************************/
+
+static void conn_destroy(struct sccp_connection *conn);
+
+static struct sccp_connection *conn_find_by_id(struct osmo_sccp_instance *inst, uint32_t id)
+{
+	struct sccp_connection *conn;
+
+	llist_for_each_entry(conn, &inst->connections, list) {
+		if (conn->conn_id == id)
+			return conn;
+	}
+	return NULL;
+}
+
+#define INIT_TIMER(x, fn, priv)		do { (x)->cb = fn; (x)->data = priv; } while (0)
+
+/* allocate + init a SCCP Connection with given ID (local reference) */
+static struct sccp_connection *conn_create_id(struct osmo_sccp_instance *inst,
+					      uint32_t conn_id)
+{
+	struct sccp_connection *conn = talloc_zero(inst, struct sccp_connection);
+	char name[16];
+
+	conn->conn_id = conn_id;
+	conn->inst = inst;
+
+	llist_add_tail(&conn->list, &inst->connections);
+
+	INIT_TIMER(&conn->t_conn, conn_tmr_cb, conn);
+	INIT_TIMER(&conn->t_ias, tx_inact_tmr_cb, conn);
+	INIT_TIMER(&conn->t_iar, rx_inact_tmr_cb, conn);
+	INIT_TIMER(&conn->t_rel, rel_tmr_cb, conn);
+	INIT_TIMER(&conn->t_int, int_tmr_cb, conn);
+	INIT_TIMER(&conn->t_rep_rel, rep_rel_tmr_cb, conn);
+
+	/* this might change at runtime, as it is not a constant :/ */
+	sccp_scoc_fsm.log_subsys = DLSCCP;
+
+	/* we simply use the local reference as FSM instance name */
+	snprintf(name, sizeof(name), "%u", conn->conn_id);
+	conn->fi = osmo_fsm_inst_alloc(&sccp_scoc_fsm, conn, conn,
+					LOGL_DEBUG, name);
+	if (!conn->fi) {
+		llist_del(&conn->list);
+		talloc_free(conn);
+		return NULL;
+	}
+
+	return conn;
+}
+
+/* Search for next free connection ID (local reference) and allocate conn */
+static struct sccp_connection *conn_create(struct osmo_sccp_instance *inst)
+{
+	uint32_t conn_id;
+
+	do {
+		conn_id = inst->next_id++;
+	} while (conn_find_by_id(inst, conn_id));
+
+	return conn_create_id(inst, conn_id);
+}
+
+/* destroy a SCCP connection state, releasing all timers, terminating
+ * FSM and releasing associated memory */
+static void conn_destroy(struct sccp_connection *conn)
+{
+	conn_stop_connect_timer(conn);
+	conn_stop_inact_timers(conn);
+	conn_stop_release_timers(conn);
+	llist_del(&conn->list);
+
+	osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REQUEST, NULL);
+
+	talloc_free(conn);
+}
+
+/* allocate a message buffer for an SCCP User Primitive */
+static struct msgb *scu_msgb_alloc(void)
+{
+	return msgb_alloc(SCU_MSGB_SIZE, "SCCP User Primitive");
+}
+
+/* generate a RELRE (release request) xua_msg for given conn */
+static struct xua_msg *xua_gen_relre(struct sccp_connection *conn,
+				     uint32_t cause,
+				     struct osmo_scu_prim *prim)
+{
+	struct xua_msg *xua = xua_msg_alloc();
+
+	if (!xua)
+		return NULL;
+
+	xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE);
+	xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx);
+	xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+	xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+	xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | cause);
+	/* optional: importance */
+	if (prim && msgb_l2(prim->oph.msg))
+		xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+				 msgb_l2(prim->oph.msg));
+
+	return xua;
+}
+
+/* generate xua_msg, encode it and send it to SCRC */
+static int xua_gen_relre_and_send(struct sccp_connection *conn, uint32_t cause,
+				  struct osmo_scu_prim *prim)
+{
+	struct xua_msg *xua;
+
+	xua = xua_gen_relre(conn, cause, prim);
+	if (!xua)
+		return -1;
+
+	/* amend this with point code information; The SUA RELRE
+	 * includes neither called nor calling party address! */
+	xua->mtp.dpc = conn->remote_pc;
+	return sccp_scrc_rx_scoc_conn_msg(conn->inst, xua);
+}
+
+/* generate a 'struct xua_msg' of requested type from connection +
+ * primitive data */
+static struct xua_msg *xua_gen_msg_co(struct sccp_connection *conn, uint32_t event,
+				      struct osmo_scu_prim *prim, int msg_type)
+{
+	struct xua_msg *xua = xua_msg_alloc();
+
+	if (!xua)
+		return NULL;
+
+	switch (msg_type) {
+	case SUA_CO_CORE: /* Connect Request == SCCP CR */
+		xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CORE);
+		xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx);
+		xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class);
+		xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+		xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->called_addr);
+		xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, 0); /* TODO */
+		/* optional: sequence number (class 3 only) */
+		if (conn->calling_addr.presence)
+			xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->calling_addr);
+		/* optional: hop count; importance; priority; credit */
+		if (prim && msgb_l2(prim->oph.msg))
+			xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+					 msgb_l2(prim->oph.msg));
+		break;
+	case SUA_CO_COAK: /* Connect Acknowledge == SCCP CC */
+		xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COAK);
+		xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx);
+		xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class);
+		xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+		xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+		xua_msg_add_u32(xua, SUA_IEI_SEQ_CTRL, 0); /* TODO */
+		/* optional: sequence number (class 3 only) */
+		if (conn->called_addr.presence)
+			xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->called_addr);
+		/* optional: hop count; importance; priority */
+		/* FIXME: destination address will [only] be present in
+		 * case the CORE message conveys the source address
+		 * parameter */
+		if (conn->calling_addr.presence)
+			xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->calling_addr);
+		if (prim && msgb_l2(prim->oph.msg))
+			xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+					 msgb_l2(prim->oph.msg));
+		break;
+	case SUA_CO_RELRE: /* Release Request == SCCP REL */
+		if (!prim)
+			goto prim_needed;
+		xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE);
+		xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx);
+		xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+		xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+		xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_RELEASE | prim->u.disconnect.cause);
+		/* optional: importance */
+		if (msgb_l2(prim->oph.msg))
+			xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+					 msgb_l2(prim->oph.msg));
+		break;
+	case SUA_CO_RELCO: /* Release Confirm == SCCP RLSD */
+		xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO);
+		xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx);
+		xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+		xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+		/* optional: importance */
+		break;
+	case SUA_CO_CODT: /* Connection Oriented Data Transfer == SCCP DT1 */
+		if (!prim)
+			goto prim_needed;
+		xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_CODT);
+		xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx);
+		/* Sequence number only in expedited data */
+		xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+		/* optional: priority; correlation id */
+		xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+				 msgb_l2(prim->oph.msg));
+		break;
+	case SUA_CO_COIT: /* Connection Oriented Interval Timer == SCCP IT */
+		xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COIT);
+		xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx);
+		xua_msg_add_u32(xua, SUA_IEI_PROTO_CLASS, conn->sccp_class);
+		xua_msg_add_u32(xua, SUA_IEI_SRC_REF, conn->conn_id);
+		xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+		/* optional: sequence number; credit (both class 3 only) */
+		break;
+	case SUA_CO_COREF: /* Connect Refuse == SCCP CREF */
+		xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF);
+		xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, conn->inst->route_ctx);
+		xua_msg_add_u32(xua, SUA_IEI_DEST_REF, conn->remote_ref);
+		//xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | prim->u.disconnect.cause);
+		xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | SCCP_REFUSAL_UNEQUIPPED_USER);
+		/* optional: source addr */
+		if (conn->called_addr.presence)
+			xua_msg_add_sccp_addr(xua, SUA_IEI_SRC_ADDR, &conn->called_addr);
+		/* conditional: dest addr */
+		if (conn->calling_addr.presence)
+			xua_msg_add_sccp_addr(xua, SUA_IEI_DEST_ADDR, &conn->calling_addr);
+		/* optional: importance */
+		/* optional: data */
+		if (prim && msgb_l2(prim->oph.msg))
+			xua_msg_add_data(xua, SUA_IEI_DATA, msgb_l2len(prim->oph.msg),
+					 msgb_l2(prim->oph.msg));
+		break;
+	/* FIXME */
+	default:
+		LOGP(DLSCCP, LOGL_ERROR, "Don't know how to encode msg_type %u\n", msg_type);
+		xua_msg_free(xua);
+		return NULL;
+	}
+	return xua;
+
+prim_needed:
+	xua_msg_free(xua);
+	LOGP(DLSCCP, LOGL_ERROR, "%s must be called with valid 'prim' "
+	     "pointer for msg_type=%u\n", __func__, msg_type);
+	return NULL;
+}
+
+/* generate xua_msg, encode it and send it to SCRC */
+static int xua_gen_encode_and_send(struct sccp_connection *conn, uint32_t event,
+				   struct osmo_scu_prim *prim, int msg_type)
+{
+	struct xua_msg *xua;
+
+	xua = xua_gen_msg_co(conn, event, prim, msg_type);
+	if (!xua)
+		return -1;
+
+	/* amend this with point code information; Many CO msgs
+	 * includes neither called nor calling party address! */
+	xua->mtp.dpc = conn->remote_pc;
+	return sccp_scrc_rx_scoc_conn_msg(conn->inst, xua);
+}
+
+/* allocate a SCU primitive to be sent to the user */
+static struct osmo_scu_prim *scu_prim_alloc(unsigned int primitive, enum osmo_prim_operation operation)
+{
+	struct msgb *upmsg = scu_msgb_alloc();
+	struct osmo_scu_prim *prim;
+
+	prim = (struct osmo_scu_prim *) msgb_put(upmsg, sizeof(*prim));
+	osmo_prim_init(&prim->oph, SCCP_SAP_USER,
+			primitive, operation, upmsg);
+	return prim;
+}
+
+/* high-level function to generate a SCCP User primitive of requested
+ * type based on the connection and currently processed XUA message */
+static void scu_gen_encode_and_send(struct sccp_connection *conn, uint32_t event,
+				    struct xua_msg *xua, unsigned int primitive,
+				    enum osmo_prim_operation operation)
+{
+	struct osmo_scu_prim *scu_prim;
+	struct osmo_scu_disconn_param *udisp;
+	struct osmo_scu_connect_param *uconp;
+	struct osmo_scu_data_param *udatp;
+	struct xua_msg_part *data_ie;
+
+	scu_prim = scu_prim_alloc(primitive, operation);
+
+	switch (OSMO_PRIM_HDR(&scu_prim->oph)) {
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
+		udisp = &scu_prim->u.disconnect;
+		udisp->conn_id = conn->conn_id;
+		udisp->responding_addr = conn->called_addr;
+		udisp->originator = OSMO_SCCP_ORIG_UNDEFINED;
+		//udisp->in_sequence_control;
+		if (xua) {
+			udisp->cause = xua_msg_get_u32(xua, SUA_IEI_CAUSE);
+			if (xua_msg_find_tag(xua, SUA_IEI_SRC_ADDR))
+				sua_addr_parse(&udisp->responding_addr, xua, SUA_IEI_SRC_ADDR);
+			data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+			udisp->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+			if (data_ie) {
+				struct msgb *upmsg = scu_prim->oph.msg;
+				upmsg->l2h = msgb_put(upmsg, data_ie->len);
+				memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+			}
+		}
+		break;
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
+		uconp = &scu_prim->u.connect;
+		uconp->conn_id = conn->conn_id;
+		uconp->called_addr = conn->called_addr;
+		uconp->calling_addr = conn->calling_addr;
+		uconp->sccp_class = conn->sccp_class;
+		uconp->importance = conn->importance;
+		data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+		if (xua) {
+			if (data_ie) {
+				struct msgb *upmsg = scu_prim->oph.msg;
+				upmsg->l2h = msgb_put(upmsg, data_ie->len);
+				memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+			}
+		}
+		break;
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
+		uconp = &scu_prim->u.connect;
+		uconp->conn_id = conn->conn_id;
+		uconp->called_addr = conn->called_addr;
+		uconp->calling_addr = conn->calling_addr;
+		//scu_prim->u.connect.in_sequence_control
+		uconp->sccp_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) & 3;
+		uconp->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+		data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+		if (data_ie) {
+			struct msgb *upmsg = scu_prim->oph.msg;
+			upmsg->l2h = msgb_put(upmsg, data_ie->len);
+			memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+		}
+		break;
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
+		udatp = &scu_prim->u.data;
+		udatp->conn_id = conn->conn_id;
+		udatp->importance = conn->importance;
+		data_ie = xua_msg_find_tag(xua, SUA_IEI_DATA);
+		if (data_ie) {
+			struct msgb *upmsg = scu_prim->oph.msg;
+			upmsg->l2h = msgb_put(upmsg, data_ie->len);
+			memcpy(upmsg->l2h, data_ie->dat, data_ie->len);
+		}
+		break;
+	default:
+		LOGPFSML(conn->fi, LOGL_ERROR, "Unsupported primitive %u:%u\n",
+			 scu_prim->oph.primitive, scu_prim->oph.operation);
+		talloc_free(scu_prim->oph.msg);
+		return;
+	}
+
+	sccp_user_prim_up(conn->user, scu_prim);
+}
+
+
+/***********************************************************************
+ * Actual SCCP Connection Oriented Control (SCOC) Finite Stte Machine
+ ***********************************************************************/
+
+/* Figure C.2/Q.714 (sheet 1 of 7) and C.3/Q.714 (sheet 1 of 6) */
+static void scoc_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct sccp_connection *conn = fi->priv;
+	struct osmo_scu_prim *prim = NULL;
+	struct osmo_scu_connect_param *uconp;
+	struct xua_msg *xua = NULL;
+
+	switch (event) {
+	case SCOC_E_SCU_N_CONN_REQ:
+		prim = data;
+		uconp = &prim->u.connect;
+		/* copy relevant parameters from prim to conn */
+		conn->called_addr = uconp->called_addr;
+		conn->calling_addr = uconp->calling_addr;
+		conn->sccp_class = uconp->sccp_class;
+		/* generate + send CR PDU to SCRC */
+		xua_gen_encode_and_send(conn, event, prim, SUA_CO_CORE);
+		/* start connection timer */
+		conn_start_connect_timer(conn);
+		osmo_fsm_inst_state_chg(fi, S_CONN_PEND_OUT, 0, 0);
+		break;
+#if 0
+	case SCOC_E_SCU_N_TYPE1_REQ:
+		/* ?!? */
+		break;
+#endif
+	case SCOC_E_RCOC_RLSD_IND:
+		/* send release complete to SCRC */
+		xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO);
+		break;
+	case SCOC_E_RCOC_REL_COMPL_IND:
+		/* do nothing */
+		break;
+	case SCOC_E_RCOC_OTHER_NPDU:
+#if 0
+		if (src_ref) {
+			/* FIXME: send ERROR to SCRC */
+		}
+#endif
+		break;
+	/* destination node / incoming connection */
+	/* Figure C.3 / Q.714 (sheet 1 of 6) */
+	case SCOC_E_RCOC_CONN_IND:
+		xua = data;
+		/* copy relevant parameters from xua to conn */
+		sua_addr_parse(&conn->calling_addr, xua, SUA_IEI_SRC_ADDR);
+		sua_addr_parse(&conn->called_addr, xua, SUA_IEI_DEST_ADDR);
+		conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF);
+		conn->sccp_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) & 3;
+		conn->importance = xua_msg_get_u32(xua, SUA_IEI_IMPORTANCE);
+		/* 3.1.6.1 The originating node of the CR message
+		 * (identified by the OPC in the calling party address
+		 * or by default by the OPC in the MTP label, [and the
+		 * MTP-SAP instance]) is associated with the incoming
+		 * connection section. */
+		if (conn->calling_addr.presence & OSMO_SCCP_ADDR_T_PC)
+			conn->remote_pc = conn->calling_addr.pc;
+		else {
+			/* Hack to get the MTP label here ?!? */
+			conn->remote_pc = xua->mtp.opc;
+		}
+
+		osmo_fsm_inst_state_chg(fi, S_CONN_PEND_IN, 0, 0);
+		/* N-CONNECT.ind to User */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_CONNECT,
+					PRIM_OP_INDICATION);
+		break;
+	}
+}
+
+static void scoc_fsm_idle_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+	conn_destroy(fi->priv);
+}
+
+/* Figure C.3 / Q.714 (sheet 2 of 6) */
+static void scoc_fsm_conn_pend_in(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct sccp_connection *conn = fi->priv;
+	struct osmo_scu_prim *prim = NULL;
+
+	switch (event) {
+	case SCOC_E_SCU_N_CONN_RESP:
+		prim = data;
+		/* FIXME: assign local reference (only now?) */
+		/* FIXME: assign sls, protocol class and credit */
+		xua_gen_encode_and_send(conn, event, prim, SUA_CO_COAK);
+		/* start inactivity timers */
+		conn_start_inact_timers(conn);
+		osmo_fsm_inst_state_chg(fi, S_ACTIVE, 0, 0);
+		break;
+	case SCOC_E_SCU_N_DISC_REQ:
+		prim = data;
+		/* release resources: implicit */
+		xua_gen_encode_and_send(conn, event, prim, SUA_CO_COREF);
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	}
+}
+
+/* Figure C.2/Q.714 (sheet 2 of 7) */
+static void scoc_fsm_conn_pend_out(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct sccp_connection *conn = fi->priv;
+	struct osmo_scu_prim *prim = NULL;
+	struct xua_msg *xua = NULL;
+
+	switch (event) {
+	case SCOC_E_SCU_N_DISC_REQ:
+		prim = data;
+		conn->release_cause = prim->u.disconnect.cause;
+		osmo_fsm_inst_state_chg(fi, S_WAIT_CONN_CONF, 0, 0);
+		/* keep conn timer running(!) */
+		break;
+	case SCOC_E_CONN_TMR_EXP:
+		/* N-DISCONNECT.ind to user */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		/* below implicitly releases resources + local ref */
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_RCOC_ROUT_FAIL_IND:
+	case SCOC_E_RCOC_CREF_IND:
+		xua = data;
+		/* stop conn timer */
+		conn_stop_connect_timer(conn);
+		/* release local res + ref (implicit by going to idle) */
+		/* N-DISCONNECT.ind to user */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		/* below implicitly releases resources + local ref */
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_RCOC_RLSD_IND:
+		xua = data;
+		/* RLC to SCRC */
+		xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO);
+		/* stop conn timer */
+		conn_stop_connect_timer(conn);
+		/* release local res + ref (implicit) */
+		/* N-DISCONNECT.ind to user */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_RCOC_OTHER_NPDU:
+		xua = data;
+		conn_start_connect_timer(conn);
+		/* release local res + ref (implicit) */
+		/* N-DISCONNECT.ind to user */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_RCOC_CC_IND:
+		xua = data;
+		/* stop conn timer */
+		conn_stop_connect_timer(conn);
+		/* start inactivity timers */
+		conn_start_inact_timers(conn);
+		/* TODO: assign PCU and credit */
+		/* associate remote ref to conn */
+		conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF);
+		/* 3.1.4.2 The node sending the CC message (identified
+		 * by the parameter OPC contained in the
+		 * MTP-TRANSFER.indication primitive which conveyed the
+		 * CC message [plus the MTP-SAP instance]) is associated
+		 * with the connection section. */
+		conn->remote_pc = xua->mtp.opc;
+
+		osmo_fsm_inst_state_chg(fi, S_ACTIVE, 0, 0);
+		/* N-CONNECT.conf to user */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_CONNECT,
+					PRIM_OP_CONFIRM);
+		break;
+	}
+}
+
+/* Figure C.2/Q.714 (sheet 3 of 7) */
+static void scoc_fsm_wait_conn_conf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct sccp_connection *conn = fi->priv;
+	struct xua_msg *xua = NULL;
+
+	switch (event) {
+	case SCOC_E_RCOC_RLSD_IND:
+		xua = data;
+		/* release complete to SCRC */
+		xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO);
+		/* stop conn timer */
+		conn_stop_connect_timer(conn);
+		/* release local res + ref (implicit) */
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_RCOC_CC_IND:
+		xua = data;
+		/* stop conn timer */
+		conn_stop_connect_timer(conn);
+		/* associate rem ref to conn */
+		conn->remote_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF);
+		/* released to SCRC */
+		xua_gen_relre_and_send(conn, conn->release_cause, NULL);
+		/* start rel timer */
+		conn_start_rel_timer(conn);
+		osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0);
+		break;
+	case SCOC_E_RCOC_OTHER_NPDU:
+	case SCOC_E_RCOC_CREF_IND:
+	case SCOC_E_RCOC_ROUT_FAIL_IND:
+		xua = data;
+		/* stop conn timer */
+		conn_stop_connect_timer(conn);
+		/* release local res + ref */
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_CONN_TMR_EXP:
+		/* release local res + ref */
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	}
+}
+
+/* C.2/Q.714 (sheet 4+5 of 7) and C.3/Q714 (sheet 3+4 of 6) */
+static void scoc_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct xua_msg *xua = data;
+	struct sccp_connection *conn = fi->priv;
+	struct osmo_scu_prim *prim = NULL;
+
+	switch (event) {
+	/* TODO: internal disco */
+		/* send N-DISCONNECT.ind to user */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		/* fall-through */
+	case SCOC_E_SCU_N_DISC_REQ:
+		prim = data;
+		/* stop inact timers */
+		conn_stop_inact_timers(conn);
+		/* send RLSD to SCRC */
+		xua_gen_encode_and_send(conn, event, prim, SUA_CO_RELRE);
+		/* start rel timer */
+		conn_start_rel_timer(conn);
+		osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0);
+		break;
+	case SCOC_E_RCOC_CREF_IND:
+	case SCOC_E_RCOC_CC_IND:
+	case SCOC_E_RCOC_REL_COMPL_IND:
+		/* do nothing */
+		break;
+	case SCOC_E_RCOC_RLSD_IND:
+		/* send N-DISCONNECT.ind to user */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		/* release res + local ref (implicit) */
+		/* stop inact timers */
+		conn_stop_inact_timers(conn);
+		/* RLC to SCRC */
+		xua_gen_encode_and_send(conn, event, NULL, SUA_CO_RELCO);
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_RCOC_ERROR_IND:
+		xua = data;
+		/* FIXME: check for cause service_class_mismatch */
+		/* release res + local ref (implicit) */
+		/* send N-DISCONNECT.ind to user */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		/* stop inact timers */
+		conn_stop_inact_timers(conn);
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_T_IAR_EXP:
+		/* Send N-DISCONNECT.ind to local user */
+		scu_gen_encode_and_send(conn, event, NULL, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		/* Send RLSD to peer */
+		xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_EXPIRATION_INACTIVE, NULL);
+		conn_start_rel_timer(conn);
+		osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0);
+		break;
+	case SCOC_E_RCOC_ROUT_FAIL_IND:
+		/* send N-DISCONNECT.ind to user */
+		scu_gen_encode_and_send(conn, event, NULL, OSMO_SCU_PRIM_N_DISCONNECT,
+					PRIM_OP_INDICATION);
+		/* stop inact timers */
+		conn_stop_inact_timers(conn);
+		/* start release timer */
+		conn_start_rel_timer(conn);
+		osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0);
+		break;
+	/* Figure C.4/Q.714 */
+	case SCOC_E_SCU_N_DATA_REQ:
+	case SCOC_E_SCU_N_EXP_DATA_REQ:
+		prim = data;
+		xua_gen_encode_and_send(conn, event, prim, SUA_CO_CODT);
+		conn_restart_tx_inact_timer(conn);
+		break;
+	case SCOC_E_RCOC_DT1_IND:
+		/* restart receive inactivity timer */
+		conn_restart_rx_inact_timer(conn);
+		/* TODO: M-bit */
+		scu_gen_encode_and_send(conn, event, xua, OSMO_SCU_PRIM_N_DATA,
+					PRIM_OP_INDICATION);
+		break;
+	/* Figure C.4/Q.714 (sheet 4 of 4) */
+	case SCOC_E_RCOC_IT_IND:
+		xua = data;
+		/* check if remote reference is what we expect */
+		/* check class is what we expect */
+		if (xua_msg_get_u32(xua, SUA_IEI_SRC_REF) != conn->remote_ref ||
+		    xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS) != conn->sccp_class) {
+			/* Release connection */
+			/* send N-DISCONNECT.ind to user */
+			scu_gen_encode_and_send(conn, event, NULL,
+						OSMO_SCU_PRIM_N_DISCONNECT,
+						PRIM_OP_INDICATION);
+			/* Stop inactivity Timers */
+			conn_stop_inact_timers(conn);
+			/* Send RLSD to SCRC */
+			xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_INCONSISTENT_CONN_DATA, NULL);
+			/* Start release timer */
+			conn_start_rel_timer(conn);
+			osmo_fsm_inst_state_chg(fi, S_DISCONN_PEND, 0, 0);
+		}
+		conn_restart_rx_inact_timer(conn);
+		break;
+	case SCOC_E_T_IAS_EXP:
+		/* Send IT to peer */
+		xua_gen_encode_and_send(conn, event, NULL, SUA_CO_COIT);
+		conn_restart_tx_inact_timer(conn);
+		break;
+	}
+}
+
+/* C.2/Q.714 (sheet 6+7 of 7) and C.3/Q.714 (sheet 5+6 of 6) */
+static void scoc_fsm_disconn_pend(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct sccp_connection *conn = fi->priv;
+
+	switch (event) {
+	case SCOC_E_RCOC_REL_COMPL_IND:
+	case SCOC_E_RCOC_RLSD_IND:
+		/* release res + local ref (implicit) */
+		/* freeze local ref */
+		/* stop release + interval timers */
+		conn_stop_release_timers(conn);
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_RCOC_ROUT_FAIL_IND:
+	case SCOC_E_RCOC_OTHER_NPDU:
+		/* do nothing */
+		break;
+	case SCOC_E_T_REL_EXP: /* release timer exp */
+		/* send RLSD */
+		xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_UNQUALIFIED, NULL);
+		/* start interval timer */
+		conn_start_int_timer(conn);
+		/* start repeat release timer */
+		conn_start_rep_rel_timer(conn);
+		break;
+	case SCOC_E_T_INT_EXP: /* interval timer exp */
+		/* TODO: Inform maintenance */
+		/* stop release and interval timers */
+		conn_stop_release_timers(conn);
+		osmo_fsm_inst_state_chg(fi, S_IDLE, 0, 0);
+		break;
+	case SCOC_E_T_REP_REL_EXP: /* repeat release timer exp */
+		/* send RLSD */
+		xua_gen_relre_and_send(conn, SCCP_RELEASE_CAUSE_UNQUALIFIED, NULL);
+		/* re-start repeat release timer */
+		conn_start_rep_rel_timer(conn);
+		break;
+	}
+}
+
+static const struct osmo_fsm_state sccp_scoc_states[] = {
+	[S_IDLE] = {
+		.name = "IDLE",
+		.action = scoc_fsm_idle,
+		.onenter= scoc_fsm_idle_onenter,
+		.in_event_mask = S(SCOC_E_SCU_N_CONN_REQ) |
+				 //S(SCOC_E_SCU_N_TYPE1_REQ) |
+				 S(SCOC_E_RCOC_CONN_IND) |
+				 S(SCOC_E_RCOC_RLSD_IND) |
+				 S(SCOC_E_RCOC_REL_COMPL_IND) |
+				 S(SCOC_E_RCOC_OTHER_NPDU),
+		.out_state_mask = S(S_CONN_PEND_OUT) |
+				  S(S_CONN_PEND_IN),
+	},
+	[S_CONN_PEND_IN] = {
+		.name = "CONN_PEND_IN",
+		.action = scoc_fsm_conn_pend_in,
+		.in_event_mask = S(SCOC_E_SCU_N_CONN_RESP) |
+				 S(SCOC_E_SCU_N_DISC_REQ),
+		.out_state_mask = S(S_IDLE) |
+				  S(S_ACTIVE),
+	},
+	[S_CONN_PEND_OUT] = {
+		.name = "CONN_PEND_OUT",
+		.action = scoc_fsm_conn_pend_out,
+		.in_event_mask = S(SCOC_E_SCU_N_DISC_REQ) |
+				 S(SCOC_E_CONN_TMR_EXP) |
+				 S(SCOC_E_RCOC_ROUT_FAIL_IND) |
+				 S(SCOC_E_RCOC_RLSD_IND) |
+				 S(SCOC_E_RCOC_OTHER_NPDU) |
+				 S(SCOC_E_RCOC_CREF_IND) |
+				 S(SCOC_E_RCOC_CC_IND),
+		.out_state_mask = S(S_IDLE) |
+				  S(S_ACTIVE) |
+				  S(S_WAIT_CONN_CONF),
+	},
+	[S_ACTIVE] = {
+		.name = "ACTIVE",
+		.action = scoc_fsm_active,
+		.in_event_mask = S(SCOC_E_SCU_N_DISC_REQ) |
+				/* internal disconnect */
+				 S(SCOC_E_RCOC_CREF_IND) |
+				 S(SCOC_E_RCOC_REL_COMPL_IND) |
+				 S(SCOC_E_RCOC_RLSD_IND) |
+				 S(SCOC_E_RCOC_ERROR_IND) |
+				 S(SCOC_E_T_IAR_EXP) |
+				 S(SCOC_E_T_IAS_EXP) |
+				 S(SCOC_E_RCOC_ROUT_FAIL_IND) |
+				 S(SCOC_E_SCU_N_DATA_REQ) |
+				 S(SCOC_E_SCU_N_EXP_DATA_REQ) |
+				 S(SCOC_E_RCOC_DT1_IND) |
+				 S(SCOC_E_RCOC_IT_IND),
+		.out_state_mask = S(S_IDLE) |
+				  S(S_DISCONN_PEND),
+	},
+	[S_DISCONN_PEND] = {
+		.name = "DISCONN_PEND",
+		.action = scoc_fsm_disconn_pend,
+		.in_event_mask = S(SCOC_E_RCOC_REL_COMPL_IND) |
+				 S(SCOC_E_RCOC_RLSD_IND) |
+				 S(SCOC_E_RCOC_ROUT_FAIL_IND) |
+				 S(SCOC_E_RCOC_OTHER_NPDU) |
+				 S(SCOC_E_T_REL_EXP) |
+				 S(SCOC_E_T_INT_EXP) |
+				 S(SCOC_E_T_REP_REL_EXP),
+		.out_state_mask = S(S_IDLE),
+	},
+	[S_RESET_IN] = {
+		.name = "RESET_IN",
+	},
+	[S_RESET_OUT] = {
+		.name = "RESET_OUT",
+	},
+	[S_BOTHWAY_RESET] = {
+		.name = "BOTHWAY_RESET",
+	},
+	[S_WAIT_CONN_CONF] = {
+		.name = "WAIT_CONN_CONF",
+		.action = scoc_fsm_wait_conn_conf,
+		.in_event_mask = S(SCOC_E_RCOC_RLSD_IND) |
+				 S(SCOC_E_RCOC_CC_IND) |
+				 S(SCOC_E_RCOC_OTHER_NPDU) |
+				 S(SCOC_E_CONN_TMR_EXP) |
+				 S(SCOC_E_RCOC_CREF_IND) |
+				 S(SCOC_E_RCOC_ROUT_FAIL_IND),
+	},
+};
+
+struct osmo_fsm sccp_scoc_fsm = {
+	.name = "SCCP-SCOC",
+	.states = sccp_scoc_states,
+	.num_states = ARRAY_SIZE(sccp_scoc_states),
+	/* ".log_subsys = DLSCCP" doesn't work as DLSCCP is not a constant */
+	.event_names = scoc_event_names,
+};
+
+/* map from SCCP return cause to SCCP Refusal cause */
+static const uint8_t cause_map_cref[] = {
+	[SCCP_RETURN_CAUSE_SUBSYSTEM_CONGESTION] =
+				SCCP_REFUSAL_SUBSYTEM_CONGESTION,
+	[SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE] =
+				SCCP_REFUSAL_SUBSYSTEM_FAILURE,
+	[SCCP_RETURN_CAUSE_UNEQUIPPED_USER] =
+				SCCP_REFUSAL_UNEQUIPPED_USER,
+	[SCCP_RETURN_CAUSE_UNQUALIFIED] =
+				SCCP_REFUSAL_UNQUALIFIED,
+	[SCCP_RETURN_CAUSE_SCCP_FAILURE] =
+				SCCP_REFUSAL_SCCP_FAILURE,
+	[SCCP_RETURN_CAUSE_HOP_COUNTER_VIOLATION] =
+				SCCP_REFUSAL_HOP_COUNTER_VIOLATION,
+};
+
+static uint8_t get_cref_cause_for_ret(uint8_t ret_cause)
+{
+	if (ret_cause < ARRAY_SIZE(cause_map_cref))
+		return cause_map_cref[ret_cause];
+	else
+		return SCCP_REFUSAL_UNQUALIFIED;
+}
+
+/* Generate a COREF message purely based on an incoming SUA message,
+ * without the use of any local connection state */
+static struct xua_msg *gen_coref_without_conn(struct osmo_sccp_instance *inst,
+					      struct xua_msg *xua_in,
+					      uint32_t ref_cause)
+{
+	struct xua_msg *xua;
+
+	xua = xua_msg_alloc();
+	xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COREF);
+	xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, inst->route_ctx);
+
+	xua_msg_copy_part(xua, SUA_IEI_DEST_REF, xua_in, SUA_IEI_SRC_REF);
+	xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_REFUSAL | ref_cause);
+	/* optional: source addr */
+	xua_msg_copy_part(xua, SUA_IEI_SRC_ADDR, xua_in, SUA_IEI_DEST_ADDR);
+	/* conditional: dest addr */
+	xua_msg_copy_part(xua, SUA_IEI_DEST_ADDR, xua_in, SUA_IEI_SRC_ADDR);
+	/* optional: importance */
+	xua_msg_copy_part(xua, SUA_IEI_IMPORTANCE, xua_in, SUA_IEI_IMPORTANCE);
+	/* optional: data */
+	xua_msg_copy_part(xua, SUA_IEI_DATA, xua_in, SUA_IEI_DATA);
+
+	return xua;
+}
+
+/*! \brief SCOC: Receive SCRC Routing Failure
+ *  \param[in] inst SCCP Instance on which we operate
+ *  \param[in] xua SUA message that was failed to route
+ *  \param[in] return_cause Reason (cause) for routing failure */
+void sccp_scoc_rx_scrc_rout_fail(struct osmo_sccp_instance *inst,
+				struct xua_msg *xua, uint32_t return_cause)
+{
+	uint32_t conn_id;
+	struct sccp_connection *conn;
+
+	/* try to dispatch to connection FSM (if any) */
+	conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF);
+	conn = conn_find_by_id(inst, conn_id);
+	if (conn) {
+		osmo_fsm_inst_dispatch(conn->fi,
+					SCOC_E_RCOC_ROUT_FAIL_IND, xua);
+	} else {
+		/* generate + send CREF directly */
+		struct xua_msg *cref;
+		uint8_t cref_cause = get_cref_cause_for_ret(return_cause);
+		cref = gen_coref_without_conn(inst, xua, cref_cause);
+		sccp_scrc_rx_scoc_conn_msg(inst, cref);
+		xua_msg_free(cref);
+	}
+}
+
+/* Find a SCCP user for given SUA message (based on SUA_IEI_DEST_ADDR */
+static struct osmo_sccp_user *sccp_find_user(struct osmo_sccp_instance *inst,
+					     struct xua_msg *xua)
+{
+	int rc;
+	struct osmo_sccp_addr called_addr;
+
+	rc = sua_addr_parse(&called_addr, xua, SUA_IEI_DEST_ADDR);
+	if (rc < 0) {
+		LOGP(DLSCCP, LOGL_ERROR, "Cannot find SCCP User for XUA "
+			"Message %s without valid DEST_ADDR\n",
+			xua_hdr_dump(xua, &xua_dialect_sua));
+		return NULL;
+	}
+
+	if (!(called_addr.presence & OSMO_SCCP_ADDR_T_SSN)) {
+		LOGP(DLSCCP, LOGL_ERROR, "Cannot resolve SCCP User for "
+			"XUA Message %s without SSN in CalledAddr\n",
+			xua_hdr_dump(xua, &xua_dialect_sua));
+		return NULL;
+	}
+
+	return sccp_user_find(inst, called_addr.ssn, called_addr.pc);
+}
+
+/* Generate a COERR based in input arguments */
+static struct xua_msg *gen_coerr(uint32_t route_ctx, uint32_t dest_ref,
+				uint32_t err_cause)
+{
+	struct xua_msg *xua = xua_msg_alloc();
+
+	xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_COERR);
+	xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx);
+	xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref);
+	xua_msg_add_u32(xua, SUA_IEI_CAUSE, SUA_CAUSE_T_ERROR | err_cause);
+
+	return xua;
+}
+
+/* generate COERR from incoming XUA and send it */
+static void tx_coerr_from_xua(struct osmo_sccp_instance *inst,
+				struct xua_msg *in, uint32_t err_cause)
+{
+	struct xua_msg *xua;
+	uint32_t route_ctx, dest_ref;
+
+	route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX);
+	/* get *source* reference and use as destination ref */
+	dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF);
+
+	xua = gen_coerr(route_ctx, dest_ref, err_cause);
+	/* copy over the MTP parameters */
+	xua->mtp.dpc = in->mtp.opc;
+	xua->mtp.opc = in->mtp.dpc;
+	xua->mtp.sio = in->mtp.sio;
+
+	/* sent to SCRC for transmission */
+	sccp_scrc_rx_scoc_conn_msg(inst, xua);
+	xua_msg_free(xua);
+}
+
+/* Generate a RELCO based in input arguments */
+static struct xua_msg *gen_relco(uint32_t route_ctx, uint32_t dest_ref,
+				uint32_t src_ref)
+{
+	struct xua_msg *xua = xua_msg_alloc();
+
+	xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELCO);
+	xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx);
+	xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref);
+	xua_msg_add_u32(xua, SUA_IEI_SRC_REF, src_ref);
+
+	return xua;
+}
+
+/* generate RELCO from incoming XUA and send it */
+static void tx_relco_from_xua(struct osmo_sccp_instance *inst,
+				struct xua_msg *in)
+{
+	struct xua_msg *xua;
+	uint32_t route_ctx, dest_ref, src_ref;
+
+	route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX);
+	/* get *source* reference and use as destination ref */
+	dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF);
+	/* get *dest* reference and use as source ref */
+	src_ref = xua_msg_get_u32(in, SUA_IEI_DEST_REF);
+
+	xua = gen_relco(route_ctx, dest_ref, src_ref);
+	/* copy over the MTP parameters */
+	xua->mtp.dpc = in->mtp.opc;
+	xua->mtp.opc = in->mtp.dpc;
+	xua->mtp.sio = in->mtp.sio;
+
+	/* send to SCRC for transmission */
+	sccp_scrc_rx_scoc_conn_msg(inst, xua);
+	xua_msg_free(xua);
+}
+
+/* Generate a RLSD based in input arguments */
+static struct xua_msg *gen_rlsd(uint32_t route_ctx, uint32_t dest_ref,
+				uint32_t src_ref)
+{
+	struct xua_msg *xua = xua_msg_alloc();
+
+	xua->hdr = XUA_HDR(SUA_MSGC_CO, SUA_CO_RELRE);
+	xua_msg_add_u32(xua, SUA_IEI_ROUTE_CTX, route_ctx);
+	xua_msg_add_u32(xua, SUA_IEI_DEST_REF, dest_ref);
+	xua_msg_add_u32(xua, SUA_IEI_SRC_REF, src_ref);
+
+	return xua;
+}
+
+/* Generate a RLSD to both the remote side and the local conn */
+static void tx_rlsd_from_xua_twoway(struct sccp_connection *conn,
+				    struct xua_msg *in)
+{
+	struct xua_msg *xua;
+	uint32_t route_ctx, dest_ref, src_ref;
+
+	route_ctx = xua_msg_get_u32(in, SUA_IEI_ROUTE_CTX);
+	/* get *source* reference and use as destination ref */
+	dest_ref = xua_msg_get_u32(in, SUA_IEI_SRC_REF);
+	/* get *source* reference and use as destination ref */
+	src_ref = xua_msg_get_u32(in, SUA_IEI_DEST_REF);
+
+	/* Generate RLSD towards remote peer */
+	xua = gen_rlsd(route_ctx, dest_ref, src_ref);
+	/* copy over the MTP parameters */
+	xua->mtp.dpc = in->mtp.opc;
+	xua->mtp.opc = in->mtp.dpc;
+	xua->mtp.sio = in->mtp.sio;
+	/* send to SCRC for transmission */
+	sccp_scrc_rx_scoc_conn_msg(conn->inst, xua);
+	xua_msg_free(xua);
+
+	/* Generate RLSD towards local peer */
+	xua = gen_rlsd(conn->inst->route_ctx, conn->conn_id, conn->remote_ref);
+	xua->mtp.dpc = in->mtp.dpc;
+	xua->mtp.opc = conn->remote_pc;
+	xua->mtp.sio = in->mtp.sio;
+	osmo_fsm_inst_dispatch(conn->fi, SCOC_E_RCOC_RLSD_IND, xua);
+	xua_msg_free(xua);
+}
+
+/* process received message for unasigned local reference */
+static void sccp_scoc_rx_unass_local_ref(struct osmo_sccp_instance *inst,
+					 struct xua_msg *xua)
+{
+	/* we have received a message with unassigned destination local
+	 * reference and thus apply the action indicated in Table
+	 * B.2/Q.714 */
+	switch (xua->hdr.msg_type) {
+	case SUA_CO_COAK: /* CC */
+	case SUA_CO_COIT: /* IT */
+	case SUA_CO_RESRE: /* RSR */
+	case SUA_CO_RESCO: /* RSC */
+		/* Send COERR */
+		tx_coerr_from_xua(inst, xua, SCCP_ERROR_LRN_MISMATCH_UNASSIGNED);
+		break;
+	case SUA_CO_COREF: /* CREF */
+	case SUA_CO_RELCO: /* RLC */
+	case SUA_CO_CODT: /* DT1 */
+	case SUA_CO_CODA: /* AK */
+	case SUA_CO_COERR: /* ERR */
+		/* DISCARD */
+		break;
+	case SUA_CO_RELRE: /* RLSD */
+		/* Send RLC */
+		tx_relco_from_xua(inst, xua);
+		break;
+	default:
+		LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n",
+			xua_hdr_dump(xua, &xua_dialect_sua));
+		break;
+	}
+}
+
+/* process received message for invalid source local reference */
+static void sccp_scoc_rx_inval_src_ref(struct sccp_connection *conn,
+					struct xua_msg *xua)
+{
+	/* we have received a message with invalid source local
+	 * reference and thus apply the action indicated in Table
+	 * B.2/Q.714 */
+	switch (xua->hdr.msg_type) {
+	case SUA_CO_RELRE: /* RLSD */
+	case SUA_CO_RESRE: /* RSR */
+	case SUA_CO_RESCO: /* RSC */
+		/* Send ERR */
+		tx_coerr_from_xua(conn->inst, xua, SCCP_ERROR_LRN_MISMATCH_INCONSISTENT);
+		break;
+	case SUA_CO_COIT: /* IT */
+		/* FIXME: RLSD to both sides */
+		tx_rlsd_from_xua_twoway(conn, xua);
+		break;
+	case SUA_CO_RELCO: /* RLC */
+		/* DISCARD */
+		break;
+	default:
+		LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n",
+			xua_hdr_dump(xua, &xua_dialect_sua));
+		break;
+	}
+}
+
+/* process received message for invalid origin point code */
+static void sccp_scoc_rx_inval_opc(struct osmo_sccp_instance *inst,
+				   struct xua_msg *xua)
+{
+	/* we have received a message with invalid origin PC and thus
+	 * apply the action indiacted in Table B.2/Q.714 */
+	switch (xua->hdr.msg_type) {
+	case SUA_CO_RELRE: /* RLSD */
+	case SUA_CO_RESRE: /* RSR */
+	case SUA_CO_RESCO: /* RSC */
+		/* Send ERR */
+		tx_coerr_from_xua(inst, xua, SCCP_ERROR_POINT_CODE_MISMATCH);
+		break;
+	case SUA_CO_RELCO: /* RLC */
+	case SUA_CO_CODT: /* DT1 */
+	case SUA_CO_CODA: /* AK */
+	case SUA_CO_COERR: /* ERR */
+		/* DISCARD */
+		break;
+	default:
+		LOGP(DLSCCP, LOGL_NOTICE, "Unhandled %s\n",
+			xua_hdr_dump(xua, &xua_dialect_sua));
+		break;
+	}
+}
+
+/*! \brief Main entrance function for primitives from the SCRC (Routing Control)
+ *  \param[in] inst SCCP Instance in which we operate
+ *  \param[in] xua SUA message in xua_msg format */
+void sccp_scoc_rx_from_scrc(struct osmo_sccp_instance *inst,
+			    struct xua_msg *xua)
+{
+	struct sccp_connection *conn;
+	struct osmo_sccp_user *scu;
+	uint32_t src_loc_ref;
+	int event;
+
+	/* we basically try to convert the SUA message into an event,
+	 * and then dispatch the event to the connection-specific FSM.
+	 * If it is a CORE (Connect REquest), we create the connection
+	 * (and imlpicitly its FSM) first */
+
+	if (xua->hdr.msg_type == SUA_CO_CORE) {
+		scu = sccp_find_user(inst, xua);
+		if (!scu) {
+			/* this shouldn't happen, as the caller should
+			 * have already verified that a local user is
+			 * equipped for this SSN */
+			LOGP(DLSCCP, LOGL_ERROR, "Cannot find user for "
+				"CORE ?!?\n");
+			return;
+		}
+		/* Allocate new connection */
+		conn = conn_create(inst);
+		conn->user = scu;
+	} else {
+		uint32_t conn_id;
+		/* Resolve existing connection */
+		conn_id = xua_msg_get_u32(xua, SUA_IEI_DEST_REF);
+		conn = conn_find_by_id(inst, conn_id);
+		if (!conn) {
+			LOGP(DLSCCP, LOGL_NOTICE, "Cannot find connection for "
+			     "local reference %u\n", conn_id);
+			sccp_scoc_rx_unass_local_ref(inst, xua);
+			return;
+		}
+	}
+	OSMO_ASSERT(conn);
+	OSMO_ASSERT(conn->fi);
+
+	DEBUGP(DLSCCP, "Received %s for local reference %u\n",
+		xua_hdr_dump(xua, &xua_dialect_sua), conn->conn_id);
+
+	if (xua->hdr.msg_type != SUA_CO_CORE &&
+	    xua->hdr.msg_type != SUA_CO_COAK &&
+	    xua->hdr.msg_type != SUA_CO_COREF) {
+		if (xua_msg_find_tag(xua, SUA_IEI_SRC_REF)) {
+			/* Check if received source local reference !=
+			 * the one we saved in local state */
+			src_loc_ref = xua_msg_get_u32(xua, SUA_IEI_SRC_REF);
+			if (src_loc_ref != conn->remote_ref) {
+				sccp_scoc_rx_inval_src_ref(conn, xua);
+				return;
+			}
+		}
+
+		/* Check if received OPC != the remote_pc we stored locally */
+		if (xua->mtp.opc != conn->remote_pc) {
+			sccp_scoc_rx_inval_opc(inst, xua);
+			return;
+		}
+	}
+
+	/* Map from XUA message to event */
+	event = xua_msg_event_map(xua, sua_scoc_event_map, ARRAY_SIZE(sua_scoc_event_map));
+	if (event < 0) {
+		LOGP(DLSCCP, LOGL_ERROR, "Cannot map SCRC msg %s to event\n",
+			xua_hdr_dump(xua, &xua_dialect_sua));
+		/* Table B.1/Q714 states DISCARD for any message with
+		 * unknown type */
+		return;
+	}
+
+	/* Dispatch event to existing connection */
+	osmo_fsm_inst_dispatch(conn->fi, event, xua);
+}
+
+/* get the Connection ID of the given SCU primitive */
+static uint32_t scu_prim_conn_id(const struct osmo_scu_prim *prim)
+{
+	switch (prim->oph.primitive) {
+	case OSMO_SCU_PRIM_N_CONNECT:
+		return prim->u.connect.conn_id;
+	case OSMO_SCU_PRIM_N_DATA:
+		return prim->u.data.conn_id;
+	case OSMO_SCU_PRIM_N_DISCONNECT:
+		return prim->u.disconnect.conn_id;
+	case OSMO_SCU_PRIM_N_RESET:
+		return prim->u.reset.conn_id;
+	default:
+		return 0;
+	}
+}
+
+/*! \brief Main entrance function for primitives from SCCP User 
+ *  \param[in] scu SCCP User sending us the primitive
+ *  \param[in] oph Osmocom primitive sent by the user
+ *  \returns 0 on success; negative on error */
+int osmo_sccp_user_sap_down(struct osmo_sccp_user *scu, struct osmo_prim_hdr *oph)
+{
+	struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
+	struct osmo_sccp_instance *inst = scu->inst;
+	struct msgb *msg = prim->oph.msg;
+	struct sccp_connection *conn;
+	int rc = 0;
+	int event;
+
+	LOGP(DLSCCP, LOGL_DEBUG, "Received SCCP User Primitive %s)\n",
+		osmo_scu_prim_name(&prim->oph));
+
+	switch (OSMO_PRIM_HDR(&prim->oph)) {
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST):
+	/* other CL primitives? */
+		/* Connectionless by-passes this altogether */
+		return sccp_sclc_user_sap_down(scu, oph);
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST):
+		/* Allocate new connection structure */
+		conn = conn_create_id(inst, prim->u.connect.conn_id);
+		if (!conn) {
+			/* FIXME: inform user */
+			goto out;
+		}
+		conn->user = scu;
+		break;
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_RESPONSE):
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST):
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_REQUEST):
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_RESET, PRIM_OP_REQUEST):
+		/* Resolve existing connection structure */
+		conn = conn_find_by_id(inst, scu_prim_conn_id(prim));
+		if (!conn) {
+			/* FIXME: inform user */
+			goto out;
+		}
+		break;
+	}
+
+	/* Map from primitive to event */
+	event = osmo_event_for_prim(oph, scu_scoc_event_map);
+
+	/* Dispatch event into connection */
+	rc = osmo_fsm_inst_dispatch(conn->fi, event, prim);
+out:
+	/* the SAP is supposed to consume the primitive/msgb */
+	msgb_free(msg);
+
+	return rc;
+}
+
+void sccp_scoc_flush_connections(struct osmo_sccp_instance *inst)
+{
+	struct sccp_connection *conn, *conn2;
+
+	llist_for_each_entry_safe(conn, conn2, &inst->connections, list)
+		conn_destroy(conn);
+}
diff --git a/src/sccp_scrc.c b/src/sccp_scrc.c
new file mode 100644
index 0000000..9bccc0a
--- /dev/null
+++ b/src/sccp_scrc.c
@@ -0,0 +1,473 @@
+/* SCCP Routing Control (SCRC) according to ITU-T Q.714 */
+
+/* (C) 2015-2017 by Harald Welte <laforge at 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdbool.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+
+#include <sccp/sccp_types.h>
+#include <osmocom/sigtran/osmo_ss7.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/protocol/sua.h>
+#include <osmocom/sigtran/protocol/mtp.h>
+
+#include "sccp_internal.h"
+#include "xua_internal.h"
+
+/***********************************************************************
+ * Helper Functions
+ ***********************************************************************/
+
+static bool sua_is_connectionless(struct xua_msg *xua)
+{
+	if (xua->hdr.msg_class == SUA_MSGC_CL)
+		return true;
+	else
+		return false;
+}
+
+static bool sua_is_cr(struct xua_msg *xua)
+{
+	if (xua->hdr.msg_class == SUA_MSGC_CO &&
+	    xua->hdr.msg_type == SUA_CO_CORE)
+		return true;
+
+	return false;
+}
+
+static bool dpc_accessible(struct osmo_sccp_instance *inst, uint32_t pc)
+{
+	/* TODO: implement this! */
+	return true;
+}
+
+static bool sccp_available(struct osmo_sccp_instance *inst,
+			   const struct osmo_sccp_addr *addr)
+{
+	/* TODO: implement this! */
+	return true;
+}
+
+static int sua2sccp_tx_m3ua(struct osmo_sccp_instance *inst,
+			    struct xua_msg *sua)
+{
+	struct msgb *msg;
+	struct osmo_mtp_prim *omp;
+	struct osmo_mtp_transfer_param *param;
+	struct osmo_ss7_instance *s7i = inst->ss7;
+	uint32_t remote_pc = sua->mtp.dpc;
+
+	/* 1) encode the SUA in xua_msg to SCCP message */
+	msg = osmo_sua_to_sccp(sua);
+	if (!msg) {
+		LOGP(DLSCCP, LOGL_ERROR, "Cannot encode SUA to SCCP\n");
+		return -1;
+	}
+
+	/* 2) wrap into MTP-TRANSFER.req primtiive */
+	msg->l2h = msg->data;
+	omp = (struct osmo_mtp_prim *) msgb_push(msg, sizeof(*omp));
+	osmo_prim_init(&omp->oph, MTP_SAP_USER,
+			OSMO_MTP_PRIM_TRANSFER, PRIM_OP_REQUEST, msg);
+	param = &omp->u.transfer;
+	if (sua->mtp.opc)
+		param->opc = sua->mtp.opc;
+	else
+		param->opc = s7i->cfg.primary_pc;
+	param->dpc = remote_pc;
+	param->sls = sua->mtp.sls;
+	param->sio = MTP_SIO(MTP_SI_SCCP, s7i->cfg.network_indicator);
+
+	/* 3) send via MTP-SAP (osmo_ss7_instance) */
+	return osmo_ss7_user_mtp_xfer_req(s7i, omp);
+}
+
+/* Gererate MTP-TRANSFER.req from xUA message */
+static int gen_mtp_transfer_req_xua(struct osmo_sccp_instance *inst,
+				    struct xua_msg *xua,
+				    const struct osmo_sccp_addr *called)
+{
+	struct osmo_ss7_route *rt;
+
+	/* this is a bit fishy due to the different requirements of
+	 * classic SSCP/MTP compared to various SIGTRAN stackings.
+	 * Normally, we would expect a fully encoded SCCP message here,
+	 * but then if the route points to a SUA link, we actually need
+	 * the SUA version of the message.
+	 *
+	 * We need to differentiate the following cases:
+	 * a) SUA: encode XUA to SUA and send via ASP
+	 * b) M3UA: encode XUA to SCCP, create MTP-TRANSFER.req
+	 *    primitive and send it via ASP
+	 * c) M2UA/M2PA or CS7: encode XUA, create MTP-TRANSFER.req
+	 *    primitive and send it via link
+	 */
+
+	if (called->presence & OSMO_SCCP_ADDR_T_PC)
+		xua->mtp.dpc = called->pc;
+	if (!xua->mtp.dpc) {
+		LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP "
+			"without DPC?!?\n");
+		return -1;
+	}
+
+	rt = osmo_ss7_route_lookup(inst->ss7, xua->mtp.dpc);
+	if (!rt) {
+		LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP for "
+			"DPC %u: no route!\n", xua->mtp.dpc);
+		return -1;
+	}
+
+	if (rt->dest.as) {
+		struct osmo_ss7_as *as = rt->dest.as;
+		switch (as->cfg.proto) {
+		case OSMO_SS7_ASP_PROT_M3UA:
+			return sua2sccp_tx_m3ua(inst, xua);
+		default:
+			LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req for "
+				"unknown protocol %u\n", as->cfg.proto);
+			break;
+		}
+	} else if (rt->dest.linkset) {
+		LOGP(DLSCCP, LOGL_ERROR, "MTP-TRANSFER.req from SCCP for "
+			"linkset %s unsupported\n", rt->dest.linkset->cfg.name);
+	} else {
+		OSMO_ASSERT(0);
+	}
+	return -1;
+}
+
+/***********************************************************************
+ * Global Title Translation
+ ***********************************************************************/
+
+static int translate(struct osmo_sccp_instance *inst,
+		     const struct osmo_sccp_addr *called,
+		     struct osmo_sccp_addr *translated)
+{
+	/* TODO: implement this! */
+	*translated = *called;
+	return 0;
+}
+
+
+/***********************************************************************
+ * Individual SCRC Nodes
+ ***********************************************************************/
+
+static int scrc_local_out_common(struct osmo_sccp_instance *inst,
+				 struct xua_msg *xua,
+				 const struct osmo_sccp_addr *called);
+
+static int scrc_node_12(struct osmo_sccp_instance *inst, struct xua_msg *xua,
+			const struct osmo_sccp_addr *called)
+{
+	/* TODO: Determine restriction */
+	/* TODO: Treat Calling Party Addr */
+	/* TODO: Hop counter */
+	/* MTP-TRANSFER.req to MTP */
+	return gen_mtp_transfer_req_xua(inst, xua, called);
+}
+
+static int scrc_node_2(struct osmo_sccp_instance *inst, struct xua_msg *xua,
+			const struct osmo_sccp_addr *called)
+{
+	/* Node 2 on Sheet 5, only CO */
+	/* Is DPC accessible? */
+	if (!dpc_accessible(inst, called->pc)) {
+		/* Error: MTP Failure */
+		/* Routing Failure SCRC -> SCOC */
+		sccp_scoc_rx_scrc_rout_fail(inst, xua,
+				SCCP_RETURN_CAUSE_MTP_FAILURE);
+		return 0;
+	}
+	/* Is SCCP available? */
+	if (!sccp_available(inst, called)) {
+		/* Error: SCCP Failure */
+		/* Routing Failure SCRC -> SCOC */
+		sccp_scoc_rx_scrc_rout_fail(inst, xua,
+				SCCP_RETURN_CAUSE_SCCP_FAILURE);
+		return 0;
+	}
+	return scrc_node_12(inst, xua, called);
+}
+
+static int scrc_node_7(struct osmo_sccp_instance *inst,
+			struct xua_msg *xua,
+			const struct osmo_sccp_addr *called)
+{
+	/* Connection Oriented? */
+	if (sua_is_connectionless(xua)) {
+		/* TODO: Perform Capability Test */
+		/* TODO: Canges Needed? */
+		if (0) {
+			/* Changes Needed -> SCLC */
+			return 0;
+		}
+	} else {
+		/* TODO: Coupling Required? */
+		if (0) {
+			/* Node 13 (Sheet 5) */
+		}
+	}
+	return scrc_node_12(inst, xua, called);
+}
+
+/* Node 4 (Sheet 3) */
+static int scrc_node_4(struct osmo_sccp_instance *inst,
+		       struct xua_msg *xua, uint32_t return_cause)
+{
+	/* TODO: Routing Failure SCRC -> OMAP */
+	if (sua_is_connectionless(xua)) {
+		/* Routing Failure SCRC -> SCLC */
+		sccp_sclc_rx_scrc_rout_fail(inst, xua, return_cause);
+	} else {
+		/* Routing Failure SCRC -> SCOC */
+		sccp_scoc_rx_scrc_rout_fail(inst, xua, return_cause);
+	}
+	return 0;
+}
+
+static int scrc_translate_node_9(struct osmo_sccp_instance *inst,
+				 struct xua_msg *xua,
+				 const struct osmo_sccp_addr *called)
+{
+	struct osmo_sccp_addr translated;
+	int rc;
+
+	/* Translate */
+	rc = translate(inst, called, &translated);
+	/* Node 9 (Sheet 3) */
+	if (rc < 0) {
+		/* Node 4 (Sheet 3) */
+		return scrc_node_4(inst, xua,
+				   SCCP_RETURN_CAUSE_NO_TRANSLATION);
+	}
+	/* Route on SSN? */
+	if (translated.ri != OSMO_SCCP_RI_SSN_PC &&
+	    translated.ri != OSMO_SCCP_RI_SSN_IP) {
+		/* TODO: GT Routing */
+		/* Node 7 (Sheet 5) */
+		return scrc_node_7(inst, xua, called);
+	}
+
+	/* Check DPC resultant from GT translation */
+	if (osmo_ss7_pc_is_local(inst->ss7, translated.pc)) {
+		if (sua_is_connectionless(xua)) {
+			/* CL_MSG -> SCLC */
+			sccp_sclc_rx_from_scrc(inst, xua);
+		} else {
+			/* Node 1 (Sheet 3) */
+			/* CO_MSG -> SCOC */
+			sccp_scoc_rx_from_scrc(inst, xua);
+		}
+		return 0;
+	} else {
+		/* Availability already checked */
+		/* Node 7 (Sheet 5) */
+		return scrc_node_7(inst, xua, called);
+	}
+}
+
+/* Node 6 (Sheet 3) */
+static int scrc_node_6(struct osmo_sccp_instance *inst,
+		       struct xua_msg *xua,
+		       const struct osmo_sccp_addr *called)
+{
+	struct osmo_sccp_user *scu;
+
+	scu = sccp_user_find(inst, called->ssn, called->pc);
+
+	/* Is subsystem equipped? */
+	if (!scu) {
+		/* Error: unequipped user */
+		return scrc_node_4(inst, xua,
+				   SCCP_RETURN_CAUSE_UNEQUIPPED_USER);
+	}
+	/* Is subsystem available? */
+	if (0 /* !subsys_available(scu) */) {
+		/* Error: subsystem failure */
+		/* TODO: SCRC -> SSPC */
+		if (sua_is_connectionless(xua)) {
+			/* Routing Failure SCRC -> SCLC */
+			sccp_sclc_rx_scrc_rout_fail(inst, xua,
+				SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE);
+		} else {
+			/* Routing Failure SCRC -> SCOC */
+			sccp_scoc_rx_scrc_rout_fail(inst, xua,
+				SCCP_RETURN_CAUSE_SUBSYSTEM_FAILURE);
+		}
+		return 0;
+	}
+	if (sua_is_connectionless(xua)) {
+		/* CL_MSG -> SCLC */
+		sccp_sclc_rx_from_scrc(inst, xua);
+	} else {
+		/* Node 1 (Sheet 3) */
+		/* CO_MSG -> SCOC */
+		sccp_scoc_rx_from_scrc(inst, xua);
+	}
+	return 0;
+}
+
+static int scrc_local_out_common(struct osmo_sccp_instance *inst,
+				 struct xua_msg *xua,
+				 const struct osmo_sccp_addr *called)
+{
+	struct osmo_ss7_instance *s7i = inst->ss7;
+
+	/* Called address includes DPC? */
+	if (called->presence & OSMO_SCCP_ADDR_T_PC) {
+		if (!osmo_ss7_pc_is_local(s7i, called->pc)) {
+			/* Node 7 of sheet 5 */
+			/* Coupling required: no */
+			return scrc_node_12(inst, xua, called);
+		}
+		/* Called address includes SSN? */
+		if (called->presence & OSMO_SCCP_ADDR_T_SSN) {
+			if (translate &&
+			    (called->presence & OSMO_SCCP_ADDR_T_GT))
+				return scrc_translate_node_9(inst, xua, called);
+			else
+				return scrc_node_6(inst, xua, called);
+		}
+	}
+	/* No SSN in CalledAddr or no DPC included */
+	if (!(called->presence & OSMO_SCCP_ADDR_T_GT)) {
+		/* Error reason: Unqualified */
+		/* TODO: Routing Failure SCRC -> OMAP */
+		/* Node 4 (Sheet 3) */
+		return scrc_node_4(inst, xua,
+				   SCCP_RETURN_CAUSE_UNQUALIFIED);
+	} else
+		return scrc_translate_node_9(inst, xua, called);
+}
+
+/***********************************************************************
+ * Entrance points from MTP, SCLC, SCOC, ...
+ ***********************************************************************/
+
+/* Figure C.1/Q.714 - SCCP Routing control procedures (SCRC) */
+
+/* Connection oriented message SCOC -> SCRC */
+int sccp_scrc_rx_scoc_conn_msg(struct osmo_sccp_instance *inst,
+				struct xua_msg *xua)
+{
+	struct osmo_sccp_addr called;
+
+	LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua));
+
+	sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR);
+
+	/* Is this a CR message ? */
+	if (xua->hdr.msg_type != SUA_CO_CORE)
+		return scrc_node_2(inst, xua, &called);
+
+	/* TOOD: Coupling performed (not supported) */
+	if (0)
+		return scrc_node_2(inst, xua, &called);
+
+	return scrc_local_out_common(inst, xua, &called);
+}
+
+/* Connectionless Message SCLC -> SCRC */
+int sccp_scrc_rx_sclc_msg(struct osmo_sccp_instance *inst,
+			  struct xua_msg *xua)
+{
+	struct osmo_sccp_addr called;
+
+	LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua));
+
+	sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR);
+
+	/* Message Type */
+	if (xua->hdr.msg_type == SUA_CL_CLDR) {
+		/* UDTS, XUDTS or LUDTS */
+		if (called.ri != OSMO_SCCP_RI_GT)
+			return scrc_node_7(inst, xua, &called);
+		/* Fall-through */
+	} else {
+		if (0 /* TODO: translation already performed */) {
+			/* Node 12 (Sheet 5) */
+			return scrc_node_12(inst, xua, &called);
+		}
+	}
+	return scrc_local_out_common(inst, xua, &called);
+}
+
+/* Figure C.1/Q.714 Sheet 1 of 12, after we converted the
+ * MTP-TRANSFER.ind to SUA */
+int scrc_rx_mtp_xfer_ind_xua(struct osmo_sccp_instance *inst,
+			     struct xua_msg *xua)
+{
+	struct osmo_sccp_addr called;
+	uint32_t proto_class;
+	struct xua_msg_part *hop_ctr_part;
+
+	LOGP(DLSS7, LOGL_DEBUG, "%s: %s\n", __func__, xua_msg_dump(xua, &xua_dialect_sua));
+	/* TODO: SCCP or nodal congestion? */
+
+	/* CR or CL message? */
+	if (!sua_is_connectionless(xua) && !sua_is_cr(xua)) {
+		/* Node 1 (Sheet 3) */
+		/* deliver to SCOC */
+		sccp_scoc_rx_from_scrc(inst, xua);
+		return 0;
+	}
+	/* We only treat connectionless and CR below */
+
+	sua_addr_parse(&called, xua, SUA_IEI_DEST_ADDR);
+
+	/* Route on GT? */
+	if (called.ri != OSMO_SCCP_RI_GT) {
+		/* Node 6 (Sheet 3) */
+		return scrc_node_6(inst, xua, &called);
+	}
+	/* Message with hop-counter? */
+	hop_ctr_part = xua_msg_find_tag(xua, SUA_IEI_S7_HOP_CTR);
+	if (hop_ctr_part) {
+		uint32_t hop_counter = xua_msg_part_get_u32(hop_ctr_part);
+		if (hop_counter <= 1) {
+			/* Error: hop-counter violation */
+			/* node 4 */
+			return scrc_node_4(inst, xua,
+					   SCCP_RETURN_CAUSE_HOP_COUNTER_VIOLATION);
+		}
+		/* Decrement hop-counter */
+		hop_counter--;
+		*(uint32_t *)hop_ctr_part->dat = htonl(hop_counter);
+	}
+
+	/* node 3 (Sheet 2) */
+	/* Protocol class 0? */
+	proto_class = xua_msg_get_u32(xua, SUA_IEI_PROTO_CLASS);
+	switch (proto_class) {
+	case 0:
+		/* TODO: Assign SLS */
+		break;
+	case 1:
+		/* TODO: Map incoming SLS to outgoing SLS */
+		break;
+	default:
+		break;
+	}
+	return scrc_translate_node_9(inst, xua, &called);
+}
diff --git a/src/sccp_user.c b/src/sccp_user.c
new file mode 100644
index 0000000..df02486
--- /dev/null
+++ b/src/sccp_user.c
@@ -0,0 +1,377 @@
+/* SCCP User related routines */
+
+/* (C) 2017 by Harald Welte <laforge at gnumonks.org>
+ * All Rights Reserved
+ *
+ * based on my 2011 Erlang implementation osmo_ss7/src/sua_sccp_conv.erl
+ *
+ * References: ITU-T Q.713 and IETF RFC 3868
+ *
+ * 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 <stdbool.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/sigtran/osmo_ss7.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/mtp_sap.h>
+#include <osmocom/sigtran/protocol/mtp.h>
+
+#include "sccp_internal.h"
+#include "xua_internal.h"
+
+/*! \brief Find a SCCP User registered for given PC+SSN or SSN only
+ *  \param[in] inst SCCP Instance in which to search
+ *  \param[in] ssn Sub-System Number to search for
+ *  \param[in] pc Point Code to search for
+ *  \returns Matching SCCP User; NULL if none found */
+struct osmo_sccp_user *
+sccp_user_find(struct osmo_sccp_instance *inst, uint16_t ssn, uint32_t pc)
+{
+	struct osmo_sccp_user *scu;
+
+	/* First try to find match for PC + SSN */
+	llist_for_each_entry(scu, &inst->users, list) {
+		if (scu->pc_valid && scu->pc == pc && scu->ssn == ssn)
+			return scu;
+	}
+
+	/* Then try to match on SSN only */
+	llist_for_each_entry(scu, &inst->users, list) {
+		if (!scu->pc_valid && scu->ssn == ssn)
+			return scu;
+	}
+
+	return NULL;
+}
+
+/*! \brief Bind a SCCP User to a given Point Code
+ *  \param[in] inst SCCP Instance
+ *  \param[in] name human-readable name
+ *  \param[in] ssn Sub-System Number to bind to
+ *  \param[in] pc Point Code to bind to (if any)
+ *  \param[in] pc_valid Whether or not \ref pc is valid/used
+ *  \returns Callee-allocated SCCP User on success; negative otherwise */
+static struct osmo_sccp_user *
+sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name,
+		  osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc, bool pc_valid)
+{
+	struct osmo_sccp_user *scu;
+	if (!pc_valid)
+		pc = 0;
+
+	if (sccp_user_find(inst, ssn, pc))
+		return NULL;
+
+	LOGP(DLSCCP, LOGL_INFO, "Binding user '%s' to SSN=%u PC=%u (pc_valid=%u)\n",
+		name, ssn, pc, pc_valid);
+
+	scu = talloc_zero(inst, struct osmo_sccp_user);
+	scu->name = talloc_strdup(scu, name);
+	scu->inst = inst;
+	scu->prim_cb = prim_cb;
+	scu->ssn = ssn;
+	scu->pc = pc;
+	scu->pc_valid = pc_valid;
+	llist_add_tail(&scu->list, &inst->users);
+
+	return scu;
+}
+
+/*! \brief Bind a given SCCP User to a given SSN+PC
+ *  \param[in] inst SCCP Instance
+ *  \param[in] name human-readable name
+ *  \param[in] ssn Sub-System Number to bind to
+ *  \param[in] pc Point Code to bind to (if any)
+ *  \returns Callee-allocated SCCP User on success; negative otherwise */
+struct osmo_sccp_user *
+osmo_sccp_user_bind_pc(struct osmo_sccp_instance *inst, const char *name,
+		       osmo_prim_cb prim_cb, uint16_t ssn, uint32_t pc)
+{
+	return sccp_user_bind_pc(inst, name, prim_cb, ssn, pc, true);
+}
+
+/*! \brief Bind a given SCCP User to a given SSN (at any PC)
+ *  \param[in] inst SCCP Instance
+ *  \param[in] name human-readable name
+ *  \param[in] ssn Sub-System Number to bind to
+ *  \returns Callee-allocated SCCP User on success; negative otherwise */
+struct osmo_sccp_user *
+osmo_sccp_user_bind(struct osmo_sccp_instance *inst, const char *name,
+		    osmo_prim_cb prim_cb, uint16_t ssn)
+{
+	return sccp_user_bind_pc(inst, name, prim_cb, ssn, 0, false);
+}
+
+/*! \brief Unbind a given SCCP user
+ *  \param[in] scu SCCP User which is to be un-bound. Will be destroyed
+ *  		at the time this function returns. */
+void osmo_sccp_user_unbind(struct osmo_sccp_user *scu)
+{
+	LOGP(DLSCCP, LOGL_INFO, "Unbinding user '%s' from SSN=%u PC=%u "
+		"(pc_valid=%u)\n", scu->name, scu->ssn, scu->pc,
+		scu->pc_valid);
+	/* FIXME: free/release all connections held by this user? */
+	llist_del(&scu->list);
+	talloc_free(scu);
+}
+
+/*! \brief Send a SCCP User SAP Primitive up to the User
+ *  \param[in] scu SCCP User to whom to send the primitive
+ *  \param[in] prim Primitive to send to the user
+ *  \returns return value of the SCCP User's prim_cb() function */
+int sccp_user_prim_up(struct osmo_sccp_user *scu, struct osmo_scu_prim *prim)
+{
+	LOGP(DLSCCP, LOGL_DEBUG, "Delivering %s to SCCP User '%s'\n",
+		osmo_scu_prim_name(&prim->oph), scu->name);
+	return scu->prim_cb(&prim->oph, scu);
+}
+
+/* prim_cb handed to MTP code for incoming MTP-TRANSFER.ind */
+static int mtp_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+	struct osmo_sccp_instance *inst = ctx;
+	struct osmo_mtp_prim *omp = (struct osmo_mtp_prim *)oph;
+	struct xua_msg *xua;
+
+	OSMO_ASSERT(oph->sap == MTP_SAP_USER);
+
+	switch OSMO_PRIM(oph->primitive, oph->operation) {
+	case OSMO_PRIM(OSMO_MTP_PRIM_TRANSFER, PRIM_OP_INDICATION):
+		/* Convert from SCCP to SUA in xua_msg format */
+		xua = osmo_sccp_to_xua(oph->msg);
+		xua->mtp = omp->u.transfer;
+		/* hand this primitive into SCCP via the SCRC code */
+		return scrc_rx_mtp_xfer_ind_xua(inst, xua);
+	default:
+		LOGP(DLSCCP, LOGL_ERROR, "Unknown primitive %u:%u receivd\n",
+			oph->primitive, oph->operation);
+		return -1;
+	}
+}
+
+static LLIST_HEAD(sccp_instances);
+
+/*! \brief create a SCCP Instance and register it as user with SS7 inst
+ *  \param[in] ss7 SS7 instance to which this SCCP instance belongs
+ *  \param[in] priv private data to be stored within SCCP instance
+ *  \returns callee-allocated SCCP instance on success; NULL on error */
+struct osmo_sccp_instance *
+osmo_sccp_instance_create(struct osmo_ss7_instance *ss7, void *priv)
+{
+	struct osmo_sccp_instance *inst;
+
+	inst = talloc_zero(ss7, struct osmo_sccp_instance);
+	if (!inst)
+		return NULL;
+
+	inst->ss7 = ss7;
+	inst->priv = priv;
+	INIT_LLIST_HEAD(&inst->connections);
+	INIT_LLIST_HEAD(&inst->users);
+
+	inst->ss7_user.inst = ss7;
+	inst->ss7_user.name = "SCCP";
+	inst->ss7_user.prim_cb = mtp_user_prim_cb;
+	inst->ss7_user.priv = inst;
+
+	osmo_ss7_user_register(ss7, MTP_SI_SCCP, &inst->ss7_user);
+
+	llist_add_tail(&inst->list, &sccp_instances);
+
+	return inst;
+}
+
+void osmo_sccp_instance_destroy(struct osmo_sccp_instance *inst)
+{
+	struct osmo_sccp_user *scu, *scu2;
+
+	inst->ss7->sccp = NULL;
+	osmo_ss7_user_unregister(inst->ss7, MTP_SI_SCCP, &inst->ss7_user);
+
+	llist_for_each_entry_safe(scu, scu2, &inst->users, list) {
+		osmo_sccp_user_unbind(scu);
+	}
+	sccp_scoc_flush_connections(inst);
+	llist_del(&inst->list);
+	talloc_free(inst);
+}
+
+/***********************************************************************
+ * Convenience function for CLIENT
+ ***********************************************************************/
+
+struct osmo_sccp_instance *
+osmo_sccp_simple_client(void *ctx, const char *name, uint32_t pc,
+			enum osmo_ss7_asp_protocol prot,
+			int local_port, int remote_port, const char *remote_ip)
+{
+	struct osmo_ss7_instance *ss7;
+	struct osmo_ss7_as *as;
+	struct osmo_ss7_route *rt;
+	struct osmo_ss7_asp *asp;
+	char *as_name, *asp_name;
+
+	if (!remote_port || remote_port < 0)
+		remote_port = osmo_ss7_asp_protocol_port(prot);
+	if (local_port < 0)
+		local_port = osmo_ss7_asp_protocol_port(prot);
+
+	/* allocate + initialize SS7 instance */
+	ss7 = osmo_ss7_instance_find_or_create(ctx, 1);
+	if (!ss7)
+		return NULL;
+	ss7->cfg.primary_pc = pc;
+
+	as_name = talloc_asprintf(ctx, "as-clnt-%s", name);
+	asp_name = talloc_asprintf(ctx, "asp-clnt-%s", name);
+
+	/* application server */
+	as = osmo_ss7_as_find_or_create(ss7, as_name, prot);
+	if (!as)
+		goto out_strings;
+
+	/* install default route */
+	rt = osmo_ss7_route_create(ss7->rtable_system, 0, 0, as_name);
+	if (!rt)
+		goto out_as;
+	talloc_free(as_name);
+
+	/* application server process */
+	asp = osmo_ss7_asp_find_or_create(ss7, asp_name, remote_port, local_port,
+					  prot);
+	if (!asp)
+		goto out_rt;
+	asp->cfg.remote.host = talloc_strdup(asp, remote_ip);
+	osmo_ss7_as_add_asp(as, asp_name);
+	talloc_free(asp_name);
+	osmo_ss7_asp_restart(asp);
+
+	/* Allocate SCCP stack + SCCP user */
+	ss7->sccp = osmo_sccp_instance_create(ss7, NULL);
+	if (!ss7->sccp)
+		goto out_asp;
+
+	return ss7->sccp;
+
+out_asp:
+	osmo_ss7_asp_destroy(asp);
+out_rt:
+	osmo_ss7_route_destroy(rt);
+out_as:
+	osmo_ss7_as_destroy(as);
+out_strings:
+	talloc_free(as_name);
+	talloc_free(asp_name);
+	osmo_ss7_instance_destroy(ss7);
+
+	return NULL;
+}
+
+/***********************************************************************
+ * Convenience function for SERVER
+ ***********************************************************************/
+
+struct osmo_sccp_instance *
+osmo_sccp_simple_server(void *ctx, uint32_t pc,
+			enum osmo_ss7_asp_protocol prot, int local_port,
+			const char *local_ip)
+{
+	struct osmo_ss7_instance *ss7;
+	struct osmo_xua_server *xs;
+
+	if (local_port < 0)
+		local_port = osmo_ss7_asp_protocol_port(prot);
+
+	/* allocate + initialize SS7 instance */
+	ss7 = osmo_ss7_instance_find_or_create(ctx, 1);
+	if (!ss7)
+		return NULL;
+	ss7->cfg.primary_pc = pc;
+
+	xs = osmo_ss7_xua_server_create(ss7, prot, local_port, local_ip);
+	if (!xs)
+		goto out_ss7;
+
+	/* Allocate SCCP stack */
+	ss7->sccp = osmo_sccp_instance_create(ss7, NULL);
+	if (!ss7->sccp)
+		goto out_xs;
+
+	return ss7->sccp;
+
+out_xs:
+	osmo_ss7_xua_server_destroy(xs);
+out_ss7:
+	osmo_ss7_instance_destroy(ss7);
+
+	return NULL;
+}
+
+struct osmo_sccp_instance *
+osmo_sccp_simple_server_add_clnt(struct osmo_sccp_instance *inst,
+				 enum osmo_ss7_asp_protocol prot,
+				 const char *name, uint32_t pc,
+				 int local_port, int remote_port,
+				 const char *remote_ip)
+{
+	struct osmo_ss7_instance *ss7 = inst->ss7;
+	struct osmo_ss7_as *as;
+	struct osmo_ss7_route *rt;
+	struct osmo_ss7_asp *asp;
+	char *as_name, *asp_name;
+
+	if (local_port < 0)
+		local_port = osmo_ss7_asp_protocol_port(prot);
+
+	if (remote_port < 0)
+		remote_port = osmo_ss7_asp_protocol_port(prot);
+
+	as_name = talloc_asprintf(ss7, "as-srv-%s", name);
+	asp_name = talloc_asprintf(ss7, "asp-srv-%s", name);
+
+	/* application server */
+	as = osmo_ss7_as_find_or_create(ss7, as_name, prot);
+	if (!as)
+		goto out_strings;
+	talloc_free(as_name);
+
+	/* route only selected PC to the client */
+	rt = osmo_ss7_route_create(ss7->rtable_system, pc, 0xffff, as_name);
+	if (!rt)
+		goto out_as;
+
+	asp = osmo_ss7_asp_find_or_create(ss7, asp_name, remote_port, local_port, prot);
+	if (!asp)
+		goto out_rt;
+	asp->cfg.is_server = true;
+	osmo_ss7_as_add_asp(as, asp_name);
+	talloc_free(asp_name);
+	osmo_ss7_asp_restart(asp);
+
+	return ss7->sccp;
+
+out_rt:
+	osmo_ss7_route_destroy(rt);
+out_as:
+	osmo_ss7_as_destroy(as);
+out_strings:
+	talloc_free(as_name);
+	talloc_free(asp_name);
+
+	return NULL;
+}

-- 
To view, visit https://gerrit.osmocom.org/2215
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newpatchset
Gerrit-Change-Id: I916e895d9a4914b05483fe12ab5251f206d10dee
Gerrit-PatchSet: 7
Gerrit-Project: libosmo-sccp
Gerrit-Branch: master
Gerrit-Owner: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Jenkins Builder



More information about the gerrit-log mailing list