From f5a0fa37c43810eb6f3278aa67a8658a5a737836 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sun, 3 Mar 2019 15:44:18 +0100 Subject: [PATCH] Initial version of remsim-server Change-Id: I1caadc528d5e61a4129c32c53283250cd37f3a3c --- .gitignore | 1 + configure.ac | 3 + src/Makefile.am | 2 +- src/rspro_util.c | 17 +- src/rspro_util.h | 1 + src/server/Makefile.am | 14 + src/server/remsim_server.c | 67 +++++ src/server/rest_api.c | 486 ++++++++++++++++++++++++++++++ src/server/rest_api.h | 5 + src/server/rspro_server.c | 603 +++++++++++++++++++++++++++++++++++++ src/server/rspro_server.h | 55 ++++ 11 files changed, 1252 insertions(+), 2 deletions(-) create mode 100644 src/server/Makefile.am create mode 100644 src/server/remsim_server.c create mode 100644 src/server/rest_api.c create mode 100644 src/server/rest_api.h create mode 100644 src/server/rspro_server.c create mode 100644 src/server/rspro_server.h diff --git a/.gitignore b/.gitignore index d543602..2ec5f9f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ src/pcsc_test src/remsim-bankd src/remsim-client src/simtrace2-remsim-client +src/server/remsim-server diff --git a/configure.ac b/configure.ac index 2c30dfb..7a07f6b 100644 --- a/configure.ac +++ b/configure.ac @@ -41,6 +41,8 @@ PKG_CHECK_MODULES(OSMOABIS, libosmoabis) PKG_CHECK_MODULES(OSMOSIM, libosmosim) PKG_CHECK_MODULES(PCSC, libpcsclite) PKG_CHECK_MODULES(USB, libusb-1.0) +PKG_CHECK_MODULES(ULFIUS, libulfius) +PKG_CHECK_MODULES(JANSSON, jansson) AC_CONFIG_MACRO_DIR([m4]) @@ -87,6 +89,7 @@ AC_OUTPUT( Makefile src/Makefile src/rspro/Makefile + src/server/Makefile include/Makefile include/osmocom/Makefile include/osmocom/rspro/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index a44906b..f8aebfe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = rspro +SUBDIRS = rspro server AM_CFLAGS = -Wall -I$(top_srcdir)/include -I$(top_builddir)/include \ $(OSMOCORE_CFLAGS) $(OSMOGSM_CFLAGS) $(OSMOABIS_CFLAGS) \ diff --git a/src/rspro_util.c b/src/rspro_util.c index ed0678e..2f2e2d1 100644 --- a/src/rspro_util.c +++ b/src/rspro_util.c @@ -93,8 +93,10 @@ static void fill_comp_id(ComponentIdentity_t *out, const struct app_comp_id *in) void string_fromOCTET_STRING(char *out, size_t out_size, const OCTET_STRING_t *in) { - if (!in) + if (!in) { out[0] = '\0'; + return; + } memcpy(out, in->buf, out_size < in->size ? out_size : in->size); if (in->size < out_size) out[in->size] = '\0'; @@ -155,6 +157,19 @@ RsproPDU_t *rspro_gen_ConnectBankReq(const struct app_comp_id *a_cid, return pdu; } +RsproPDU_t *rspro_gen_ConnectBankRes(const struct app_comp_id *a_cid, e_ResultCode res) +{ + RsproPDU_t *pdu = CALLOC(1, sizeof(*pdu)); + if (!pdu) + return NULL; + pdu->version = 2; + pdu->msg.present = RsproPDUchoice_PR_connectBankRes; + fill_comp_id(&pdu->msg.choice.connectBankRes.identity, a_cid); + pdu->msg.choice.connectBankRes.result = res; + + return pdu; +} + RsproPDU_t *rspro_gen_ConnectClientReq(const struct app_comp_id *a_cid, const ClientSlot_t *client) { RsproPDU_t *pdu = CALLOC(1, sizeof(*pdu)); diff --git a/src/rspro_util.h b/src/rspro_util.h index 26b3883..945d4f7 100644 --- a/src/rspro_util.h +++ b/src/rspro_util.h @@ -22,6 +22,7 @@ struct msgb *rspro_enc_msg(RsproPDU_t *pdu); RsproPDU_t *rspro_dec_msg(struct msgb *msg); RsproPDU_t *rspro_gen_ConnectBankReq(const struct app_comp_id *a_cid, uint16_t bank_id, uint16_t num_slots); +RsproPDU_t *rspro_gen_ConnectBankRes(const struct app_comp_id *a_cid, e_ResultCode res); RsproPDU_t *rspro_gen_ConnectClientReq(const struct app_comp_id *a_cid, const ClientSlot_t *client); RsproPDU_t *rspro_gen_ConnectClientRes(const struct app_comp_id *a_cid, e_ResultCode res); RsproPDU_t *rspro_gen_CreateMappingReq(const ClientSlot_t *client, const BankSlot_t *bank); diff --git a/src/server/Makefile.am b/src/server/Makefile.am new file mode 100644 index 0000000..ce7843b --- /dev/null +++ b/src/server/Makefile.am @@ -0,0 +1,14 @@ + +AM_CFLAGS = -Wall -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/src \ + -I$(top_srcdir)/include/osmocom/rspro \ + $(OSMOCORE_CFLAGS) $(OSMOGSM_CFLAGS) $(OSMOABIS_CFLAGS) \ + $(ULFIUS_CFLAGS) $(JANSSON_CFLAGS) + +noinst_HEADERS = rspro_server.h rest_api.h + +bin_PROGRAMS = remsim-server + +remsim_server_SOURCES = remsim_server.c rspro_server.c rest_api.c ../rspro_util.c ../slotmap.c ../debug.c +remsim_server_LDADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \ + $(ULFIUS_LIBS) $(JANSSON_LIBS) \ + ../libosmo-rspro.la diff --git a/src/server/remsim_server.c b/src/server/remsim_server.c new file mode 100644 index 0000000..29ac582 --- /dev/null +++ b/src/server/remsim_server.c @@ -0,0 +1,67 @@ +#include + +#include + +#include +#include +#include +#include + +#include "debug.h" +#include "slotmap.h" +#include "rest_api.h" +#include "rspro_server.h" + +struct rspro_server *g_rps; +void *g_tall_ctx; +struct osmo_fd g_event_ofd; + +int main(int argc, char **argv) +{ + int rc; + + g_tall_ctx = talloc_named_const(NULL, 0, "global"); + + osmo_init_logging2(g_tall_ctx, &log_info); + + g_rps = rspro_server_create(g_tall_ctx, "0.0.0.0", 9998); + if (!g_rps) + exit(1); + g_rps->slotmaps = slotmap_init(g_rps); + if (!g_rps->slotmaps) + goto out_rspro; + + g_rps->comp_id.type = ComponentType_remsimServer; + OSMO_STRLCPY_ARRAY(g_rps->comp_id.name, "fixme-name"); + OSMO_STRLCPY_ARRAY(g_rps->comp_id.software, "remsim-server"); + OSMO_STRLCPY_ARRAY(g_rps->comp_id.sw_version, PACKAGE_VERSION); + /* FIXME: other members of app_comp_id */ + + rc = eventfd(0, 0); + if (rc < 0) + goto out_rps; + osmo_fd_setup(&g_event_ofd, rc, BSC_FD_READ, event_fd_cb, g_rps, 0); + osmo_fd_register(&g_event_ofd); + + rc = rest_api_init(9997); + if (rc < 0) + goto out_eventfd; + + while (1) { + osmo_select_main(0); + } + + rest_api_fini(); + + exit(0); + +out_eventfd: + close(g_event_ofd.fd); +out_rps: + talloc_free(g_rps->slotmaps); + talloc_free(g_rps); +out_rspro: + rspro_server_destroy(g_rps); + + exit(1); +} diff --git a/src/server/rest_api.c b/src/server/rest_api.c new file mode 100644 index 0000000..a1c5718 --- /dev/null +++ b/src/server/rest_api.c @@ -0,0 +1,486 @@ +#include +#include +#include + +#include +#include + +#include +#include + +#define PREFIX "/api/backend/v1" + +#include "debug.h" +#include "rest_api.h" +#include "slotmap.h" +#include "rspro_server.h" + +static json_t *comp_id2json(const struct app_comp_id *comp_id) +{ + json_t *ret = json_object(); + + static const char *type_names[] = { + [ComponentType_remsimClient] = "remsimClient", + [ComponentType_remsimServer] = "remsimServer", + [ComponentType_remsimBankd] = "remsimBankd" + }; + + json_object_set_new(ret, "type_", json_string(type_names[comp_id->type])); + json_object_set_new(ret, "name", json_string(comp_id->name)); + json_object_set_new(ret, "software", json_string(comp_id->software)); + json_object_set_new(ret, "swVersion", json_string(comp_id->sw_version)); + if (strlen(comp_id->hw_manufacturer)) + json_object_set_new(ret, "hwManufacturer", json_string(comp_id->hw_manufacturer)); + if (strlen(comp_id->hw_model)) + json_object_set_new(ret, "hwModel", json_string(comp_id->hw_model)); + if (strlen(comp_id->hw_serial_nr)) + json_object_set_new(ret, "hwSerialNr", json_string(comp_id->hw_serial_nr)); + if (strlen(comp_id->hw_version)) + json_object_set_new(ret, "hwVersion", json_string(comp_id->hw_version)); + if (strlen(comp_id->fw_version)) + json_object_set_new(ret, "fwVersion", json_string(comp_id->fw_version)); + + return ret; +} + +static json_t *client2json(const struct rspro_client_conn *conn) +{ + json_t *ret = json_object(); + + json_object_set_new(ret, "peer", json_string(conn->fi->id)); + json_object_set_new(ret, "state", json_string(osmo_fsm_inst_state_name(conn->fi))); + /* FIXME: only in the right state */ + json_object_set_new(ret, "component_id", comp_id2json(&conn->comp_id)); + + return ret; +} + +static json_t *bank2json(const struct rspro_client_conn *conn) +{ + json_t *ret = client2json(conn); + json_object_set_new(ret, "bankId", json_integer(conn->bank.bank_id)); + json_object_set_new(ret, "numberOfSlots", json_integer(conn->bank.num_slots)); + return ret; +} + +static json_t *bank_slot2json(const struct bank_slot *bslot) +{ + json_t *ret = json_object(); + json_object_set_new(ret, "bankId", json_integer(bslot->bank_id)); + json_object_set_new(ret, "slotNr", json_integer(bslot->slot_nr)); + return ret; +} +static int json2bank_slot(struct bank_slot *bslot, json_t *in) +{ + json_t *jbank_id, *jslot_nr; + + if (!json_is_object(in)) + return -EINVAL; + jbank_id = json_object_get(in, "bankId"); + if (!jbank_id || !json_is_integer(jbank_id)) + return -EINVAL; + jslot_nr = json_object_get(in, "slotNr"); + if (!jslot_nr || !json_is_integer(jslot_nr)) + return -EINVAL; + bslot->bank_id = json_integer_value(jbank_id); + bslot->slot_nr = json_integer_value(jslot_nr); + return 0; +} + +static json_t *client_slot2json(const struct client_slot *bslot) +{ + json_t *ret = json_object(); + json_object_set_new(ret, "clientId", json_integer(bslot->client_id)); + json_object_set_new(ret, "slotNr", json_integer(bslot->slot_nr)); + return ret; +} +static int json2client_slot(struct client_slot *cslot, json_t *in) +{ + json_t *jclient_id, *jslot_nr; + + if (!json_is_object(in)) + return -EINVAL; + jclient_id = json_object_get(in, "clientId"); + if (!jclient_id || !json_is_integer(jclient_id)) + return -EINVAL; + jslot_nr = json_object_get(in, "slotNr"); + if (!jslot_nr || !json_is_integer(jslot_nr)) + return -EINVAL; + cslot->client_id = json_integer_value(jclient_id); + cslot->slot_nr = json_integer_value(jslot_nr); + return 0; +} + +static json_t *slotmap2json(const struct slot_mapping *slotmap) +{ + json_t *ret = json_object(); + json_object_set_new(ret, "bank", bank_slot2json(&slotmap->bank)); + json_object_set_new(ret, "client", client_slot2json(&slotmap->client)); + json_object_set_new(ret, "state", json_string(slotmap_state_name(slotmap->state))); + return ret; +} +static int json2slotmap(struct slot_mapping *out, json_t *in) +{ + json_t *jbank, *jclient; + int rc; + + if (!json_is_object(in)) + return -EINVAL; + jbank = json_object_get(in, "bank"); + if (!jbank || !json_is_object(jbank)) + return -EINVAL; + jclient = json_object_get(in, "client"); + if (!jclient || !json_is_object(jclient)) + return -EINVAL; + + rc = json2bank_slot(&out->bank, jbank); + if (rc < 0) + return rc; + return json2client_slot(&out->client, jclient); +} + + +extern struct rspro_server *g_rps; + +static int api_cb_rest_ctr_get(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + return U_CALLBACK_CONTINUE; +} + +static int api_cb_banks_get(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + struct rspro_client_conn *conn; + json_t *json_body = json_object(); + json_t *json_banks = json_array(); + + pthread_rwlock_rdlock(&g_rps->rwlock); + llist_for_each_entry(conn, &g_rps->banks, list) { + json_array_append_new(json_banks, bank2json(conn)); + } + pthread_rwlock_unlock(&g_rps->rwlock); + + json_object_set_new(json_body, "banks", json_banks); + ulfius_set_json_body_response(resp, 200, json_body); + json_decref(json_body); + + return U_CALLBACK_COMPLETE; +} + +static int api_cb_bank_get(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + const char *bank_id_str = u_map_get(req->map_url, "bank_id"); + struct rspro_client_conn *conn; + json_t *json_body = NULL; + unsigned long bank_id; + int status; + + if (!bank_id_str) { + status = 400; + goto out_err; + } + + bank_id = strtoul(bank_id_str, NULL, 10); + if (bank_id > 0xffff) { + status = 400; + goto out_err; + } + + pthread_rwlock_rdlock(&g_rps->rwlock); + llist_for_each_entry(conn, &g_rps->banks, list) { + if (conn->bank.bank_id == bank_id) { + json_body = bank2json(conn); + break; + } + } + pthread_rwlock_unlock(&g_rps->rwlock); + + if (json_body) { + ulfius_set_json_body_response(resp, 200, json_body); + json_decref(json_body); + } else { + ulfius_set_json_body_response(resp, 404, json_body); + } + + return U_CALLBACK_COMPLETE; + +out_err: + ulfius_set_empty_body_response(resp, status); + return U_CALLBACK_COMPLETE; +} + + +static int api_cb_clients_get(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + struct rspro_client_conn *conn; + json_t *json_body = json_object(); + json_t *json_clients = json_array(); + + pthread_rwlock_rdlock(&g_rps->rwlock); + llist_for_each_entry(conn, &g_rps->clients, list) { + json_array_append_new(json_clients, client2json(conn)); + } + pthread_rwlock_unlock(&g_rps->rwlock); + + json_object_set_new(json_body, "clients", json_clients); + ulfius_set_json_body_response(resp, 200, json_body); + json_decref(json_body); + + return U_CALLBACK_COMPLETE; +} + +static int api_cb_client_get(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + const char *client_id_str = u_map_get(req->map_url, "client_id"); + struct rspro_client_conn *conn; + json_t *json_body = NULL; + unsigned long client_id; + int status; + + if (!client_id_str) { + status = 400; + goto out_err; + } + + client_id = strtoul(client_id_str, NULL, 10); + if (client_id > 0xffff) { + status = 400; + goto out_err; + } + + pthread_rwlock_rdlock(&g_rps->rwlock); + llist_for_each_entry(conn, &g_rps->clients, list) { + if (conn->bank.bank_id == client_id) { /* FIXME */ + json_body = client2json(conn); + break; + } + } + pthread_rwlock_unlock(&g_rps->rwlock); + + if (json_body) { + ulfius_set_json_body_response(resp, 200, json_body); + json_decref(json_body); + } else { + ulfius_set_json_body_response(resp, 404, json_body); + } + + return U_CALLBACK_COMPLETE; + +out_err: + ulfius_set_empty_body_response(resp, status); + return U_CALLBACK_COMPLETE; +} + +static int api_cb_slotmaps_get(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + struct slot_mapping *map; + json_t *json_body = json_object(); + json_t *json_maps = json_array(); + + slotmaps_rdlock(g_rps->slotmaps); + llist_for_each_entry(map, &g_rps->slotmaps->mappings, list) { + json_array_append_new(json_maps, slotmap2json(map)); + } + slotmaps_unlock(g_rps->slotmaps); + + json_object_set_new(json_body, "slotmaps", json_maps); + ulfius_set_json_body_response(resp, 200, json_body); + json_decref(json_body); + + return U_CALLBACK_COMPLETE; +} + +extern struct osmo_fd g_event_ofd; +/* trigger our main thread select() loop */ +static void trigger_main_thread_via_eventfd(void) +{ + uint64_t one = 1; + int rc; + + rc = write(g_event_ofd.fd, &one, sizeof(one)); + if (rc < 8) + fprintf(stderr, "Error writing to eventfd(): %d\n", rc); +} + +static int api_cb_slotmaps_post(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + struct rspro_server *srv = g_rps; + struct slot_mapping slotmap, *map; + struct rspro_client_conn *conn; + json_error_t json_err; + json_t *json_req; + int rc; + + json_req = ulfius_get_json_body_request(req, &json_err); + if (!json_req) { + fprintf(stderr, "REST: No JSON Body\n"); + goto err; + } + + rc = json2slotmap(&slotmap, json_req); + if (rc < 0) + goto err; + map = slotmap_add(g_rps->slotmaps, &slotmap.bank, &slotmap.client); + if (!map) { + fprintf(stderr, "REST: Cannot add slotmap\n"); + goto err; + } + slotmap_state_change(map, SLMAP_S_NEW, NULL); + + /* check if any already-connected bankd matches this new map. If yes, associate it */ + pthread_rwlock_rdlock(&srv->rwlock); + llist_for_each_entry(conn, &srv->banks, list) { + if (conn->bank.bank_id == slotmap.bank.bank_id) { + slotmap_state_change(map, SLMAP_S_NEW, &conn->bank.maps_new); + /* FIXME: Notify the conn FSM about some new maps being available */ + trigger_main_thread_via_eventfd(); + break; + } + } + pthread_rwlock_unlock(&srv->rwlock); + + + ulfius_set_empty_body_response(resp, 201); + + return U_CALLBACK_COMPLETE; +err: + ulfius_set_empty_body_response(resp, 400); + return U_CALLBACK_COMPLETE; +} + +/* caller is holding a write lock on slotmaps->rwlock */ +static void _slotmap_mark_deleted(struct slot_mapping *map) +{ + struct rspro_client_conn *conn = bankd_conn_by_id(g_rps, map->bank.bank_id); + + /* delete map from global list to ensure it's not found by further lookups, + * particularly in case somebody wants to create a new map for the same bank/slot */ + llist_del(&map->list); + /* safely initialize list head to avoid trouble when del_slotmap() does another llist_del() */ + INIT_LLIST_HEAD(&map->list); + + switch (map->state) { + case SLMAP_S_NEW: + /* new map, not yet sent to bank: we can remove it immediately */ + /* delete from bank list (if any) */ + llist_del(&map->bank_list); + /* safely initialize list head to avoid trouble when del_slotmap() does another llist_del() */ + INIT_LLIST_HEAD(&map->bank_list); + _slotmap_del(map->maps, map); + break; + case SLMAP_S_UNACKNOWLEDGED: + /* map has been sent to bank already, but wasn't acknowledged yet */ + /* FIXME: what to do now? If we keep it unchanged, it will not be deleted. If we + * move it to DELETE_REQ, */ + break; + case SLMAP_S_ACTIVE: + /* map is fully active. Need to move it to DELETE_REQ state + trigger rspro thread, + * so the deletion can propagate to the bankd */ + _slotmap_state_change(map, SLMAP_S_DELETE_REQ, &conn->bank.maps_delreq); + trigger_main_thread_via_eventfd(); + break; + case SLMAP_S_DELETE_REQ: + /* REST had already requested deletion, but RSPRO thread hasn't issued the delete + * command to the bankd yet: Do nothing */ + break; + case SLMAP_S_DELETING: + /* we had already requested deletion of this map previously: Do nothing */ + break; + default: + OSMO_ASSERT(0); + } +} + +static int api_cb_slotmaps_del(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + const char *slotmap_id_str = u_map_get(req->map_url, "slotmap_id"); + struct slot_mapping *map; + int status = 404; + unsigned long map_id; + + if (!slotmap_id_str) { + status = 400; + goto err; + } + map_id = strtoul(slotmap_id_str, NULL, 10); + if (map_id < 0) { + status = 400; + goto err; + } + + slotmaps_wrlock(g_rps->slotmaps); + llist_for_each_entry(map, &g_rps->slotmaps->mappings, list) { + if (slotmap_get_id(map) == map_id) { + _slotmap_mark_deleted(map); + status = 200; + break; + } + } + slotmaps_unlock(g_rps->slotmaps); + trigger_main_thread_via_eventfd(); + + + ulfius_set_empty_body_response(resp, status); + return U_CALLBACK_COMPLETE; +err: + ulfius_set_empty_body_response(resp, status); + return U_CALLBACK_COMPLETE; +} + +static int api_cb_global_reset_post(const struct _u_request *req, struct _u_response *resp, void *user_data) +{ + struct slot_mapping *map, *map2; + + LOGP(DMAIN, LOGL_NOTICE, "Global RESET from REST API\n"); + + /* mark all slot mappings as deleted */ + slotmaps_wrlock(g_rps->slotmaps); + llist_for_each_entry_safe(map, map2, &g_rps->slotmaps->mappings, list) { + _slotmap_mark_deleted(map); + } + slotmaps_unlock(g_rps->slotmaps); + trigger_main_thread_via_eventfd(); + + ulfius_set_empty_body_response(resp, 200); + return U_CALLBACK_COMPLETE; +} + +static const struct _u_endpoint api_endpoints[] = { + /* get the current restart counter */ + { "GET", PREFIX, "/restart-counter", 0, &api_cb_rest_ctr_get, NULL }, + /* get a list of SIM banks */ + { "GET", PREFIX, "/banks", 0, &api_cb_banks_get, NULL }, + { "GET", PREFIX, "/banks/:bank_id", 0, &api_cb_bank_get, NULL }, + /* get a list of SIM clients */ + { "GET", PREFIX, "/clients", 0, &api_cb_clients_get, NULL }, + { "GET", PREFIX, "/clients/:client_id", 0, &api_cb_client_get, NULL }, + /* get a list of mappings */ + { "GET", PREFIX, "/slotmaps", 0, &api_cb_slotmaps_get, NULL }, + { "POST", PREFIX, "/slotmaps", 0, &api_cb_slotmaps_post, NULL }, + { "DELETE", PREFIX, "/slotmaps/:slotmap_id", 0, &api_cb_slotmaps_del, NULL }, + { "POST", PREFIX, "/global-reset", 0, &api_cb_global_reset_post, NULL }, +}; + +static struct _u_instance g_instance; + +int rest_api_init(uint16_t port) +{ + int i; + + if (ulfius_init_instance(&g_instance, port, NULL, NULL) != U_OK) + return -1; + + for (i = 0; i < ARRAY_SIZE(api_endpoints); i++) + ulfius_add_endpoint(&g_instance, &api_endpoints[i]); + + if (ulfius_start_framework(&g_instance) != U_OK) { + fprintf(stderr, "Cannot start REST API on port %u\n", port); + return -1; + } + return 0; +} + +void rest_api_fini(void) +{ + ulfius_stop_framework(&g_instance); + ulfius_clean_instance(&g_instance); +} diff --git a/src/server/rest_api.h b/src/server/rest_api.h new file mode 100644 index 0000000..87269e8 --- /dev/null +++ b/src/server/rest_api.h @@ -0,0 +1,5 @@ +#pragma once +#include + +int rest_api_init(uint16_t port); +void rest_api_fini(void); diff --git a/src/server/rspro_server.c b/src/server/rspro_server.c new file mode 100644 index 0000000..27a0272 --- /dev/null +++ b/src/server/rspro_server.c @@ -0,0 +1,603 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "debug.h" +#include "rspro_util.h" +#include "rspro_server.h" + +#define S(x) (1 << (x)) + +static void client_slot2rspro(ClientSlot_t *out, const struct client_slot *in) +{ + out->clientId = in->client_id; + out->slotNr = in->slot_nr; +} + +static void bank_slot2rspro(BankSlot_t *out, const struct bank_slot *in) +{ + out->bankId = in->bank_id; + out->slotNr = in->slot_nr; +} + +static RsproPDU_t *slotmap2CreateMappingReq(const struct slot_mapping *slotmap) +{ + ClientSlot_t clslot; + BankSlot_t bslot; + + client_slot2rspro(&clslot, &slotmap->client); + bank_slot2rspro(&bslot, &slotmap->bank); + + return rspro_gen_CreateMappingReq(&clslot, &bslot); +} + +static RsproPDU_t *slotmap2RemoveMappingReq(const struct slot_mapping *slotmap) +{ + ClientSlot_t clslot; + BankSlot_t bslot; + + client_slot2rspro(&clslot, &slotmap->client); + bank_slot2rspro(&bslot, &slotmap->bank); + + return rspro_gen_RemoveMappingReq(&clslot, &bslot); +} + + +static void client_conn_send(struct rspro_client_conn *conn, RsproPDU_t *pdu) +{ + struct msgb *msg_tx = rspro_enc_msg(pdu); + if (!msg_tx) { + ASN_STRUCT_FREE(asn_DEF_RsproPDU, pdu); + return; + } + ipa_prepend_header_ext(msg_tx, IPAC_PROTO_EXT_RSPRO); + ipa_msg_push_header(msg_tx, IPAC_PROTO_OSMO); + ipa_server_conn_send(conn->peer, msg_tx); +} + + +/*********************************************************************** + * per-client connection FSM + ***********************************************************************/ + +static void rspro_client_conn_destroy(struct rspro_client_conn *conn); + +enum remsim_server_client_fsm_state { + CLNTC_ST_INIT, + CLNTC_ST_ESTABLISHED, + CLNTC_ST_CONNECTED, +}; + +enum remsim_server_client_event { + CLNTC_E_TCP_UP, + CLNTC_E_CLIENT_CONN, /* Connect{Client,Bank}Req received */ + CLNTC_E_BANK_CONN, + CLNTC_E_TCP_DOWN, + CLNTC_E_CREATE_MAP_RES, /* CreateMappingRes received */ + CLNTC_E_REMOVE_MAP_RES, /* RemoveMappingRes received */ + CLNTC_E_PUSH, /* drain maps_new or maps_delreq */ +}; + +static const struct value_string server_client_event_names[] = { + OSMO_VALUE_STRING(CLNTC_E_TCP_UP), + OSMO_VALUE_STRING(CLNTC_E_CLIENT_CONN), + OSMO_VALUE_STRING(CLNTC_E_BANK_CONN), + OSMO_VALUE_STRING(CLNTC_E_TCP_DOWN), + OSMO_VALUE_STRING(CLNTC_E_CREATE_MAP_RES), + OSMO_VALUE_STRING(CLNTC_E_REMOVE_MAP_RES), + OSMO_VALUE_STRING(CLNTC_E_PUSH), + { 0, NULL } +}; + +static void clnt_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case CLNTC_E_TCP_UP: + osmo_fsm_inst_state_chg(fi, CLNTC_ST_ESTABLISHED, 10, 1); + break; + default: + OSMO_ASSERT(0); + } +} + +static void clnt_st_established(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct rspro_client_conn *conn = fi->priv; + const RsproPDU_t *pdu = data; + const ConnectClientReq_t *cclreq = NULL; + const ConnectBankReq_t *cbreq = NULL; + RsproPDU_t *resp = NULL; + + switch (event) { + case CLNTC_E_CLIENT_CONN: + cclreq = &pdu->msg.choice.connectClientReq; + /* save the [remote] component identity in 'conn' */ + rspro_comp_id_retrieve(&conn->comp_id, &cclreq->identity); + if (conn->comp_id.type != ComponentType_remsimClient) { + LOGPFSM(fi, "ConnectClientReq from identity != Client ?!?\n"); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + } + /* FIXME: determine client ID */ + //osmo_fsm_inst_update_id_f(fi, "C%u:%u", conn->bank.bank_id); + + /* reparent us from srv->connections to srv->clients */ + pthread_rwlock_wrlock(&conn->srv->rwlock); + llist_del(&conn->list); + llist_add_tail(&conn->list, &conn->srv->clients); + pthread_rwlock_unlock(&conn->srv->rwlock); + + osmo_fsm_inst_state_chg(fi, CLNTC_ST_CONNECTED, 0, 0); + + resp = rspro_gen_ConnectClientRes(&conn->srv->comp_id, ResultCode_ok); + client_conn_send(conn, resp); + break; + case CLNTC_E_BANK_CONN: + cbreq = &pdu->msg.choice.connectBankReq; + /* save the [remote] component identity in 'conn' */ + rspro_comp_id_retrieve(&conn->comp_id, &cbreq->identity); + if (conn->comp_id.type != ComponentType_remsimBankd) { + LOGPFSM(fi, "ConnectBankReq from identity != Bank ?!?\n"); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + } + conn->bank.bank_id = cbreq->bankId; + conn->bank.num_slots = cbreq->numberOfSlots; + osmo_fsm_inst_update_id_f(fi, "B%u", conn->bank.bank_id); + + /* reparent us from srv->connections to srv->banks */ + pthread_rwlock_wrlock(&conn->srv->rwlock); + llist_del(&conn->list); + llist_add_tail(&conn->list, &conn->srv->banks); + pthread_rwlock_unlock(&conn->srv->rwlock); + + /* send response to bank first */ + resp = rspro_gen_ConnectBankRes(&conn->srv->comp_id, ResultCode_ok); + client_conn_send(conn, resp); + + /* the state change will associate any pre-existing slotmaps */ + osmo_fsm_inst_state_chg(fi, CLNTC_ST_CONNECTED, 0, 0); + + osmo_fsm_inst_dispatch(fi, CLNTC_E_PUSH, NULL); + break; + default: + OSMO_ASSERT(0); + } +} + + +static void clnt_st_connected_cl_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ +#if 0 + struct rspro_client_conn *conn = fi->priv; + ClientSlot_t clslot; + RsproPDU_t *pdu; + + /* send configuration to this new client */ + client_slot2rspro(&clslot, FIXME); + pdu = rspro_gen_ConfigClientReq(&clslot, bankd_ip, bankd_port); + client_conn_send(conn, pdu); +#endif +} + +static void clnt_st_connected_bk_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct rspro_client_conn *conn = fi->priv; + struct slotmaps *slotmaps = conn->srv->slotmaps; + struct slot_mapping *map; + + LOGPFSM(fi, "Associating pre-existing slotmaps (if any)\n"); + /* Link all known mappings to this new bank */ + slotmaps_wrlock(slotmaps); + llist_for_each_entry(map, &slotmaps->mappings, list) { + if (map->bank.bank_id == conn->bank.bank_id) + _slotmap_state_change(map, SLMAP_S_NEW, &conn->bank.maps_new); + } + slotmaps_unlock(slotmaps); +} + +static void clnt_st_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct rspro_client_conn *conn = fi->priv; + switch (conn->comp_id.type) { + case ComponentType_remsimClient: + clnt_st_connected_cl_onenter(fi, prev_state); + break; + case ComponentType_remsimBankd: + clnt_st_connected_bk_onenter(fi, prev_state); + break; + default: + OSMO_ASSERT(0); + } +} + +static void clnt_st_connected(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct rspro_client_conn *conn = fi->priv; + struct slotmaps *slotmaps = conn->srv->slotmaps; + const struct RsproPDU_t *rx = NULL; + struct slot_mapping *map, *map2; + + switch (event) { + case CLNTC_E_CREATE_MAP_RES: + rx = data; + slotmaps_wrlock(slotmaps); + /* FIXME: resolve map by pdu->tag */ + /* as hack use first element of conn->maps_unack */ + map = llist_first_entry(&conn->bank.maps_unack, struct slot_mapping, bank_list); + if (!map) { + slotmaps_unlock(slotmaps); + LOGPFSM(fi, "CreateMapRes but no unacknowledged map"); + break; + } + _slotmap_state_change(map, SLMAP_S_ACTIVE, &conn->bank.maps_active); + slotmaps_unlock(slotmaps); + break; + case CLNTC_E_REMOVE_MAP_RES: + rx = data; + slotmaps_wrlock(slotmaps); + /* FIXME: resolve map by pdu->tag */ + /* as hack use first element of conn->maps_deleting */ + map = llist_first_entry(&conn->bank.maps_deleting, struct slot_mapping, bank_list); + if (!map) { + slotmaps_unlock(slotmaps); + LOGPFSM(fi, "RemoveMapRes but no unacknowledged map"); + break; + } + slotmaps_unlock(slotmaps); + /* slotmap_del() will remove it from both global and bank list */ + slotmap_del(map->maps, map); + break; + case CLNTC_E_PUSH: + slotmaps_wrlock(slotmaps); + /* send any pending create requests */ + llist_for_each_entry_safe(map, map2, &conn->bank.maps_new, bank_list) { + RsproPDU_t *pdu = slotmap2CreateMappingReq(map); + client_conn_send(conn, pdu); + _slotmap_state_change(map, SLMAP_S_UNACKNOWLEDGED, &conn->bank.maps_unack); + } + /* send any pending delete requests */ + llist_for_each_entry_safe(map, map2, &conn->bank.maps_delreq, bank_list) { + RsproPDU_t *pdu = slotmap2RemoveMappingReq(map); + client_conn_send(conn, pdu); + _slotmap_state_change(map, SLMAP_S_DELETING, &conn->bank.maps_deleting); + } + slotmaps_unlock(slotmaps); + break; + default: + OSMO_ASSERT(0); + } +} + +static void clnt_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct rspro_client_conn *conn = fi->priv; + + switch (event) { + case CLNTC_E_TCP_DOWN: + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + default: + OSMO_ASSERT(0); + } +} + +static int server_client_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct rspro_client_conn *conn = fi->priv; + + switch (fi->T) { + case 1: + /* No ClientConnectReq received:disconnect */ + return 1; /* ask core to terminate FSM */ + default: + OSMO_ASSERT(0); + } + return 0; +} + +static void server_client_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct rspro_client_conn *conn = fi->priv; + /* this call will destroy the IPA connection, which will in turn call closed_cb() + * which will try to deliver a E_TCP_DOWN event. Clear conn->fi to avoid that loop */ + conn->fi = NULL; + rspro_client_conn_destroy(conn); +} + +static const struct osmo_fsm_state server_client_fsm_states[] = { + [CLNTC_ST_INIT] = { + .name = "INIT", + .in_event_mask = S(CLNTC_E_TCP_UP), + .out_state_mask = S(CLNTC_ST_ESTABLISHED), + .action = clnt_st_init, + }, + [CLNTC_ST_ESTABLISHED] = { + .name = "ESTABLISHED", + .in_event_mask = S(CLNTC_E_CLIENT_CONN) | S(CLNTC_E_BANK_CONN), + .out_state_mask = S(CLNTC_ST_CONNECTED), + .action = clnt_st_established, + }, + [CLNTC_ST_CONNECTED] = { + .name = "CONNECTED", + .in_event_mask = S(CLNTC_E_CREATE_MAP_RES) | S(CLNTC_E_REMOVE_MAP_RES) | + S(CLNTC_E_PUSH), + .action = clnt_st_connected, + .onenter = clnt_st_connected_onenter, + }, +}; + +static struct osmo_fsm remsim_server_client_fsm = { + .name = "SERVER_CONN", + .states = server_client_fsm_states, + .num_states = ARRAY_SIZE(server_client_fsm_states), + .allstate_event_mask = S(CLNTC_E_TCP_DOWN), + .allstate_action = clnt_allstate_action, + .cleanup = server_client_cleanup, + .timer_cb = server_client_fsm_timer_cb, + .log_subsys = DMAIN, + .event_names = server_client_event_names, +}; + +struct osmo_fsm_inst *server_client_fsm_alloc(void *ctx, struct rspro_client_conn *conn) +{ + //const char *id = osmo_sock_get_name2(conn->peer->ofd.fd); + return osmo_fsm_inst_alloc(&remsim_server_client_fsm, ctx, conn, LOGL_DEBUG, NULL); +} + + +static __attribute__((constructor)) void on_dso_load(void) +{ + osmo_fsm_register(&remsim_server_client_fsm); +} + + +/*********************************************************************** + * IPA RSPRO Server + ***********************************************************************/ + +struct rspro_client_conn *_bankd_conn_by_id(struct rspro_server *srv, uint16_t bank_id) +{ + struct rspro_client_conn *conn; + llist_for_each_entry(conn, &srv->banks, list) { + if (conn->bank.bank_id == bank_id) + return conn; + } + return NULL; +} +struct rspro_client_conn *bankd_conn_by_id(struct rspro_server *srv, uint16_t bank_id) +{ + struct rspro_client_conn *conn; + pthread_rwlock_rdlock(&srv->rwlock); + conn = _bankd_conn_by_id(srv, bank_id); + pthread_rwlock_unlock(&srv->rwlock); + return conn; +} + +static int handle_rx_rspro(struct rspro_client_conn *conn, const RsproPDU_t *pdu) +{ + switch (pdu->msg.present) { + case RsproPDUchoice_PR_connectClientReq: + osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_CLIENT_CONN, (void *)pdu); + break; + case RsproPDUchoice_PR_connectBankReq: + osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_BANK_CONN, (void *)pdu); + break; + case RsproPDUchoice_PR_createMappingRes: + osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_CREATE_MAP_RES, (void *)pdu); + break; + case RsproPDUchoice_PR_removeMappingRes: + osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_REMOVE_MAP_RES, (void *)pdu); + break; + default: + LOGPFSML(conn->fi, LOGL_ERROR, "Received unknown/unimplemented RSPRO msg_type %d\n", + pdu->msg.present); + return -1; + } + return 0; +} + +/* data was received from one of the client connections to the RSPRO socket */ +static int sock_read_cb(struct ipa_server_conn *peer, struct msgb *msg) +{ + struct ipaccess_head *hh = (struct ipaccess_head *) msg->data; + struct ipaccess_head_ext *he = (struct ipaccess_head_ext *) msgb_l2(msg); + struct rspro_client_conn *conn = peer->data; + RsproPDU_t *pdu; + int rc; + + if (msgb_length(msg) < sizeof(*hh)) + goto invalid; + msg->l2h = &hh->data[0]; + if (hh->proto != IPAC_PROTO_OSMO) + goto invalid; + if (!he || msgb_l2len(msg)< sizeof(*he)) + goto invalid; + msg->l2h = &he->data[0]; + + if (he->proto!= IPAC_PROTO_EXT_RSPRO) + goto invalid; + + pdu = rspro_dec_msg(msg); + if (!pdu) + goto invalid; + + rc = handle_rx_rspro(conn, pdu); + ASN_STRUCT_FREE(asn_DEF_RsproPDU, pdu); + return rc; + +invalid: + msgb_free(msg); + return -1; +} + +static int sock_closed_cb(struct ipa_server_conn *peer) +{ + struct rspro_client_conn *conn = peer->data; + if (conn->fi) + osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_TCP_DOWN, NULL); + /* FIXME: who cleans up conn? */ + /* ipa server code relases 'peer' just after this */ + return 0; +} + +/* a new TCP connection was accepted on the RSPRO server socket */ +static int accept_cb(struct ipa_server_link *link, int fd) +{ + struct rspro_server *srv = link->data; + struct rspro_client_conn *conn; + + conn = talloc_zero(srv, struct rspro_client_conn); + OSMO_ASSERT(conn); + + conn->srv = srv; + /* don't allocate peer under 'conn', as it must survive 'conn' during teardown */ + conn->peer = ipa_server_conn_create(link, link, fd, sock_read_cb, sock_closed_cb, conn); + if (!conn->peer) + goto out_err; + + /* don't allocate 'fi' as slave from 'conn', as 'fi' needs to survive 'conn' during + * teardown */ + conn->fi = server_client_fsm_alloc(srv, conn); + if (!conn->fi) + goto out_err_conn; + + INIT_LLIST_HEAD(&conn->bank.maps_new); + INIT_LLIST_HEAD(&conn->bank.maps_unack); + INIT_LLIST_HEAD(&conn->bank.maps_active); + INIT_LLIST_HEAD(&conn->bank.maps_delreq); + INIT_LLIST_HEAD(&conn->bank.maps_deleting); + + pthread_rwlock_wrlock(&conn->srv->rwlock); + llist_add_tail(&conn->list, &srv->connections); + pthread_rwlock_unlock(&conn->srv->rwlock); + + osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_TCP_UP, NULL); + return 0; + +out_err_conn: + ipa_server_conn_destroy(conn->peer); + /* the above will free 'conn' down the chain */ + return -1; +out_err: + talloc_free(conn); + return -1; +} + +/* call-back if we were triggered by a rest_api thread */ +int event_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct rspro_server *srv = ofd->data; + struct rspro_client_conn *conn; + bool non_empty_new, non_empty_del; + uint64_t value; + int rc; + + /* read from the socket to "confirm" the event and make it non-readable again */ + rc = read(ofd->fd, &value, 8); + if (rc < 8) { + fprintf(stderr, "Error reading eventfd: %d\n", rc); + return rc; + } + + printf("rspro_server: Event FD arrived, checking for any pending work\n"); + + pthread_rwlock_rdlock(&srv->rwlock); + llist_for_each_entry(conn, &srv->banks, list) { + slotmaps_rdlock(srv->slotmaps); + non_empty_new = llist_empty(&conn->bank.maps_new); + non_empty_del = llist_empty(&conn->bank.maps_delreq); + slotmaps_unlock(srv->slotmaps); + + /* trigger FSM to send any pending new/deleted maps */ + if (non_empty_new || non_empty_del) + osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_PUSH, NULL); + } + pthread_rwlock_unlock(&srv->rwlock); + + return 0; +} + +/* unlink all slotmaps from any of the lists of this conn->bank.maps_* */ +static void _unlink_all_slotmaps(struct rspro_client_conn *conn) +{ + struct slot_mapping *smap, *smap2; + + llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_new, bank_list) { + /* unlink from list and keep in state NEW */ + _slotmap_state_change(smap, SLMAP_S_NEW, NULL); + } + llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_unack, bank_list) { + /* unlink from list and change to state NEW */ + _slotmap_state_change(smap, SLMAP_S_NEW, NULL); + } + llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_active, bank_list) { + /* unlink from list and change to state NEW */ + _slotmap_state_change(smap, SLMAP_S_NEW, NULL); + } + llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_delreq, bank_list) { + /* unlink from list and delete */ + _slotmap_del(smap->maps, smap); + } + llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_deleting, bank_list) { + /* unlink from list and delete */ + _slotmap_del(smap->maps, smap); + } +} + +/* only to be used by the FSM cleanup. */ +static void rspro_client_conn_destroy(struct rspro_client_conn *conn) +{ + /* this will internally call closed_cb() which will dispatch a TCP_DOWN event */ + ipa_server_conn_destroy(conn->peer); + conn->peer = NULL; + + /* ensure all slotmaps are unlinked + returned to NEW or deleted */ + slotmaps_wrlock(conn->srv->slotmaps); + _unlink_all_slotmaps(conn); + slotmaps_unlock(conn->srv->slotmaps); + + pthread_rwlock_wrlock(&conn->srv->rwlock); + llist_del(&conn->list); + pthread_rwlock_unlock(&conn->srv->rwlock); + + talloc_free(conn); +} + + +struct rspro_server *rspro_server_create(void *ctx, const char *host, uint16_t port) + +{ + struct rspro_server *srv = talloc_zero(ctx, struct rspro_server); + OSMO_ASSERT(srv); + + pthread_rwlock_init(&srv->rwlock, NULL); + pthread_rwlock_wrlock(&srv->rwlock); + INIT_LLIST_HEAD(&srv->connections); + INIT_LLIST_HEAD(&srv->clients); + INIT_LLIST_HEAD(&srv->banks); + pthread_rwlock_unlock(&srv->rwlock); + + srv->link = ipa_server_link_create(ctx, NULL, host, port, accept_cb, srv); + ipa_server_link_open(srv->link); + + return srv; +} + +void rspro_server_destroy(struct rspro_server *srv) +{ + /* FIXME: clear all lists */ + + ipa_server_link_destroy(srv->link); + srv->link = NULL; + talloc_free(srv); +} diff --git a/src/server/rspro_server.h b/src/server/rspro_server.h new file mode 100644 index 0000000..9001192 --- /dev/null +++ b/src/server/rspro_server.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "rspro_util.h" +#include "slotmap.h" + +struct rspro_server { + struct ipa_server_link *link; + /* list of rspro_client_conn */ + struct llist_head connections; + struct llist_head clients; + struct llist_head banks; + /* rwlock protecting any of the lists above */ + pthread_rwlock_t rwlock; + + struct slotmaps *slotmaps; + + /* our own (server) component identity */ + struct app_comp_id comp_id; +}; + +/* representing a single client connection to an RSPRO server */ +struct rspro_client_conn { + /* global list of connections */ + struct llist_head list; + /* back-pointer to rspro_server */ + struct rspro_server *srv; + /* reference to the underlying IPA server connection */ + struct ipa_server_conn *peer; + /* FSM instance for this connection */ + struct osmo_fsm_inst *fi; + /* remote component identity (after it has been received) */ + struct app_comp_id comp_id; + + struct { + struct llist_head maps_new; + struct llist_head maps_unack; + struct llist_head maps_active; + struct llist_head maps_delreq; + struct llist_head maps_deleting; + uint16_t bank_id; + uint16_t num_slots; + } bank; +}; + +struct rspro_server *rspro_server_create(void *ctx, const char *host, uint16_t port); +void rspro_server_destroy(struct rspro_server *srv); +int event_fd_cb(struct osmo_fd *ofd, unsigned int what); + +struct rspro_client_conn *_bankd_conn_by_id(struct rspro_server *srv, uint16_t bank_id); +struct rspro_client_conn *bankd_conn_by_id(struct rspro_server *srv, uint16_t bank_id);