add libosmo-mslookup abstract client

mslookup is a key concept in Distributed GSM, which allows querying the current
location of a subscriber in a number of cooperating but independent core
network sites, by arbitrary service names and by MSISDN/IMSI.

Add the abstract mslookup client library. An actual lookup method (besides
mslookup_client_fake.c) is added in a subsequent patch.

For a detailed overview of this and upcoming patches, please see the elaborate
comment at the top of mslookup.c.

Add as separate library, libosmo-mslookup, to allow adding D-GSM capability to
arbitrary client programs.

osmo-hlr will be the only mslookup server implementation, added in a subsequent
patch.

osmo-hlr itself will also use this library and act as an mslookup client, when
requesting the home HLR for locally unknown IMSIs.

Related: OS#4237
Patch-by: osmith, nhofmeyr
Change-Id: I83487ab8aad1611eb02e997dafbcb8344da13df1
diff --git a/src/mslookup/mslookup_client_fake.c b/src/mslookup/mslookup_client_fake.c
new file mode 100644
index 0000000..cae73f2
--- /dev/null
+++ b/src/mslookup/mslookup_client_fake.c
@@ -0,0 +1,156 @@
+/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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 <osmocom/hlr/logging.h>
+#include <osmocom/mslookup/mslookup_client.h>
+#include <osmocom/mslookup/mslookup_client_fake.h>
+
+#include <string.h>
+
+/* Fake mslookup method */
+
+struct fake_lookup_state {
+	struct osmo_mslookup_client *client;
+	struct llist_head requests;
+	struct osmo_timer_list async_response_timer;
+	struct osmo_mslookup_fake_response *responses;
+	size_t responses_len;
+};
+
+struct fake_lookup_request {
+	struct llist_head entry;
+	uint32_t request_handle;
+	struct osmo_mslookup_query query;
+	struct timeval received_at;
+};
+
+/*! Args for osmo_timer_schedule: seconds and microseconds. */
+#define ASYNC_RESPONSE_PERIOD 0, (1e6 / 10)
+static void fake_lookup_async_response(void *state);
+
+static void fake_lookup_request(struct osmo_mslookup_client_method *method,
+				const struct osmo_mslookup_query *query,
+				uint32_t request_handle)
+{
+	struct fake_lookup_state *state = method->priv;
+	char buf[256];
+	LOGP(DMSLOOKUP, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
+
+	/* A real implementation would send packets to some remote server.
+	 * Here this is simulated: add to the list of requests, which fake_lookup_async_response() will reply upon
+	 * according to the test data listing the replies that the test wants to generate. */
+
+	struct fake_lookup_request *r = talloc_zero(method->client, struct fake_lookup_request);
+	*r = (struct fake_lookup_request){
+		.request_handle = request_handle,
+		.query = *query,
+	};
+	osmo_gettimeofday(&r->received_at, NULL);
+	llist_add_tail(&r->entry, &state->requests);
+}
+
+static void fake_lookup_request_cleanup(struct osmo_mslookup_client_method *method,
+					uint32_t request_handle)
+{
+	struct fake_lookup_state *state = method->priv;
+
+	/* Tear down any state associated with this handle. */
+	struct fake_lookup_request *r;
+	llist_for_each_entry(r, &state->requests, entry) {
+		if (r->request_handle != request_handle)
+			continue;
+		llist_del(&r->entry);
+		talloc_free(r);
+		LOGP(DMSLOOKUP, LOGL_DEBUG, "%s() ok\n", __func__);
+		return;
+	}
+	LOGP(DMSLOOKUP, LOGL_DEBUG, "%s() FAILED\n", __func__);
+}
+
+static void fake_lookup_async_response(void *data)
+{
+	struct fake_lookup_state *state = data;
+	struct fake_lookup_request *req, *n;
+	struct timeval now;
+	char str[256];
+
+	osmo_gettimeofday(&now, NULL);
+
+	llist_for_each_entry_safe(req, n, &state->requests, entry) {
+		struct osmo_mslookup_fake_response *resp;
+
+		for (resp = state->responses;
+		     (resp - state->responses) < state->responses_len;
+		     resp++) {
+			struct timeval diff;
+
+			if (resp->sent)
+				continue;
+			if (osmo_mslookup_id_cmp(&req->query.id, &resp->for_id) != 0)
+				continue;
+			if (strcmp(req->query.service, resp->for_service) != 0)
+				continue;
+
+			timersub(&now, &req->received_at, &diff);
+			if (timercmp(&diff, &resp->time_to_reply, <))
+				continue;
+
+			/* It's time to reply to this request. */
+			LOGP(DMSLOOKUP, LOGL_DEBUG, "osmo_mslookup_client_rx_result(): %s\n",
+			     osmo_mslookup_result_name_b(str, sizeof(str), &req->query, &resp->result));
+			osmo_mslookup_client_rx_result(state->client, req->request_handle, &resp->result);
+			resp->sent = true;
+
+			/* The req will have been cleaned up now, so we must not iterate over state->responses anymore
+			 * with this req. */
+			break;
+		}
+	}
+
+	osmo_timer_schedule(&state->async_response_timer, ASYNC_RESPONSE_PERIOD);
+}
+
+struct osmo_mslookup_client_method *osmo_mslookup_client_add_fake(struct osmo_mslookup_client *client,
+								  struct osmo_mslookup_fake_response *responses,
+								  size_t responses_len)
+{
+	struct osmo_mslookup_client_method *method = talloc_zero(client, struct osmo_mslookup_client_method);
+	OSMO_ASSERT(method);
+
+	struct fake_lookup_state *state = talloc_zero(method, struct fake_lookup_state);
+	OSMO_ASSERT(state);
+	*state = (struct fake_lookup_state){
+		.client = client,
+		.responses = responses,
+		.responses_len = responses_len,
+	};
+	INIT_LLIST_HEAD(&state->requests);
+
+	*method = (struct osmo_mslookup_client_method){
+		.name = "fake",
+		.priv = state,
+		.request = fake_lookup_request,
+		.request_cleanup = fake_lookup_request_cleanup,
+	};
+
+	osmo_timer_setup(&state->async_response_timer, fake_lookup_async_response, state);
+	osmo_mslookup_client_method_add(client, method);
+
+	osmo_timer_schedule(&state->async_response_timer, ASYNC_RESPONSE_PERIOD);
+	return method;
+}