blob: ce4629657f7cb93668544277679ef3195889f8fa [file] [log] [blame]
Oliver Smithbf7deda2019-11-20 10:56:35 +01001/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
2 *
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <osmocom/hlr/logging.h>
20#include <osmocom/mslookup/mslookup_client.h>
21
22/*! Lookup client's internal data for a query. */
23struct osmo_mslookup_client {
24 struct llist_head lookup_methods;
25 struct llist_head requests;
26 uint32_t next_request_handle;
27};
28
29/*! Lookup client's internal data for a query.
30 * The request methods only get to see the query part, and result handling is done commonly for all request methods. */
31struct osmo_mslookup_client_request {
32 struct llist_head entry;
33 struct osmo_mslookup_client *client;
34 uint32_t request_handle;
35
36 struct osmo_mslookup_query query;
37 struct osmo_mslookup_query_handling handling;
38 struct osmo_timer_list timeout;
39 bool waiting_min_delay;
40
41 struct osmo_mslookup_result result;
42};
43
44static struct osmo_mslookup_client_request *get_request(struct osmo_mslookup_client *client, uint32_t request_handle)
45{
46 struct osmo_mslookup_client_request *r;
47 if (!request_handle)
48 return NULL;
49 llist_for_each_entry(r, &client->requests, entry) {
50 if (r->request_handle == request_handle)
51 return r;
52 }
53 return NULL;
54}
55
56struct osmo_mslookup_client *osmo_mslookup_client_new(void *ctx)
57{
58 struct osmo_mslookup_client *client = talloc_zero(ctx, struct osmo_mslookup_client);
59 OSMO_ASSERT(client);
60 INIT_LLIST_HEAD(&client->lookup_methods);
61 INIT_LLIST_HEAD(&client->requests);
62 return client;
63}
64
65/*! Return whether any lookup methods are available.
66 * \param[in] client Client to query.
67 * \return true when a client is present that has at least one osmo_mslookup_client_method registered.
68 */
69bool osmo_mslookup_client_active(struct osmo_mslookup_client *client)
70{
71 if (!client)
72 return false;
73 if (llist_empty(&client->lookup_methods))
74 return false;
75 return true;
76}
77
78static void _osmo_mslookup_client_method_del(struct osmo_mslookup_client_method *method)
79{
80 if (method->destruct)
81 method->destruct(method);
82 llist_del(&method->entry);
83 talloc_free(method);
84}
85
86/*! Stop and free mslookup client and all registered lookup methods.
87 */
88void osmo_mslookup_client_free(struct osmo_mslookup_client *client)
89{
90 struct osmo_mslookup_client_method *m, *n;
91 if (!client)
92 return;
93 llist_for_each_entry_safe(m, n, &client->lookup_methods, entry) {
94 _osmo_mslookup_client_method_del(m);
95 }
96 talloc_free(client);
97}
98
99/*! Add an osmo_mslookup_client_method to service MS Lookup requests.
100 * Note, osmo_mslookup_client_method_del() will talloc_free() the method pointer, so it needs to be dynamically
101 * allocated.
102 * \param client The osmo_mslookup_client instance to add to.
103 * \param method A fully initialized method struct, allocated by talloc.
104 */
105void osmo_mslookup_client_method_add(struct osmo_mslookup_client *client,
106 struct osmo_mslookup_client_method *method)
107{
108 method->client = client;
109 llist_add_tail(&method->entry, &client->lookup_methods);
110}
111
112/*! \return false if the method was not listed, true if the method was listed, removed and talloc_free()d.
113 */
114bool osmo_mslookup_client_method_del(struct osmo_mslookup_client *client,
115 struct osmo_mslookup_client_method *method)
116{
117 struct osmo_mslookup_client_method *m;
118 llist_for_each_entry(m, &client->lookup_methods, entry) {
119 if (m == method) {
120 _osmo_mslookup_client_method_del(method);
121 return true;
122 }
123 }
124 return false;
125}
126
127static void osmo_mslookup_request_send_result(struct osmo_mslookup_client_request *r, bool finish)
128{
129 struct osmo_mslookup_client *client = r->client;
130 uint32_t request_handle = r->request_handle;
131
132 r->result.last = finish;
133 r->handling.result_cb(r->client, r->request_handle, &r->query, &r->result);
134
135 /* Make sure the request struct is discarded.
136 * The result_cb() may already have triggered a cleanup, so query by request_handle. */
137 if (finish)
138 osmo_mslookup_client_request_cancel(client, request_handle);
139}
140
141void osmo_mslookup_client_rx_result(struct osmo_mslookup_client *client, uint32_t request_handle,
142 const struct osmo_mslookup_result *result)
143{
144 struct osmo_mslookup_client_request *req = get_request(client, request_handle);
145
146 if (!req) {
147 LOGP(DMSLOOKUP, LOGL_ERROR,
148 "Internal error: Got mslookup result for a request that does not exist (handle %u)\n",
Oliver Smithf55f6052020-01-13 14:51:50 +0100149 request_handle);
Oliver Smithbf7deda2019-11-20 10:56:35 +0100150 return;
151 }
152
153 /* Ignore incoming results that are not successful */
154 if (result->rc != OSMO_MSLOOKUP_RC_RESULT)
155 return;
156
157 /* If we already stored an earlier successful result, keep that if its age is younger. */
158 if (req->result.rc == OSMO_MSLOOKUP_RC_RESULT
159 && result->age >= req->result.age)
160 return;
161
162 req->result = *result;
163
164 /* If age == 0, it doesn't get any better, so return the result immediately. */
165 if (req->result.age == 0) {
166 osmo_mslookup_request_send_result(req, true);
167 return;
168 }
169
170 if (req->waiting_min_delay)
171 return;
172
173 osmo_mslookup_request_send_result(req, false);
174}
175
176static void _osmo_mslookup_client_request_cleanup(struct osmo_mslookup_client_request *r)
177{
178 struct osmo_mslookup_client_method *m;
179 osmo_timer_del(&r->timeout);
180 llist_for_each_entry(m, &r->client->lookup_methods, entry) {
181 if (!m->request_cleanup)
182 continue;
183 m->request_cleanup(m, r->request_handle);
184 }
185 llist_del(&r->entry);
186 talloc_free(r);
187}
188
189static void timeout_cb(void *data);
190
191static void set_timer(struct osmo_mslookup_client_request *r, unsigned long milliseconds)
192{
193 osmo_timer_setup(&r->timeout, timeout_cb, r);
194 osmo_timer_schedule(&r->timeout, milliseconds / 1000, (milliseconds % 1000) * 1000);
195}
196
197static void timeout_cb(void *data)
198{
199 struct osmo_mslookup_client_request *r = data;
200 if (r->waiting_min_delay) {
201 /* The initial delay has passed. See if it stops here, or whether the overall timeout continues. */
202 r->waiting_min_delay = false;
203
204 if (r->handling.result_timeout_milliseconds <= r->handling.min_wait_milliseconds) {
205 /* It ends here. Return a final result. */
206 if (r->result.rc != OSMO_MSLOOKUP_RC_RESULT)
207 r->result.rc = OSMO_MSLOOKUP_RC_NOT_FOUND;
208 osmo_mslookup_request_send_result(r, true);
209 return;
210 }
211
212 /* We continue to listen for results. If one is already on record, send it now. */
213 if (r->result.rc == OSMO_MSLOOKUP_RC_RESULT)
214 osmo_mslookup_request_send_result(r, false);
215
216 set_timer(r, r->handling.result_timeout_milliseconds - r->handling.min_wait_milliseconds);
217 return;
218 }
219 /* The final timeout has passed, finish and clean up the request. */
220 switch (r->result.rc) {
221 case OSMO_MSLOOKUP_RC_RESULT:
222 /* If the rc == OSMO_MSLOOKUP_RC_RESULT, this result has already been sent.
223 * Don't send it again, instead send an RC_NONE, last=true result. */
224 r->result.rc = OSMO_MSLOOKUP_RC_NONE;
225 break;
226 default:
227 r->result.rc = OSMO_MSLOOKUP_RC_NOT_FOUND;
228 break;
229 }
230 osmo_mslookup_request_send_result(r, true);
231}
232
233/*! Launch a subscriber lookup with the provided query.
234 * A request is cleared implicitly when the handling->result_cb is invoked; if the quer->priv pointer becomes invalid
235 * before that, a request should be canceled by calling osmo_mslookup_client_request_cancel() with the returned
236 * request_handle. A request handle of zero indicates error.
237 * \return a nonzero request_handle that allows ending the request, or 0 on invalid query data. */
238uint32_t osmo_mslookup_client_request(struct osmo_mslookup_client *client,
239 const struct osmo_mslookup_query *query,
240 const struct osmo_mslookup_query_handling *handling)
241{
242 struct osmo_mslookup_client_request *r;
243 struct osmo_mslookup_client_request *other;
244 struct osmo_mslookup_client_method *m;
245
246 if (!osmo_mslookup_service_valid(query->service)
247 || !osmo_mslookup_id_valid(&query->id)) {
248 char buf[256];
249 LOGP(DMSLOOKUP, LOGL_ERROR, "Invalid query: %s\n",
250 osmo_mslookup_result_name_b(buf, sizeof(buf), query, NULL));
251 return 0;
252 }
253
254 r = talloc_zero(client, struct osmo_mslookup_client_request);
255 OSMO_ASSERT(r);
256
257 /* A request_handle of zero means error, so make sure we don't use a zero handle. */
258 if (!client->next_request_handle)
259 client->next_request_handle++;
260 *r = (struct osmo_mslookup_client_request){
261 .client = client,
262 .query = *query,
263 .handling = *handling,
264 .request_handle = client->next_request_handle++,
265 };
266
267 if (!r->handling.result_timeout_milliseconds)
268 r->handling.result_timeout_milliseconds = r->handling.min_wait_milliseconds;
269 if (!r->handling.result_timeout_milliseconds)
270 r->handling.result_timeout_milliseconds = 1000;
271
272 /* Paranoia: make sure a request_handle exists only once, by expiring an already existing one. This is unlikely
273 * to happen in practice: before we get near wrapping a uint32_t range, previous requests should long have
274 * timed out or ended. */
275 llist_for_each_entry(other, &client->requests, entry) {
276 if (other->request_handle != r->request_handle)
277 continue;
278 osmo_mslookup_request_send_result(other, true);
279 /* we're sure it exists only once. */
280 break;
281 }
282
283 /* Now sure that the new request_handle does not exist a second time. */
284 llist_add_tail(&r->entry, &client->requests);
285
286 if (r->handling.min_wait_milliseconds) {
287 r->waiting_min_delay = true;
288 set_timer(r, r->handling.min_wait_milliseconds);
289 } else {
290 set_timer(r, r->handling.result_timeout_milliseconds);
291 }
292
293 /* Let the lookup implementations know */
294 llist_for_each_entry(m, &client->lookup_methods, entry) {
295 m->request(m, query, r->request_handle);
296 }
297 return r->request_handle;
298}
299
300/*! End or cancel a subscriber lookup. This *must* be invoked exactly once per osmo_mslookup_client_request() invocation,
301 * either after a lookup has concluded or to abort an ongoing lookup.
302 * \param[in] request_handle The request_handle returned by an osmo_mslookup_client_request() invocation.
303 */
304void osmo_mslookup_client_request_cancel(struct osmo_mslookup_client *client, uint32_t request_handle)
305{
306 struct osmo_mslookup_client_request *r = get_request(client, request_handle);
307 if (!r)
308 return;
309 _osmo_mslookup_client_request_cleanup(r);
310}