initial working osmo-smlc implementation

The lower level Lb/SCCP interface conn handling is essentially a copy of
OsmoMSC's A/SCCP infrastructure (OsmoMSC also connects to multiple BSCs).

The smlc_subscr is mostly a copy of OsmoBSC's bsc_subscr.

smlc_loc_req FSM is the SMLC side of OsmoBSC's new lcs_loc_req FSM.

cell_locations configures geographic coordinates of individual cells.

Change-Id: I917ba8fc51a1f1150be77ae01e12a7b16a853052
diff --git a/include/osmocom/smlc/Makefile.am b/include/osmocom/smlc/Makefile.am
index 599651d..4933441 100644
--- a/include/osmocom/smlc/Makefile.am
+++ b/include/osmocom/smlc/Makefile.am
@@ -1,4 +1,12 @@
 noinst_HEADERS = \
+	cell_locations.h \
+	debug.h \
+	lb_conn.h \
+	lb_peer.h \
+	sccp_lb_inst.h \
 	smlc_data.h \
+	smlc_loc_req.h \
 	smlc_sigtran.h \
+	smlc_subscr.h \
+	smlc_vty.h \
 	$(NULL)
diff --git a/include/osmocom/smlc/cell_locations.h b/include/osmocom/smlc/cell_locations.h
new file mode 100644
index 0000000..33023bb
--- /dev/null
+++ b/include/osmocom/smlc/cell_locations.h
@@ -0,0 +1,49 @@
+/* OsmoSMLC cell locations configuration */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/sigtran/sccp_sap.h>
+
+struct osmo_gad;
+
+struct cell_location {
+	struct llist_head entry;
+
+	struct gsm0808_cell_id cell_id;
+
+	/*! latitude in micro degrees (degrees * 1e6) */
+	int32_t lat;
+	/*! longitude in micro degrees (degrees * 1e6) */
+	int32_t lon;
+};
+
+int cell_location_from_ta(struct osmo_gad *location_estimate,
+			  const struct gsm0808_cell_id *cell_id,
+			  uint8_t ta);
+
+int cell_locations_vty_init();
diff --git a/include/osmocom/smlc/debug.h b/include/osmocom/smlc/debug.h
new file mode 100644
index 0000000..0c64323
--- /dev/null
+++ b/include/osmocom/smlc/debug.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#define DEBUG
+#include <osmocom/core/logging.h>
+
+/* Debug Areas of the code */
+enum {
+	DSMLC,
+	DREF,
+	DLB,
+	DLCS,
+	Debug_LastEntry,
+};
diff --git a/include/osmocom/smlc/lb_conn.h b/include/osmocom/smlc/lb_conn.h
new file mode 100644
index 0000000..5640780
--- /dev/null
+++ b/include/osmocom/smlc/lb_conn.h
@@ -0,0 +1,55 @@
+#pragma once
+/* SMLC Lb connection implementation */
+
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/smlc/smlc_subscr.h>
+
+struct lb_peer;
+struct osmo_fsm_inst;
+struct msgb;
+struct bssmap_le_pdu;
+
+#define LOG_LB_CONN_SL(CONN, CAT, LEVEL, file, line, FMT, args...) \
+	LOGPSRC(CAT, LEVEL, file, line, "Lb-%d %s %s: " FMT, (CONN) ? (CONN)->sccp_conn_id : 0, \
+	     ((CONN) && (CONN)->smlc_subscr) ? smlc_subscr_to_str_c(OTC_SELECT, (CONN)->smlc_subscr) : "no-subscr", \
+	     (CONN) ? osmo_use_count_to_str_c(OTC_SELECT, &(CONN)->use_count) : "-", \
+	     ##args)
+
+#define LOG_LB_CONN_S(CONN, CAT, LEVEL, FMT, args...) \
+	LOG_LB_CONN_SL(CONN, CAT, LEVEL, NULL, 0, FMT, ##args)
+
+#define LOG_LB_CONN(CONN, LEVEL, FMT, args...) \
+	LOG_LB_CONN_S(CONN, DLB, LEVEL, FMT, ##args)
+
+#define SMLC_SUBSCR_USE_LB_CONN "Lb-conn"
+
+struct lb_conn {
+	struct llist_head entry;
+	struct osmo_use_count use_count;
+
+	struct lb_peer *lb_peer;
+	uint32_t sccp_conn_id;
+
+	bool closing;
+
+	struct smlc_subscr *smlc_subscr;
+	struct smlc_loc_req *smlc_loc_req;
+};
+
+#define lb_conn_get(lb_conn, use) \
+	OSMO_ASSERT(osmo_use_count_get_put(&(lb_conn)->use_count, use, 1) == 0)
+#define lb_conn_put(lb_conn, use) \
+	OSMO_ASSERT(osmo_use_count_get_put(&(lb_conn)->use_count, use, -1) == 0)
+
+struct lb_conn *lb_conn_create_incoming(struct lb_peer *lb_peer, uint32_t sccp_conn_id, const char *use_token);
+struct lb_conn *lb_conn_create_outgoing(struct lb_peer *lb_peer, const char *use_token);
+struct lb_conn *lb_conn_find_by_smlc_subscr(struct smlc_subscr *smlc_subscr, const char *use_token);
+
+void lb_conn_msc_role_gone(struct lb_conn *lb_conn, struct osmo_fsm_inst *msc_role);
+void lb_conn_close(struct lb_conn *lb_conn);
+void lb_conn_discard(struct lb_conn *lb_conn);
+
+int lb_conn_rx(struct lb_conn *lb_conn, struct msgb *msg, bool initial);
+int lb_conn_send_bssmap_le(struct lb_conn *lb_conn, const struct bssmap_le_pdu *bssmap_le);
diff --git a/include/osmocom/smlc/lb_peer.h b/include/osmocom/smlc/lb_peer.h
new file mode 100644
index 0000000..b5ac6d6
--- /dev/null
+++ b/include/osmocom/smlc/lb_peer.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/sigtran/sccp_sap.h>
+
+#include <osmocom/smlc/debug.h>
+#include <osmocom/smlc/lb_conn.h>
+
+struct vlr_subscr;
+struct lb_conn;
+struct neighbor_ident_entry;
+
+#define LOG_LB_PEER_CAT(LB_PEER, subsys, loglevel, fmt, args ...) \
+	LOGPFSMSL((LB_PEER)? (LB_PEER)->fi : NULL, subsys, loglevel, fmt, ## args)
+
+#define LOG_LB_PEER(LB_PEER, loglevel, fmt, args ...) \
+	LOG_LB_PEER_CAT(LB_PEER, DLB, loglevel, fmt, ## args)
+
+struct lb_peer {
+	struct llist_head entry;
+	struct osmo_fsm_inst *fi;
+
+	struct sccp_lb_inst *sli;
+	struct osmo_sccp_addr peer_addr;
+};
+
+#define lb_peer_for_each_lb_conn(LB_CONN, LB_PEER) \
+	llist_for_each_entry(LB_CONN, &(LB_PEER)->sli->lb_conns, entry) \
+		if ((LB_CONN)->lb_peer == (LB_PEER))
+
+#define lb_peer_for_each_lb_conn_safe(LB_CONN, LB_CONN_NEXT, LB_PEER) \
+	llist_for_each_entry_safe(LB_CONN, LB_CONN_NEXT, &(LB_PEER)->sli->lb_conns, entry) \
+		if ((LB_CONN)->lb_peer == (LB_PEER))
+
+enum lb_peer_state {
+	LB_PEER_ST_WAIT_RX_RESET = 0,
+	LB_PEER_ST_WAIT_RX_RESET_ACK,
+	LB_PEER_ST_READY,
+	LB_PEER_ST_DISCARDING,
+};
+
+enum lb_peer_event {
+	LB_PEER_EV_MSG_UP_CL = 0,
+	LB_PEER_EV_MSG_UP_CO_INITIAL,
+	LB_PEER_EV_MSG_UP_CO,
+	LB_PEER_EV_MSG_DOWN_CL,
+	LB_PEER_EV_MSG_DOWN_CO_INITIAL,
+	LB_PEER_EV_MSG_DOWN_CO,
+	LB_PEER_EV_RX_RESET,
+	LB_PEER_EV_RX_RESET_ACK,
+	LB_PEER_EV_CONNECTION_SUCCESS,
+	LB_PEER_EV_CONNECTION_TIMEOUT,
+};
+
+struct lb_peer_ev_ctx {
+	uint32_t conn_id;
+	struct lb_conn *lb_conn;
+	struct msgb *msg;
+};
+
+struct lb_peer *lb_peer_find_or_create(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *peer_addr);
+struct lb_peer *lb_peer_find(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *peer_addr);
+
+int lb_peer_up_l2(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *calling_addr, bool co, uint32_t conn_id,
+		  struct msgb *l2);
+void lb_peer_disconnect(struct sccp_lb_inst *sli, uint32_t conn_id);
diff --git a/include/osmocom/smlc/sccp_lb_inst.h b/include/osmocom/smlc/sccp_lb_inst.h
new file mode 100644
index 0000000..525eac0
--- /dev/null
+++ b/include/osmocom/smlc/sccp_lb_inst.h
@@ -0,0 +1,63 @@
+/* Lb: BSSAP-LE/SCCP */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include <osmocom/sigtran/sccp_sap.h>
+
+struct msgb;
+struct sccp_lb_inst;
+
+#define LOG_SCCP_LB_CO(sli, peer_addr, conn_id, level, fmt, args...) \
+	LOGP(DLB, level, "(Lb-%u%s%s) " fmt, \
+	     conn_id, peer_addr ? " from " : "", \
+	     peer_addr ? osmo_sccp_inst_addr_name((sli)->sccp, peer_addr) : "", \
+	     ## args)
+
+#define LOG_SCCP_LB_CL_CAT(sli, peer_addr, subsys, level, fmt, args...) \
+	LOGP(subsys, level, "(Lb%s%s) " fmt, \
+	     peer_addr ? " from " : "", \
+	     peer_addr ? osmo_sccp_inst_addr_name((sli)->sccp, peer_addr) : "", \
+	     ## args)
+
+#define LOG_SCCP_LB_CL(sli, peer_addr, level, fmt, args...) \
+	LOG_SCCP_LB_CL_CAT(sli, peer_addr, DLB, level, fmt, ##args)
+
+#define LOG_SCCP_LB_CAT(sli, subsys, level, fmt, args...) \
+	LOG_SCCP_LB_CL_CAT(sli, NULL, subsys, level, fmt, ##args)
+
+#define LOG_SCCP_LB(sli, level, fmt, args...) \
+	LOG_SCCP_LB_CL(sli, NULL, level, fmt, ##args)
+
+enum reset_msg_type {
+	SCCP_LB_MSG_NON_RESET = 0,
+	SCCP_LB_MSG_RESET,
+	SCCP_LB_MSG_RESET_ACK,
+};
+
+struct sccp_lb_inst {
+	struct osmo_sccp_instance *sccp;
+	struct osmo_sccp_user *scu;
+	struct osmo_sccp_addr local_sccp_addr;
+
+	struct llist_head lb_peers;
+	struct llist_head lb_conns;
+
+	void *user_data;
+};
+
+struct sccp_lb_inst *sccp_lb_init(void *talloc_ctx, struct osmo_sccp_instance *sccp, enum osmo_sccp_ssn ssn,
+				  const char *sccp_user_name);
+int sccp_lb_inst_next_conn_id();
+
+int sccp_lb_down_l2_co_initial(struct sccp_lb_inst *sli,
+			       const struct osmo_sccp_addr *called_addr,
+			       uint32_t conn_id, struct msgb *l2);
+int sccp_lb_down_l2_co(struct sccp_lb_inst *sli, uint32_t conn_id, struct msgb *l2);
+int sccp_lb_down_l2_cl(struct sccp_lb_inst *sli, const struct osmo_sccp_addr *called_addr, struct msgb *l2);
+
+int sccp_lb_disconnect(struct sccp_lb_inst *sli, uint32_t conn_id, uint32_t cause);
diff --git a/include/osmocom/smlc/smlc_data.h b/include/osmocom/smlc/smlc_data.h
index 2506fdc..dc77507 100644
--- a/include/osmocom/smlc/smlc_data.h
+++ b/include/osmocom/smlc/smlc_data.h
@@ -8,25 +8,26 @@
 
 #include <osmocom/ctrl/control_if.h>
 
-#include <osmocom/sigtran/sccp_sap.h>
+struct osmo_sccp_instance;
+struct sccp_lb_inst;
 
 struct smlc_state {
-	struct osmo_sccp_user *sccp_user;
+	struct osmo_sccp_instance *sccp_inst;
+	struct sccp_lb_inst *lb;
+
 	struct ctrl_handle *ctrl;
 
 	struct rate_ctr_group *ctrs;
 	struct osmo_stat_item_group *statg;
-	struct osmo_tdef *T_defs;
+
+	struct llist_head subscribers;
+	struct llist_head cell_locations;
 };
 
 extern struct smlc_state *g_smlc;
+struct smlc_state *smlc_state_alloc(void *ctx);
 
-
-enum {
-	DSMLC,
-	DLB,		/* Lb interface */
-};
-
+extern struct osmo_tdef g_smlc_tdefs[];
 
 int smlc_ctrl_node_lookup(void *data, vector vline, int *node_type,
 			  void **node_data, int *i);
@@ -35,3 +36,25 @@
 	CTRL_NODE_SMLC = _LAST_CTRL_NODE,
 	_LAST_CTRL_NODE_SMLC
 };
+
+enum {
+	SMLC_CTR_BSSMAP_LE_RX_UDT_RESET,
+	SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK,
+	SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG,
+	SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG,
+	SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_REQUEST,
+	SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_RESPONSE,
+	SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_REJECT,
+	SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_RESET,
+	SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_ABORT,
+
+	SMLC_CTR_BSSMAP_LE_TX_ERR_INVALID_MSG,
+	SMLC_CTR_BSSMAP_LE_TX_ERR_CONN_NOT_READY,
+	SMLC_CTR_BSSMAP_LE_TX_ERR_SEND,
+	SMLC_CTR_BSSMAP_LE_TX_SUCCESS,
+
+	SMLC_CTR_BSSMAP_LE_TX_UDT_RESET,
+	SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK,
+	SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_RESPONSE,
+	SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_REQUEST,
+};
diff --git a/include/osmocom/smlc/smlc_loc_req.h b/include/osmocom/smlc/smlc_loc_req.h
new file mode 100644
index 0000000..a8aa27e
--- /dev/null
+++ b/include/osmocom/smlc/smlc_loc_req.h
@@ -0,0 +1,65 @@
+/* Handle LCS BSSMAP-LE Perform Location Request */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#pragma once
+
+#include <osmocom/smlc/debug.h>
+#include <osmocom/gsm/bssmap_le.h>
+
+#define LOG_SMLC_LOC_REQ(LOC_REQ, level, fmt, args...) do { \
+		if (LOC_REQ) \
+			LOGPFSML(LOC_REQ->fi, level, fmt, ## args); \
+		else \
+			LOGP(DLCS, level, "LCS Perf Loc Req: " fmt, ## args); \
+	} while(0)
+
+struct smlc_ta_req;
+struct lb_conn;
+struct msgb;
+
+#define LB_CONN_USE_SMLC_LOC_REQ "smlc_loc_req"
+
+enum smlc_loc_req_fsm_event {
+	SMLC_LOC_REQ_EV_RX_TA_RESPONSE,
+	SMLC_LOC_REQ_EV_RX_BSSLAP_RESET,
+	SMLC_LOC_REQ_EV_RX_LE_PERFORM_LOCATION_ABORT,
+};
+
+struct smlc_loc_req {
+	struct osmo_fsm_inst *fi;
+
+	struct smlc_subscr *smlc_subscr;
+	struct lb_conn *lb_conn;
+
+	struct bssmap_le_perform_loc_req req;
+
+	bool ta_present;
+	uint8_t ta;
+
+	struct gsm0808_cell_id latest_cell_id;
+
+	struct lcs_cause_ie lcs_cause;
+};
+
+int smlc_loc_req_rx_bssap_le(struct lb_conn *conn, const struct bssap_le_pdu *bssap_le);
diff --git a/include/osmocom/smlc/smlc_sigtran.h b/include/osmocom/smlc/smlc_sigtran.h
index aeaf2c5..f89f284 100644
--- a/include/osmocom/smlc/smlc_sigtran.h
+++ b/include/osmocom/smlc/smlc_sigtran.h
@@ -1,3 +1,5 @@
 #pragma once
 
 int smlc_sigtran_init(void);
+int smlc_sigtran_send(uint32_t sccp_conn_id, struct msgb *msg);
+int smlc_sigtran_send_udt(uint32_t sccp_conn_id, struct msgb *msg);
diff --git a/include/osmocom/smlc/smlc_subscr.h b/include/osmocom/smlc/smlc_subscr.h
new file mode 100644
index 0000000..fbc4103
--- /dev/null
+++ b/include/osmocom/smlc/smlc_subscr.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/use_count.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0808.h>
+
+struct smlc_subscr {
+	struct llist_head entry;
+	struct osmo_use_count use_count;
+
+	struct osmo_mobile_identity imsi;
+	struct gsm0808_cell_id cell_id;
+
+	struct osmo_fsm_inst *loc_req;
+};
+
+struct smlc_subscr *smlc_subscr_find_or_create(const struct osmo_mobile_identity *imsi, const char *use_token);
+struct smlc_subscr *smlc_subscr_find(const struct osmo_mobile_identity *imsi, const char *use_token);
+
+int smlc_subscr_to_str_buf(char *buf, size_t buf_len, const struct smlc_subscr *smlc_subscr);
+char *smlc_subscr_to_str_c(void *ctx, const struct smlc_subscr *smlc_subscr);
+
+struct smlc_subscr *smlc_subscr_find_or_create(const struct osmo_mobile_identity *imsi, const char *use_token);
+
+#define smlc_subscr_get(smlc_subscr, use) \
+	OSMO_ASSERT(osmo_use_count_get_put(&(smlc_subscr)->use_count, use, 1) == 0)
+#define smlc_subscr_put(smlc_subscr, use) \
+	OSMO_ASSERT(osmo_use_count_get_put(&(smlc_subscr)->use_count, use, -1) == 0)
diff --git a/include/osmocom/smlc/smlc_vty.h b/include/osmocom/smlc/smlc_vty.h
new file mode 100644
index 0000000..d5d82f8
--- /dev/null
+++ b/include/osmocom/smlc/smlc_vty.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <osmocom/vty/command.h>
+
+enum smlc_vty_node {
+	CELLS_NODE = _LAST_OSMOVTY_NODE + 1,
+};