From ab7dc40f168da4326f207ef8aeeec40229c94486 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Wed, 20 Nov 2019 03:35:37 +0100 Subject: [PATCH] D-GSM 1/n: add mslookup server in osmo-hlr Implement the mslookup server to service remote mslookup requests. This patch merely adds the logic to answer incoming mslookup requests, an actual method to receive requests (mDNS) follows in a subsequent patch. - API to configure service names and addresses for the local site (per MSC). - determine whether a subscriber is on a local MSC (checking the local proxy will be added in subsequent patch that adds proxy capability). - VTY config follows in a subsequent patch. For a detailed overview of the D-GSM and mslookup related files, please see the elaborate comment at the top of mslookup.c (already added in an earlier patch). Change-Id: Ife4a61d71926d08f310a1aeed9d9f1974f64178b --- include/osmocom/hlr/Makefile.am | 2 + include/osmocom/hlr/hlr.h | 7 + include/osmocom/hlr/mslookup_server.h | 65 +++++ include/osmocom/hlr/timestamp.h | 28 ++ src/Makefile.am | 5 + src/hlr.c | 1 + src/mslookup_server.c | 376 ++++++++++++++++++++++++++ src/timestamp.c | 53 ++++ 8 files changed, 537 insertions(+) create mode 100644 include/osmocom/hlr/mslookup_server.h create mode 100644 include/osmocom/hlr/timestamp.h create mode 100644 src/mslookup_server.c create mode 100644 src/timestamp.c diff --git a/include/osmocom/hlr/Makefile.am b/include/osmocom/hlr/Makefile.am index 532fa5dd..5c96ec87 100644 --- a/include/osmocom/hlr/Makefile.am +++ b/include/osmocom/hlr/Makefile.am @@ -10,5 +10,7 @@ noinst_HEADERS = \ hlr_vty_subscr.h \ logging.h \ lu_fsm.h \ + mslookup_server.h \ rand.h \ + timestamp.h \ $(NULL) diff --git a/include/osmocom/hlr/hlr.h b/include/osmocom/hlr/hlr.h index 58856002..12699942 100644 --- a/include/osmocom/hlr/hlr.h +++ b/include/osmocom/hlr/hlr.h @@ -67,6 +67,13 @@ struct hlr { /* Bitmask of DB_SUBSCR_FLAG_* */ uint8_t subscr_create_on_demand_flags; unsigned int subscr_create_on_demand_rand_msisdn_len; + + struct { + struct { + uint32_t local_attach_max_age; + struct llist_head local_site_services; + } server; + } mslookup; }; extern struct hlr *g_hlr; diff --git a/include/osmocom/hlr/mslookup_server.h b/include/osmocom/hlr/mslookup_server.h new file mode 100644 index 00000000..54253283 --- /dev/null +++ b/include/osmocom/hlr/mslookup_server.h @@ -0,0 +1,65 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +struct osmo_mslookup_query; +struct osmo_mslookup_result; + +/*! mslookup service name used for roaming/proxying between osmo-hlr instances. */ +#define OSMO_MSLOOKUP_SERVICE_HLR_GSUP "gsup.hlr" + +/*! What addresses to return to mslookup queries when a subscriber is attached at the local site. + * Mapping of service name to IP address and port. This corresponds to the VTY config for + * 'mslookup' / 'server' [/ 'msc MSC-1-2-3'] / 'service sip.voice at 1.2.3.4 1234'. + */ +struct mslookup_service_host { + struct llist_head entry; + char service[OSMO_MSLOOKUP_SERVICE_MAXLEN+1]; + struct osmo_sockaddr_str host_v4; + struct osmo_sockaddr_str host_v6; +}; + +/*! Sets of mslookup_service_host per connected MSC. + * When there are more than one MSC connected to this osmo-hlr, this allows keeping separate sets of service addresses + * for each MSC. The entry with mslookup_server_msc_wildcard as MSC name is used for all MSCs (if no match for that + * particular MSC is found). This corresponds to the VTY config for + * 'mslookup' / 'server' / 'msc MSC-1-2-3'. + */ +struct mslookup_server_msc_cfg { + struct llist_head entry; + struct osmo_ipa_name name; + struct llist_head service_hosts; +}; + +struct mslookup_service_host *mslookup_server_service_get(const struct osmo_ipa_name *msc_name, const char *service); + +struct mslookup_service_host *mslookup_server_msc_service_get(struct mslookup_server_msc_cfg *msc, const char *service, + bool create); +int mslookup_server_msc_service_set(struct mslookup_server_msc_cfg *msc, const char *service, + const struct osmo_sockaddr_str *addr); +int mslookup_server_msc_service_del(struct mslookup_server_msc_cfg *msc, const char *service, + const struct osmo_sockaddr_str *addr); + +extern const struct osmo_ipa_name mslookup_server_msc_wildcard; +struct mslookup_server_msc_cfg *mslookup_server_msc_get(const struct osmo_ipa_name *msc_name, bool create); + +const struct mslookup_service_host *mslookup_server_get_local_gsup_addr(); +void mslookup_server_rx(const struct osmo_mslookup_query *query, + struct osmo_mslookup_result *result); diff --git a/include/osmocom/hlr/timestamp.h b/include/osmocom/hlr/timestamp.h new file mode 100644 index 00000000..97089856 --- /dev/null +++ b/include/osmocom/hlr/timestamp.h @@ -0,0 +1,28 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include +#include +#include + +typedef time_t timestamp_t; +void timestamp_update(timestamp_t *timestamp); +bool timestamp_age(const timestamp_t *timestamp, uint32_t *age); diff --git a/src/Makefile.am b/src/Makefile.am index fec52757..571eaef2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,7 @@ AM_CFLAGS = \ $(LIBOSMOGSM_CFLAGS) \ $(LIBOSMOVTY_CFLAGS) \ $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOMSLOOKUP_CFLAGS) \ $(LIBOSMOABIS_CFLAGS) \ $(SQLITE3_CFLAGS) \ $(NULL) @@ -53,14 +54,18 @@ osmo_hlr_SOURCES = \ gsup_send.c \ hlr_ussd.c \ lu_fsm.c \ + mslookup_server.c \ + timestamp.c \ $(NULL) osmo_hlr_LDADD = \ $(top_builddir)/src/gsupclient/libosmo-gsup-client.la \ + $(top_builddir)/src/mslookup/libosmo-mslookup.la \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOMSLOOKUP_LIBS) \ $(LIBOSMOABIS_LIBS) \ $(SQLITE3_LIBS) \ $(NULL) diff --git a/src/hlr.c b/src/hlr.c index a33a68c6..2cabab46 100644 --- a/src/hlr.c +++ b/src/hlr.c @@ -695,6 +695,7 @@ int main(int argc, char **argv) INIT_LLIST_HEAD(&g_hlr->euse_list); INIT_LLIST_HEAD(&g_hlr->ss_sessions); INIT_LLIST_HEAD(&g_hlr->ussd_routes); + INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services); g_hlr->db_file_path = talloc_strdup(g_hlr, HLR_DEFAULT_DB_FILE_PATH); /* Init default (call independent) SS session guard timeout value */ diff --git a/src/mslookup_server.c b/src/mslookup_server.c new file mode 100644 index 00000000..9c4dc58d --- /dev/null +++ b/src/mslookup_server.c @@ -0,0 +1,376 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct osmo_mslookup_result not_found = { + .rc = OSMO_MSLOOKUP_RC_NOT_FOUND, + }; +const struct osmo_ipa_name mslookup_server_msc_wildcard = {}; + +static void set_result(struct osmo_mslookup_result *result, + const struct mslookup_service_host *service_host, + uint32_t age) +{ + if (!osmo_sockaddr_str_is_nonzero(&service_host->host_v4) + && !osmo_sockaddr_str_is_nonzero(&service_host->host_v6)) { + *result = not_found; + return; + } + result->rc = OSMO_MSLOOKUP_RC_RESULT; + result->host_v4 = service_host->host_v4; + result->host_v6 = service_host->host_v6; + result->age = age; +} + +const struct mslookup_service_host *mslookup_server_get_local_gsup_addr() +{ + static struct mslookup_service_host gsup_bind = {}; + struct mslookup_service_host *host; + + /* Find a HLR/GSUP service set for the server (no VLR unit name) */ + host = mslookup_server_service_get(&mslookup_server_msc_wildcard, OSMO_MSLOOKUP_SERVICE_HLR_GSUP); + if (host) + return host; + + /* Try to use the locally configured GSUP bind address */ + osmo_sockaddr_str_from_str(&gsup_bind.host_v4, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT); + if (gsup_bind.host_v4.af == AF_INET6) { + gsup_bind.host_v6 = gsup_bind.host_v4; + gsup_bind.host_v4 = (struct osmo_sockaddr_str){}; + } + return &gsup_bind; +} + +struct mslookup_server_msc_cfg *mslookup_server_msc_get(const struct osmo_ipa_name *msc_name, bool create) +{ + struct llist_head *c = &g_hlr->mslookup.server.local_site_services; + struct mslookup_server_msc_cfg *msc; + + if (!msc_name) + return NULL; + + llist_for_each_entry(msc, c, entry) { + if (osmo_ipa_name_cmp(&msc->name, msc_name)) + continue; + return msc; + } + if (!create) + return NULL; + + msc = talloc_zero(g_hlr, struct mslookup_server_msc_cfg); + OSMO_ASSERT(msc); + INIT_LLIST_HEAD(&msc->service_hosts); + msc->name = *msc_name; + llist_add_tail(&msc->entry, c); + return msc; +} + +struct mslookup_service_host *mslookup_server_msc_service_get(struct mslookup_server_msc_cfg *msc, const char *service, + bool create) +{ + struct mslookup_service_host *e; + if (!msc) + return NULL; + + llist_for_each_entry(e, &msc->service_hosts, entry) { + if (!strcmp(e->service, service)) + return e; + } + + if (!create) + return NULL; + + e = talloc_zero(msc, struct mslookup_service_host); + OSMO_ASSERT(e); + OSMO_STRLCPY_ARRAY(e->service, service); + llist_add_tail(&e->entry, &msc->service_hosts); + return e; +} + +struct mslookup_service_host *mslookup_server_service_get(const struct osmo_ipa_name *msc_name, const char *service) +{ + struct mslookup_server_msc_cfg *msc = mslookup_server_msc_get(msc_name, false); + if (!msc) + return NULL; + return mslookup_server_msc_service_get(msc, service, false); +} + +int mslookup_server_msc_service_set(struct mslookup_server_msc_cfg *msc, const char *service, + const struct osmo_sockaddr_str *addr) +{ + struct mslookup_service_host *e; + + if (!service || !service[0] + || strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN) + return -EINVAL; + if (!addr || !osmo_sockaddr_str_is_nonzero(addr)) + return -EINVAL; + + e = mslookup_server_msc_service_get(msc, service, true); + if (!e) + return -EINVAL; + + switch (addr->af) { + case AF_INET: + e->host_v4 = *addr; + break; + case AF_INET6: + e->host_v6 = *addr; + break; + default: + return -EINVAL; + } + return 0; +} + +int mslookup_server_msc_service_del(struct mslookup_server_msc_cfg *msc, const char *service, + const struct osmo_sockaddr_str *addr) +{ + struct mslookup_service_host *e, *n; + int deleted = 0; + + if (!msc) + return -ENOENT; + + llist_for_each_entry_safe(e, n, &msc->service_hosts, entry) { + if (service && strcmp(service, e->service)) + continue; + + if (addr) { + if (!osmo_sockaddr_str_cmp(addr, &e->host_v4)) { + e->host_v4 = (struct osmo_sockaddr_str){}; + /* Removed one addr. If the other is still there, keep the entry. */ + if (osmo_sockaddr_str_is_nonzero(&e->host_v6)) + continue; + } else if (!osmo_sockaddr_str_cmp(addr, &e->host_v6)) { + e->host_v6 = (struct osmo_sockaddr_str){}; + /* Removed one addr. If the other is still there, keep the entry. */ + if (osmo_sockaddr_str_is_nonzero(&e->host_v4)) + continue; + } else + /* No addr match, keep the entry. */ + continue; + /* Addr matched and none is left. Delete. */ + } + llist_del(&e->entry); + talloc_free(e); + deleted++; + } + return deleted; +} + +/* A remote entity is asking us whether we are the home HLR of the given subscriber. */ +static void mslookup_server_rx_hlr_gsup(const struct osmo_mslookup_query *query, + struct osmo_mslookup_result *result) +{ + const struct mslookup_service_host *host; + int rc; + switch (query->id.type) { + case OSMO_MSLOOKUP_ID_IMSI: + rc = db_subscr_exists_by_imsi(g_hlr->dbc, query->id.imsi); + break; + case OSMO_MSLOOKUP_ID_MSISDN: + rc = db_subscr_exists_by_msisdn(g_hlr->dbc, query->id.msisdn); + break; + default: + LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type); + *result = not_found; + return; + } + + if (rc) { + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); + *result = not_found; + return; + } + + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: found in local HLR\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); + + host = mslookup_server_get_local_gsup_addr(); + + set_result(result, host, 0); + if (result->rc != OSMO_MSLOOKUP_RC_RESULT) { + LOGP(DMSLOOKUP, LOGL_ERROR, + "Subscriber found, but error in service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' config:" + " v4: " OSMO_SOCKADDR_STR_FMT " v6: " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v4), + OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v6)); + } +} + +/* Look in the local HLR record: If the subscriber is "at home" in this HLR and is also currently located at a local + * VLR, we will find a valid location updating with vlr_number, and no vlr_via_proxy entry. */ +static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *query, + uint32_t *lu_age, + struct osmo_ipa_name *local_msc_name, + struct hlr_subscriber *ret_subscr) +{ + struct hlr_subscriber _subscr; + int rc; + uint32_t age; + + struct hlr_subscriber *subscr = ret_subscr ? : &_subscr; + + switch (query->id.type) { + case OSMO_MSLOOKUP_ID_IMSI: + rc = db_subscr_get_by_imsi(g_hlr->dbc, query->id.imsi, subscr); + break; + case OSMO_MSLOOKUP_ID_MSISDN: + rc = db_subscr_get_by_msisdn(g_hlr->dbc, query->id.msisdn, subscr); + break; + default: + LOGP(DMSLOOKUP, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type); + return false; + } + + if (rc) { + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: does not exist in local HLR\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); + return false; + } + + if (!subscr->vlr_number[0]) { + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: not attached (vlr_number unset)\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); + return false; + } + + if (subscr->vlr_via_proxy.len) { + /* The VLR is behind a proxy, the subscriber is not attached to a local VLR but a remote one. That + * remote proxy should instead respond to the service lookup request. */ + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: last attach is not at local VLR, but at VLR '%s' via proxy %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), + subscr->vlr_number, + osmo_ipa_name_to_str(&subscr->vlr_via_proxy)); + return false; + } + + if (!timestamp_age(&subscr->last_lu_seen, &age)) { + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: Invalid last_lu_seen timestamp for subscriber\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); + return false; + } + if (age > g_hlr->mslookup.server.local_attach_max_age) { + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: last attach was here, but too long ago: %us > %us\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), + age, g_hlr->mslookup.server.local_attach_max_age); + return false; + } + + *lu_age = age; + osmo_ipa_name_set_str(local_msc_name, subscr->vlr_number); + LOGP(DMSLOOKUP, LOGL_DEBUG, "%s: attached %u seconds ago at local VLR %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), + age, osmo_ipa_name_to_str(local_msc_name)); + + return true; +} + +static bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query, + uint32_t *lu_age_p, + struct osmo_ipa_name *local_msc_name) +{ + bool attached_here; + uint32_t lu_age = 0; + struct osmo_ipa_name msc_name = {}; + + /* First ask the local HLR db, but if the local proxy record indicates a more recent LU, use that instead. + * For all usual cases, only one of these will reflect a LU, even if a subscriber had more than one home HLR: + * - if the subscriber is known here, we will never proxy. + * - if the subscriber is not known here, this local HLR db will never record a LU. + * However, if a subscriber was being proxied to a remote home HLR, and if then the subscriber was also added to + * the local HLR database, there might occur a situation where both reflect a LU. So, to be safe against all + * situations, compare the two entries. + */ + attached_here = subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name, NULL); + + /* Future: If proxy has a younger lu, replace. */ + + if (attached_here && !msc_name.len) { + LOGP(DMSLOOKUP, LOGL_ERROR, "%s: attached here, but no VLR name known\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL)); + return false; + } + + if (!attached_here) { + /* Already logged "not attached" for both local-db and proxy attach */ + return false; + } + + LOGP(DMSLOOKUP, LOGL_INFO, "%s: attached here, at VLR %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), + osmo_ipa_name_to_str(&msc_name)); + *lu_age_p = lu_age; + *local_msc_name = msc_name; + return true; +} + +/* A remote entity is asking us whether we are providing the given service for the given subscriber. */ +void mslookup_server_rx(const struct osmo_mslookup_query *query, + struct osmo_mslookup_result *result) +{ + const struct mslookup_service_host *service_host; + uint32_t age; + struct osmo_ipa_name msc_name; + + /* A request for a home HLR: answer exactly if this is the subscriber's home HLR, i.e. the IMSI is listed in the + * HLR database. */ + if (strcmp(query->service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP) == 0) + return mslookup_server_rx_hlr_gsup(query, result); + + /* All other service types: answer when the subscriber has done a LU that is either listed in the local HLR or + * in the GSUP proxy database: i.e. if the subscriber has done a Location Updating at an VLR belonging to this + * HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */ + if (!subscriber_has_done_lu_here(query, &age, &msc_name)) { + *result = not_found; + return; + } + + /* We've detected a LU here. The VLR where the LU happened is stored in msc_unit_name, and the LU age is stored + * in 'age'. Figure out the address configured for that VLR and service name. */ + service_host = mslookup_server_service_get(&msc_name, query->service); + + if (!service_host) { + /* Find such service set globally (no VLR unit name) */ + service_host = mslookup_server_service_get(&mslookup_server_msc_wildcard, query->service); + } + + if (!service_host) { + LOGP(DMSLOOKUP, LOGL_ERROR, + "%s: subscriber found, but no service %s configured, cannot service lookup request\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), + osmo_quote_str_c(OTC_SELECT, query->service, -1)); + *result = not_found; + return; + } + + set_result(result, service_host, age); +} diff --git a/src/timestamp.c b/src/timestamp.c new file mode 100644 index 00000000..002857d0 --- /dev/null +++ b/src/timestamp.c @@ -0,0 +1,53 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +/* Central implementation to set a timestamp to the current time, in case we want to modify this in the future. */ +void timestamp_update(timestamp_t *timestamp) +{ + struct timeval tv; + time_t raw; + struct tm utc; + /* The simpler way would be just time(&raw), but by using osmo_gettimeofday() we can also use + * osmo_gettimeofday_override for unit tests independent from real time. */ + osmo_gettimeofday(&tv, NULL); + raw = tv.tv_sec; + gmtime_r(&raw, &utc); + *timestamp = mktime(&utc); +} + +/* Calculate seconds since a given timestamp was taken. Return true for a valid age returned in age_p, return false if + * the timestamp is either in the future or the age surpasses uint32_t range. When false is returned, *age_p is set to + * UINT32_MAX. */ +bool timestamp_age(const timestamp_t *timestamp, uint32_t *age_p) +{ + int64_t age64; + timestamp_t now; + timestamp_update(&now); + age64 = (int64_t)now - (int64_t)(*timestamp); + if (age64 < 0 || age64 > UINT32_MAX) { + *age_p = UINT32_MAX; + return false; + } + *age_p = (uint32_t)age64; + return true; +} +