aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/osmocom/sigtran/sccp_sap.h20
-rw-r--r--src/Makefile.am3
-rw-r--r--src/osmo_ss7.c2
-rw-r--r--src/sccp_internal.h86
-rw-r--r--src/sccp_sclc.c337
-rw-r--r--src/sccp_scoc.c1642
-rw-r--r--src/sccp_scrc.c473
-rw-r--r--src/sccp_user.c377
8 files changed, 2938 insertions, 2 deletions
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 @@ struct osmo_scu_prim {
#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 @@ lib_LTLIBRARIES = libosmo-sigtran.la
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 @@ int osmo_ss7_init(void)
{
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@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@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@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@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;
+}