Add support for LCLS to the MSC

This commit is largely based on work by
Max <msuraev@sysmocom.de>

Adds LCLS parameters for A-interface transactions
This commit also adds a vty option to facilitate globally
disabling LCLS for all calls on this MSC.

Add a global call reference (GCR) to MNCC and therefore
bump the MNCC version to version 8. (This commit has to be
merged at the same time as the corresponing commit in the
osmo-sip-connector for mncc-external use.)

Depends: osmo-sip-connector Id40d7e0fed9356f801b3627c118150055e7232b1
Change-Id: I705c860e51637b4537cad65a330ecbaaca96dd5b
diff --git a/src/libmsc/gsm_04_08_cc.c b/src/libmsc/gsm_04_08_cc.c
index a8b4665..d6a2864 100644
--- a/src/libmsc/gsm_04_08_cc.c
+++ b/src/libmsc/gsm_04_08_cc.c
@@ -318,6 +318,16 @@
 		msc_a_get(msc_a, MSC_A_USE_CC);
 		trans->msc_a = msc_a;
 		trans->paging_request = NULL;
+
+		/* Get the GCR from the MO call leg (if any). */
+		if (!trans->cc.lcls) {
+			trans->cc.lcls = trans_lcls_compose(trans, true);
+			if (trans->cc.lcls) {
+				trans->cc.lcls->gcr = trans->cc.msg.gcr;
+				trans->cc.lcls->gcr_available = true;
+			}
+		}
+
 		osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans);
 		/* send SETUP request to called party */
 		gsm48_cc_tx_setup(trans, &trans->cc.msg);
@@ -502,6 +512,14 @@
 	memset(&setup, 0, sizeof(struct gsm_mncc));
 	setup.callref = trans->callref;
 
+	/* New Global Call Reference */
+	if (!trans->cc.lcls)
+		trans->cc.lcls = trans_lcls_compose(trans, true);
+
+	/* Pass the LCLS GCR on to the MT call leg via MNCC */
+	if (trans->cc.lcls)
+		setup.gcr = trans->cc.lcls->gcr;
+
 	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
 	/* emergency setup is identified by msg_type */
 	if (msg_type == GSM48_MT_CC_EMERG_SETUP) {
diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c
index fa8e842..9b6b602 100644
--- a/src/libmsc/msc_a.c
+++ b/src/libmsc/msc_a.c
@@ -569,6 +569,7 @@
 			.osmux_cid = msc_a->cc.call_leg->rtp[RTP_TO_RAN]->local_osmux_cid,
 			.call_id_present = true,
 			.call_id = cc_trans->callref,
+			.lcls = cc_trans->cc.lcls,
 		},
 	};
 	if (msc_a_ran_down(msc_a, MSC_ROLE_I, &msg)) {
@@ -1506,6 +1507,13 @@
 		rc = msc_a_up_ho(msc_a, d, MSC_HO_EV_RX_FAILURE);
 		break;
 
+	case RAN_MSG_LCLS_STATUS:
+		/* The BSS sends us LCLS_STATUS. We do nothing for now, but it is not an error. */
+		LOG_MSC_A(msc_a, LOGL_DEBUG, "LCLS_STATUS (%s) received from MSC-I\n",
+			  gsm0808_lcls_status_name(msg->lcls_status.status));
+		rc = 0;
+		break;
+
 	default:
 		LOG_MSC_A(msc_a, LOGL_ERROR, "Message from MSC-I not implemented: %s\n", ran_msg_type_name(msg->msg_type));
 		rc = -ENOTSUP;
diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c
index 2a4dbbb..e4e0937 100644
--- a/src/libmsc/msc_vty.c
+++ b/src/libmsc/msc_vty.c
@@ -499,6 +499,24 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN_ATTR(cfg_msc_lcls_disable, cfg_msc_lcls_disable_cmd,
+	    "lcls-permitted",
+	    "Globally allow LCLS (Local Call Local Switch) for all calls on this MSC.\n",
+	    CMD_ATTR_IMMEDIATE)
+{
+	gsmnet->lcls_permitted = true;
+	return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_msc_no_lcls_disable, cfg_msc_no_lcls_disable_cmd,
+	    "no lcls-permitted",
+	    NO_STR "Globally disable LCLS (Local Call Local Switch) for all calls on this MSC.\n",
+	    CMD_ATTR_IMMEDIATE)
+{
+	gsmnet->lcls_permitted = false;
+	return CMD_SUCCESS;
+}
+
 DEFUN(cfg_msc_cs7_instance_a,
       cfg_msc_cs7_instance_a_cmd,
       "cs7-instance-a <0-15>",
@@ -764,6 +782,8 @@
 		gsmnet->ncss_guard_timeout, VTY_NEWLINE);
 	vty_out(vty, " %sassign-tmsi%s",
 		gsmnet->vlr->cfg.assign_tmsi? "" : "no ", VTY_NEWLINE);
+	if (gsmnet->lcls_permitted)
+		vty_out(vty, " lcls-permitted%s", VTY_NEWLINE);
 
 	vty_out(vty, " cs7-instance-a %u%s", gsmnet->a.cs7_instance,
 		VTY_NEWLINE);
@@ -2083,6 +2103,8 @@
 	install_node(&msc_node, config_write_msc);
 	install_element(MSC_NODE, &cfg_sms_database_cmd);
 	install_element(MSC_NODE, &cfg_msc_assign_tmsi_cmd);
+	install_element(MSC_NODE, &cfg_msc_lcls_disable_cmd);
+	install_element(MSC_NODE, &cfg_msc_no_lcls_disable_cmd);
 	install_element(MSC_NODE, &cfg_msc_mncc_internal_cmd);
 	install_element(MSC_NODE, &cfg_msc_mncc_external_cmd);
 	install_element(MSC_NODE, &cfg_msc_mncc_guard_timeout_cmd);
diff --git a/src/libmsc/ran_msg_a.c b/src/libmsc/ran_msg_a.c
index 273f8dd..b50259d 100644
--- a/src/libmsc/ran_msg_a.c
+++ b/src/libmsc/ran_msg_a.c
@@ -1004,7 +1004,8 @@
 	if(ac->call_id_present == true)
 		call_id = &ac->call_id;
 
-	msg = gsm0808_create_ass(ac->channel_type, NULL, use_rtp_addr, use_scl, call_id);
+	msg = gsm0808_create_ass2(ac->channel_type, NULL, use_rtp_addr, use_scl, call_id,
+				  NULL, ac->lcls);
 	if (ac->osmux_present)
 		_gsm0808_assignment_extend_osmux(msg, ac->osmux_cid);
 	return msg;
diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c
index 94712cc..2108ab4 100644
--- a/src/libmsc/transaction.c
+++ b/src/libmsc/transaction.c
@@ -110,6 +110,66 @@
 	return NULL;
 }
 
+struct osmo_lcls *trans_lcls_compose(const struct gsm_trans *trans, bool use_lac)
+{
+	if (!trans->net->a.sri->sccp)
+		return NULL;
+
+	struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(trans->net->a.sri->sccp);
+	struct osmo_lcls *lcls;
+	uint8_t w = osmo_ss7_pc_width(&ss7->cfg.pc_fmt);
+
+	if (!trans) {
+		LOGP(DCC, LOGL_ERROR, "LCLS: unable to fill parameters for unallocated transaction\n");
+		return NULL;
+	}
+
+	if (!trans->net->lcls_permitted) {
+		LOGP(DCC, LOGL_NOTICE, "LCLS disabled globally\n");
+		return NULL;
+	}
+
+	if (!trans->msc_a) {
+		LOGP(DCC, LOGL_ERROR, "LCLS: unable to fill parameters for transaction without connection\n");
+		return NULL;
+	}
+
+	if (trans->msc_a->c.ran->type != OSMO_RAT_GERAN_A) {
+		LOGP(DCC, LOGL_ERROR, "LCLS: only A interface is supported at the moment\n");
+		return NULL;
+	}
+
+	lcls = talloc_zero(trans, struct osmo_lcls);
+	if (!lcls) {
+		LOGP(DCC, LOGL_ERROR, "LCLS: failed to allocate osmo_lcls\n");
+		return NULL;
+	}
+
+	LOGP(DCC, LOGL_INFO, "LCLS: using %u bits (%u bytes) for node ID\n", w, w / 8);
+
+	lcls->gcr.net_len = 3;
+	lcls->gcr.node = ss7->cfg.primary_pc;
+
+	/* net id from Q.1902.3 3-5 bytes, this function gives 3 bytes exactly */
+	osmo_plmn_to_bcd(lcls->gcr.net, &trans->net->plmn);
+
+	osmo_store32be(trans->callref, lcls->gcr.cr);
+	osmo_store16be(use_lac ? trans->msc_a->via_cell.lai.lac : trans->msc_a->via_cell.cell_identity, lcls->gcr.cr + 3);
+
+	LOGP(DCC, LOGL_INFO, "LCLS: allocated %s-based CR-ID %s\n", use_lac ? "LAC" : "CI",
+	     osmo_hexdump(lcls->gcr.cr, 5));
+
+	lcls->config = GSM0808_LCLS_CFG_BOTH_WAY;
+	lcls->control = GSM0808_LCLS_CSC_CONNECT;
+	lcls->corr_needed = true;
+	lcls->gcr_available = true;
+
+	LOGP(DCC, LOGL_DEBUG, "Filled %s\n", osmo_lcls_dump(lcls));
+	LOGP(DCC, LOGL_DEBUG, "Filled %s\n", osmo_gcr_dump(lcls));
+
+	return lcls;
+}
+
 static const char *trans_vsub_use(enum trans_type type)
 {
 	return get_value_string_or_null(trans_type_names, type) ? : "trans-type-unknown";
diff --git a/src/osmo-msc/msc_main.c b/src/osmo-msc/msc_main.c
index a0f584d..cd91d54 100644
--- a/src/osmo-msc/msc_main.c
+++ b/src/osmo-msc/msc_main.c
@@ -258,6 +258,7 @@
 
 	mgcp_client_conf_init(&net->mgw.conf);
 	net->call_waiting = true;
+	net->lcls_permitted = false;
 
 	net->mgw.tdefs = g_mgw_tdefs;
 	osmo_tdefs_reset(net->mgw.tdefs);