Oliver Smith | bf7deda | 2019-11-20 10:56:35 +0100 | [diff] [blame] | 1 | /* 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. */ |
| 23 | struct 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. */ |
| 31 | struct 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 | |
| 44 | static 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 | |
| 56 | struct 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 | */ |
| 69 | bool 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 | |
| 78 | static 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 | */ |
| 88 | void 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 | */ |
| 105 | void 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 | */ |
| 114 | bool 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 | |
| 127 | static 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 | |
| 141 | void 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 Smith | f55f605 | 2020-01-13 14:51:50 +0100 | [diff] [blame] | 149 | request_handle); |
Oliver Smith | bf7deda | 2019-11-20 10:56:35 +0100 | [diff] [blame] | 150 | 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 | |
| 176 | static 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 | |
| 189 | static void timeout_cb(void *data); |
| 190 | |
| 191 | static 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 | |
| 197 | static 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. */ |
| 238 | uint32_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 | */ |
| 304 | void 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 | } |