osmo-mgw: refactor endpoint and trunk handling

The trunk and endpoint handling in osmo-mgw is still very complex and
implemented in various places (mostly mgcp_protocol.c). Also we use
still integers for endpoint identification, which is not flexible enough
to address timeslots/subslots on an E1 trunk. Some refactoring is needed.

  - get rid of integers as endpoint identifiers, use strings instead and
    find the endpoint based on its string name on the trunk.

  - identify the trunk based on the trunk prefix given in the endpoint
    name.

  - refactor trunk and endpoint allocation. Aggregate functionality in
    in mgcp_endp.c and mgcp_trunk.c. Also remove non-reusable code that
    relates to the still exisiting, but unfinished E1 trunk support.

  - refactor rate counters, put them into a separate module and do no
    longer allocate them per trunk. Allocate them globally instead.

Change-Id: Ia8cf4d6caf05a4e13f1f507dc68cbabb7e6239aa
Related: OS#2659
diff --git a/src/libosmo-mgcp/mgcp_endp.c b/src/libosmo-mgcp/mgcp_endp.c
index eec46bf..6c78de2 100644
--- a/src/libosmo-mgcp/mgcp_endp.c
+++ b/src/libosmo-mgcp/mgcp_endp.c
@@ -1,7 +1,7 @@
 /* Endpoint types */
 
 /*
- * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * (C) 2017-2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
  * All Rights Reserved
  *
  * Author: Philipp Maier
@@ -23,6 +23,7 @@
 
 #include <osmocom/mgcp/mgcp_internal.h>
 #include <osmocom/mgcp/mgcp_endp.h>
+#include <osmocom/mgcp/mgcp_trunk.h>
 
 /* Endpoint typeset definition */
 const struct mgcp_endpoint_typeset ep_typeset = {
@@ -32,6 +33,39 @@
 	.rtp.cleanup_cb = mgcp_cleanup_rtp_bridge_cb
 };
 
+/*! allocate an endpoint and set default values.
+ *  \param[in] trunk configuration.
+ *  \param[in] name endpoint name.
+ *  \returns endpoint on success, NULL on failure. */
+struct mgcp_endpoint *mgcp_endp_alloc(struct mgcp_trunk *trunk, char *name)
+{
+	struct mgcp_endpoint *endp;
+
+	endp = talloc_zero(trunk->endpoints, struct mgcp_endpoint);
+	if (!endp)
+		return NULL;
+
+	INIT_LLIST_HEAD(&endp->conns);
+	endp->cfg = trunk->cfg;
+	endp->trunk = trunk;
+	endp->name = talloc_strdup(endp, name);
+
+	switch (trunk->trunk_type) {
+	case MGCP_TRUNK_VIRTUAL:
+		endp->type = &ep_typeset.rtp;
+		break;
+	case MGCP_TRUNK_E1:
+		/* FIXME: Implement E1 allocation */
+		LOGP(DLMGCP, LOGL_FATAL, "E1 trunks not implemented!\n");
+		break;
+	default:
+		osmo_panic("Cannot allocate unimplemented trunk type %d! %s:%d\n",
+			   trunk->trunk_type, __FILE__, __LINE__);
+	}
+
+	return endp;
+}
+
 /*! release endpoint, all open connections are closed.
  *  \param[in] endp endpoint to release */
 void mgcp_endp_release(struct mgcp_endpoint *endp)
@@ -53,3 +87,223 @@
 	endp->local_options.codec = NULL;
 	endp->wildcarded_req = false;
 }
+
+/* Check if the endpoint name contains the prefix (e.g. "rtpbridge/" or
+ * "ds/e1-") and write the epname without the prefix back to the memory
+ * pointed at by epname. (per trunk the prefix is the same for all endpoints,
+ * so no ambiguity is introduced) */
+static void chop_epname_prefix(char *epname, const struct mgcp_trunk *trunk)
+{
+	size_t prefix_len;
+	switch (trunk->trunk_type) {
+	case MGCP_TRUNK_VIRTUAL:
+		prefix_len = sizeof(MGCP_ENDPOINT_PREFIX_VIRTUAL_TRUNK) - 1;
+		if (strncmp
+		    (epname, MGCP_ENDPOINT_PREFIX_VIRTUAL_TRUNK,
+		     prefix_len) == 0)
+			memmove(epname, epname + prefix_len,
+				strlen(epname) - prefix_len + 1);
+		return;
+	case MGCP_TRUNK_E1:
+		prefix_len = sizeof(MGCP_ENDPOINT_PREFIX_E1_TRUNK) - 1;
+		if (strncmp
+		    (epname, MGCP_ENDPOINT_PREFIX_VIRTUAL_TRUNK,
+		     prefix_len) == 0)
+			memmove(epname, epname + prefix_len,
+				strlen(epname) - prefix_len + 1);
+		return;
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+/* Check if the endpoint name contains a suffix (e.g. "@mgw") and truncate
+ * epname by writing a '\0' char where the suffix starts. */
+static void chop_epname_suffix(char *epname, const struct mgcp_trunk *trunk)
+{
+	char *suffix_begin;
+
+	/* Endpoints on the virtual trunk may have a domain name that is
+	 * followed after an @ character, this can be chopped off. All
+	 * other supported trunk types do not have any suffixes that may
+	 * be chopped off */
+	if (trunk->trunk_type == MGCP_TRUNK_VIRTUAL) {
+		suffix_begin = strchr(epname, '@');
+		if (!suffix_begin)
+			return;
+		*suffix_begin = '\0';
+	}
+}
+
+/* Convert all characters in epname to lowercase and strip trunk prefix and
+ * endpoint name suffix (domain name) from epname. The result is written to
+ * to the memory pointed at by epname_stripped. The expected size of the
+ * result is either equal or lower then the length of the input string
+ * (epname) */
+static void strip_epname(char *epname_stripped, const char *epname,
+			 const struct mgcp_trunk *trunk)
+{
+	osmo_str_tolower_buf(epname_stripped, MGCP_ENDPOINT_MAXLEN, epname);
+	chop_epname_prefix(epname_stripped, trunk);
+	chop_epname_suffix(epname_stripped, trunk);
+}
+
+/* Go through the trunk and find a random free (no active calls) endpoint,
+ * this function is called when a wildcarded request is carried out, which
+ * means that it is up to the MGW to choose a random free endpoint. */
+static struct mgcp_endpoint *find_free_endpoint(const struct mgcp_trunk *trunk)
+{
+	struct mgcp_endpoint *endp;
+	unsigned int i;
+
+	for (i = 0; i < trunk->number_endpoints; i++) {
+		endp = trunk->endpoints[i];
+		if (endp->callid == NULL)
+			return endp;
+	}
+
+	return NULL;
+}
+
+/* Find an endpoint specified by its name. If the endpoint can not be found,
+ * return NULL */
+static struct mgcp_endpoint *find_specific_endpoint(const char *epname,
+						    const struct mgcp_trunk *trunk)
+{
+	char epname_stripped[MGCP_ENDPOINT_MAXLEN];
+	char epname_stripped_endp[MGCP_ENDPOINT_MAXLEN];
+	struct mgcp_endpoint *endp;
+	unsigned int i;
+
+	/* Strip irrelevant information from the endpoint name */
+	strip_epname(epname_stripped, epname, trunk);
+
+	for (i = 0; i < trunk->number_endpoints; i++) {
+		endp = trunk->endpoints[i];
+		strip_epname(epname_stripped_endp, endp->name, trunk);
+		if (strcmp(epname_stripped_endp, epname_stripped) == 0)
+			return endp;
+	}
+
+	return NULL;
+}
+
+/*! Find an endpoint by its name on a specified trunk.
+ *  \param[out] cause pointer to store cause code, can be NULL.
+ *  \param[in] epname endpoint name to lookup.
+ *  \param[in] trunk where the endpoint is located.
+ *  \returns endpoint or NULL if endpoint was not found. */
+struct mgcp_endpoint *mgcp_endp_by_name_trunk(int *cause, const char *epname,
+					      const struct mgcp_trunk *trunk)
+{
+	struct mgcp_endpoint *endp;
+
+	if (cause)
+		*cause = 0;
+
+	/* At the moment we only support a primitive ('*'-only) method of
+	 * wildcarded endpoint searches that picks the next free endpoint on
+	 * a trunk. */
+	if (strstr(epname, "*")) {
+		endp = find_free_endpoint(trunk);
+		if (endp) {
+			LOGPENDP(endp, DLMGCP, LOGL_DEBUG,
+				 "(trunk:%d) found free endpoint: %s\n",
+				 trunk->trunk_nr, endp->name);
+			endp->wildcarded_req = true;
+			return endp;
+		}
+
+		LOGP(DLMGCP, LOGL_ERROR,
+		     "(trunk:%d) Not able to find a free endpoint\n",
+		     trunk->trunk_nr);
+		if (cause)
+			*cause = -403;
+		return NULL;
+	}
+
+	/* Find an endpoint by its name (if wildcarded request is not
+	 * applicable) */
+	endp = find_specific_endpoint(epname, trunk);
+	if (endp) {
+		LOGPENDP(endp, DLMGCP, LOGL_DEBUG,
+			 "(trunk:%d) found endpoint: %s\n",
+			 trunk->trunk_nr, endp->name);
+		endp->wildcarded_req = false;
+		return endp;
+	}
+
+	LOGP(DLMGCP, LOGL_ERROR,
+	     "(trunk:%d) Not able to find specified endpoint: %s\n",
+	     trunk->trunk_nr, epname);
+	if (cause)
+		*cause = -500;
+
+	return NULL;
+}
+
+/* Check if the domain name, which is supplied with the endpoint name
+ * matches the configuration. */
+static int check_domain_name(const char *epname, struct mgcp_config *cfg)
+{
+	char *domain_to_check;
+
+	domain_to_check = strstr(epname, "@");
+	if (!domain_to_check) {
+		LOGP(DLMGCP, LOGL_ERROR, "missing domain name in endpoint name \"%s\", expecting \"%s\"\n",
+		     epname, cfg->domain);
+		return -EINVAL;
+	}
+
+	/* Accept any domain if configured as "*" */
+	if (!strcmp(cfg->domain, "*"))
+		return 0;
+
+	if (strcmp(domain_to_check+1, cfg->domain) != 0) {
+		LOGP(DLMGCP, LOGL_ERROR, "wrong domain name in endpoint name \"%s\", expecting \"%s\"\n",
+		     epname, cfg->domain);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*! Find an endpoint by its name, search at all trunks.
+ *  \param[out] cause, pointer to store cause code, can be NULL.
+ *  \param[in] epname, must contain trunk prefix.
+ *  \param[in] cfg, mgcp configuration (trunks).
+ *  \returns endpoint or NULL if endpoint was not found. */
+struct mgcp_endpoint *mgcp_endp_by_name(int *cause, const char *epname,
+					struct mgcp_config *cfg)
+{
+	struct mgcp_trunk *trunk;
+	struct mgcp_endpoint *endp;
+	char epname_lc[MGCP_ENDPOINT_MAXLEN];
+
+	osmo_str_tolower_buf(epname_lc, sizeof(epname_lc), epname);
+	epname = epname_lc;
+
+	if (cause)
+		*cause = -500;
+
+	/* Identify the trunk where the endpoint is located */
+	trunk = mgcp_trunk_by_name(cfg, epname);
+	if (!trunk)
+		return NULL;
+
+	/* Virtual endpoints require a domain name (see RFC3435, section E.3) */
+	  if (trunk->trunk_type == MGCP_TRUNK_VIRTUAL) {
+		  if (check_domain_name(epname, cfg))
+			return NULL;
+	}
+
+	/* Identify the endpoint on the trunk */
+        endp = mgcp_endp_by_name_trunk(cause, epname, trunk);
+	if (!endp) {
+		return NULL;
+	}
+
+	if (cause)
+		*cause = 0;
+	return endp;
+}