Add initial remsim-bankd skeleton
This is not a complete program yet, but a rough initial skeleton with
the key data structures in place, as well as the thread / locking model
in place.
Change-Id: I5ad5a1a4918b8eacdaeb7e709ff05dc056346752
diff --git a/src/Makefile.am b/src/Makefile.am
index 05ac9ae..f1731fa 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,10 +1,24 @@
SUBDIRS = rspro
AM_CFLAGS = -Wall -I$(top_srcdir)/include -I$(top_builddir)/include \
- $(OSMOCORE_CFLAGS) $(ASN1C_CFLAGS)
+ $(OSMOCORE_CFLAGS) $(OSMOGSM_CFLAGS) $(OSMOABIS_CFLAGS) \
+ $(ASN1C_CFLAGS) $(PCSC_CFLAGS)
RSPRO_LIBVERSION=0:0:0
lib_LTLIBRARIES = libosmo-rspro.la
libosmo_rspro_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(RSPRO_LIBVERSION)
-libosmo_rspro_la_LIBADD = $(OSMOCORE_LIBS) $(ASN1C_LIBS) rspro/libosmo-asn1-rspro.la
-libosmo_rspro_la_SOURCES = rspro_util.c
+libosmo_rspro_la_LIBADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \
+ $(ASN1C_LIBS) rspro/libosmo-asn1-rspro.la
+libosmo_rspro_la_SOURCES = rspro_util.c rspro_client.c
+
+noinst_HEADERS = bankd.h internal.h
+
+bin_PROGRAMS = pcsc_test remsim-bankd
+
+pcsc_test_SOURCES = driver_core.c driver_pcsc.c main.c
+pcsc_test_LDADD = $(OSMOCORE_LIBS) \
+ $(ASN1C_LIBS) $(PCSC_LIBS) libosmo-rspro.la
+
+remsim_bankd_SOURCES = bankd_slotmap.c bankd_main.c
+remsim_bankd_LDADD = $(OSMOCORE_LIBS) \
+ $(ASN1C_LIBS) $(PCSC_LIBS) libosmo-rspro.la
diff --git a/src/bankd.h b/src/bankd.h
new file mode 100644
index 0000000..eabf132
--- /dev/null
+++ b/src/bankd.h
@@ -0,0 +1,118 @@
+#pragma once
+
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <pthread.h>
+
+#include <wintypes.h>
+#include <winscard.h>
+
+#include <osmocom/core/linuxlist.h>
+
+struct bankd;
+
+struct bank_slot {
+ uint16_t bank_id;
+ uint16_t slot_nr;
+};
+
+static inline bool bank_slot_equals(const struct bank_slot *a, const struct bank_slot *b)
+{
+ if (a->bank_id == b->bank_id && a->slot_nr == b->slot_nr)
+ return true;
+ else
+ return false;
+}
+
+struct client_slot {
+ uint16_t client_id;
+ uint16_t slot_nr;
+};
+
+static inline bool client_slot_equals(const struct client_slot *a, const struct client_slot *b)
+{
+ if (a->client_id == b->client_id && a->slot_nr == b->slot_nr)
+ return true;
+ else
+ return false;
+}
+
+/* slot mappings are created / removed by the server */
+struct bankd_slot_mapping {
+ /* global lits of bankd slot mappings */
+ struct llist_head list;
+ /* slot on bank side */
+ struct bank_slot bank;
+ /* slot on client side */
+ struct client_slot client;
+};
+
+/* thread-safe lookup of map by client:slot */
+struct bankd_slot_mapping *bankd_slotmap_by_client(struct bankd *bankd,
+ const struct client_slot *client);
+
+/* thread-safe lookup of map by bank:slot */
+struct bankd_slot_mapping *bankd_slotmap_by_bank(struct bankd *bankd, const struct bank_slot *bank);
+
+/* thread-safe creating of a new bank<->client map */
+int bankd_slotmap_add(struct bankd *bankd, const struct bank_slot *bank,
+ const struct client_slot *client);
+
+/* thread-safe removal of a bank<->client map */
+void bankd_slotmap_del(struct bankd *bankd, struct bankd_slot_mapping *map);
+
+
+/* bankd worker instance; one per card/slot, includes thread */
+struct bankd_worker {
+ /* global list of workers */
+ struct llist_head list;
+ /* back-pointer to bankd */
+ struct bankd *bankd;
+
+ /* slot number we are representing */
+ struct bank_slot slot;
+
+ /* thread of this worker. */
+ pthread_t thread;
+
+ /* File descriptor of the TCP connection to the remsim-client (modem) */
+ struct {
+ int fd;
+ struct sockaddr_storage peer_addr;
+ socklen_t peer_addr_len;
+ } client;
+
+ struct {
+ const char *name;
+ union {
+ struct {
+ /* PC/SC context / application handle */
+ SCARDCONTEXT hContext;
+ /* PC/SC card handle */
+ SCARDHANDLE hCard;
+ } pcsc;
+ };
+ } reader;
+};
+
+
+/* global bank deamon */
+struct bankd {
+ struct {
+ uint16_t bank_id;
+ } cfg;
+
+ /* TCP socket at which we are listening */
+ int accept_fd;
+
+ /* list of slit mappings. only ever modified in main thread! */
+ struct llist_head slot_mappings;
+ pthread_rwlock_t slot_mappings_rwlock;
+
+ /* list of bankd_workers. accessed/modified by multiple threads; protected by mutex */
+ struct llist_head workers;
+ pthread_mutex_t workers_mutex;
+};
diff --git a/src/bankd_main.c b/src/bankd_main.c
new file mode 100644
index 0000000..f6eb64f
--- /dev/null
+++ b/src/bankd_main.c
@@ -0,0 +1,285 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <pthread.h>
+
+#include <wintypes.h>
+#include <winscard.h>
+#include <pcsclite.h>
+
+#include <osmocom/core/linuxlist.h>
+
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <asn1c/asn_application.h>
+#include <osmocom/rspro/RsproPDU.h>
+
+#include "bankd.h"
+
+__thread void *talloc_asn1_ctx;
+
+static void *worker_main(void *arg);
+
+/***********************************************************************
+* bankd core / main thread
+***********************************************************************/
+
+static void bankd_init(struct bankd *bankd)
+{
+ /* intialize members of 'bankd' */
+ INIT_LLIST_HEAD(&bankd->slot_mappings);
+ pthread_rwlock_init(&bankd->slot_mappings_rwlock, NULL);
+ INIT_LLIST_HEAD(&bankd->workers);
+ pthread_mutex_init(&bankd->workers_mutex, NULL);
+}
+
+/* create + start a new bankd_worker thread */
+static struct bankd_worker *bankd_create_worker(struct bankd *bankd)
+{
+ struct bankd_worker *worker;
+ int rc;
+
+ worker = talloc_zero(bankd, struct bankd_worker);
+ if (!worker)
+ return NULL;
+
+ worker->bankd = bankd;
+
+ /* in the initial state, the worker has no client.fd, bank_slot or pcsc handle yet */
+
+ rc = pthread_create(&worker->thread, NULL, worker_main, worker);
+ if (rc != 0) {
+ talloc_free(worker);
+ return NULL;
+ }
+
+ pthread_mutex_lock(&bankd->workers_mutex);
+ llist_add_tail(&worker->list, &bankd->workers);
+ pthread_mutex_unlock(&bankd->workers_mutex);
+
+ return worker;
+}
+
+static bool terminate = false;
+
+int main(int argc, char **argv)
+{
+ struct bankd *bankd = talloc_zero(NULL, struct bankd);
+ int i, rc;
+
+ OSMO_ASSERT(bankd);
+ bankd_init(bankd);
+
+ for (i = 0; i < 10; i++) {
+ struct bankd_worker *w;
+ w = bankd_create_worker(bankd);
+ if (!w)
+ exit(21);
+ }
+
+ while (1) {
+ if (terminate)
+ break;
+ }
+
+ talloc_free(bankd);
+ exit(0);
+}
+
+
+
+/***********************************************************************
+ * bankd worker thread
+ ***********************************************************************/
+
+#define PCSC_ERROR(rv, text) \
+if (rv != SCARD_S_SUCCESS) { \
+ fprintf(stderr, text ": %s (0x%lX)\n", pcsc_stringify_error(rv), rv); \
+ goto end; \
+} else { \
+ printf(text ": OK\n\n"); \
+}
+
+
+static void worker_cleanup(void *arg)
+{
+ struct bankd_worker *worker = (struct bankd_worker *) arg;
+ struct bankd *bankd = worker->bankd;
+
+ /* FIXME: should we still do this? in the thread ?!? */
+ pthread_mutex_lock(&bankd->workers_mutex);
+ llist_del(&worker->list);
+ talloc_free(worker); /* FIXME: is this safe? */
+ pthread_mutex_unlock(&bankd->workers_mutex);
+}
+
+
+#if 0
+/* function running inside a worker thread; doing some initialization */
+static void worker_init(struct bankd_worker *worker)
+{
+ int rc;
+
+ /* push cleanup helper */
+ pthread_cleanup_push(&worker_cleanup, worker);
+
+ /* The PC/SC context must be created inside the thread where we'll later use it */
+ rc = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &worker->reader.pcsc.hContext);
+ PCSC_ERROR(rc, "SCardEstablishContext")
+
+ rc = SCardConnect(worker->reader.pcsc.hContext, worker->reader.name, SCARD_SHARE_SHARED,
+ SCARD_PROTOCOL_T0, &worker->reader.pcsc.hCard, NULL);
+ PCSC_ERROR(rc, "SCardConnect")
+
+ return;
+end:
+ pthread_exit(NULL);
+}
+#endif
+
+
+static int blocking_ipa_read(int fd, uint8_t *buf, unsigned int buf_size)
+{
+ struct ipaccess_head *hh;
+ uint16_t len;
+ int needed, rc;
+
+ if (buf_size < sizeof(*hh))
+ return -1;
+
+ hh = (struct ipaccess_head *) buf;
+
+ /* 1) blocking read from the socket (IPA header) */
+ rc = read(fd, buf, sizeof(*hh));
+ if (rc < sizeof(*hh))
+ return -2;
+
+ len = ntohs(hh->len);
+ needed = len; //- sizeof(*hh);
+
+ /* 2) blocking read from the socket (payload) */
+ rc = read(fd, buf+sizeof(*hh), needed);
+ if (rc < needed)
+ return -3;
+
+ return len;
+}
+
+/* handle one incoming RSPRO message from a client inside a worker thread */
+static int worker_handle_rspro(struct bankd_worker *worker, const RsproPDU_t *pdu)
+{
+ switch (pdu->msg.present) {
+ case RsproPDUchoice_PR_connectClientReq:
+ /* FIXME */
+ break;
+ case RsproPDUchoice_PR_tpduModemToCard:
+ /* FIXME */
+ break;
+ case RsproPDUchoice_PR_clientSlotStatusInd:
+ /* FIXME */
+ break;
+ default:
+ return -100;
+ }
+
+ return 0;
+}
+
+/* body of the main transceive loop */
+static int worker_transceive_loop(struct bankd_worker *worker)
+{
+ struct ipaccess_head *hh;
+ struct ipaccess_head_ext *hh_ext;
+ uint8_t buf[65536]; /* maximum length expressed in 16bit length field */
+ asn_dec_rval_t rval;
+ int data_len, rc;
+ RsproPDU_t *pdu;
+
+ /* 1) blocking read of entire IPA message from the socket */
+ rc = blocking_ipa_read(worker->client.fd, buf, sizeof(buf));
+ if (rc < 0)
+ return rc;
+ data_len = rc;
+
+ hh = (struct ipaccess_head *) buf;
+ if (hh->proto != IPAC_PROTO_OSMO)
+ return -4;
+
+ hh_ext = (struct ipaccess_head_ext *) buf + sizeof(*hh);
+ if (data_len < sizeof(*hh_ext))
+ return -5;
+ data_len -= sizeof(*hh_ext);
+ if (hh_ext->proto != IPAC_PROTO_EXT_RSPRO)
+ return -6;
+
+ /* 2) ASN1 BER decode of the message */
+ rval = ber_decode(NULL, &asn_DEF_RsproPDU, (void **) &pdu, hh_ext->data, data_len);
+ if (rval.code != RC_OK)
+ return -7;
+
+ /* 3) handling of the message, possibly resulting in PCSC commands */
+ rc = worker_handle_rspro(worker, pdu);
+ ASN_STRUCT_FREE(asn_DEF_RsproPDU, pdu);
+ if (rc < 0)
+ return rc;
+
+ /* everything OK if we reach here */
+ return 0;
+}
+
+/* worker thread main function */
+static void *worker_main(void *arg)
+{
+ struct bankd_worker *worker = (struct bankd_worker *) arg;
+ void *top_ctx;
+ int rc;
+
+ /* not permitted in multithreaded environment */
+ talloc_disable_null_tracking();
+ top_ctx = talloc_named_const(NULL, 0, "top");
+ talloc_asn1_ctx = talloc_named_const(top_ctx, 0, "asn1");
+
+ /* push cleanup helper */
+ pthread_cleanup_push(&worker_cleanup, worker);
+
+ /* we continuously perform the same loop here, recycling the worker thread
+ * once the client connection is gone or we have some trouble with the card/reader */
+ while (1) {
+ worker->client.peer_addr_len = sizeof(worker->client.peer_addr);
+
+ /* first wait for an incoming TCP connection */
+ rc = accept(worker->bankd->accept_fd, (struct sockaddr *) &worker->client.peer_addr,
+ &worker->client.peer_addr_len);
+ if (rc < 0) {
+ continue;
+ }
+ worker->client.fd = rc;
+
+ /* run the main worker transceive loop body until there was some error */
+ while (1) {
+ rc = worker_transceive_loop(worker);
+ if (rc < 0)
+ break;
+ }
+
+ /* clean-up: reset to sane state */
+ if (worker->reader.pcsc.hCard) {
+ SCardDisconnect(worker->reader.pcsc.hCard, SCARD_UNPOWER_CARD);
+ worker->reader.pcsc.hCard = 0;
+ }
+ if (worker->reader.pcsc.hContext) {
+ SCardReleaseContext(worker->reader.pcsc.hContext);
+ worker->reader.pcsc.hContext = 0;
+ }
+ if (worker->client.fd >= 0)
+ close(worker->client.fd);
+ worker->client.fd = -1;
+ }
+
+ pthread_cleanup_pop(1);
+ talloc_free(top_ctx);
+ pthread_exit(NULL);
+}
diff --git a/src/bankd_slotmap.c b/src/bankd_slotmap.c
new file mode 100644
index 0000000..373399b
--- /dev/null
+++ b/src/bankd_slotmap.c
@@ -0,0 +1,100 @@
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <pthread.h>
+
+#include <talloc.h>
+
+#include <osmocom/core/linuxlist.h>
+
+#include "bankd.h"
+
+/* thread-safe lookup of map by client:slot */
+struct bankd_slot_mapping *bankd_slotmap_by_client(struct bankd *bankd, const struct client_slot *client)
+{
+ struct bankd_slot_mapping *map;
+
+ pthread_rwlock_rdlock(&bankd->slot_mappings_rwlock);
+ llist_for_each_entry(map, &bankd->slot_mappings, list) {
+ if (client_slot_equals(&map->client, client)) {
+ pthread_rwlock_unlock(&bankd->slot_mappings_rwlock);
+ return map;
+ }
+ }
+ pthread_rwlock_unlock(&bankd->slot_mappings_rwlock);
+ return NULL;
+}
+
+/* thread-safe lookup of map by bank:slot */
+struct bankd_slot_mapping *bankd_slotmap_by_bank(struct bankd *bankd, const struct bank_slot *bank)
+{
+ struct bankd_slot_mapping *map;
+
+ pthread_rwlock_rdlock(&bankd->slot_mappings_rwlock);
+ llist_for_each_entry(map, &bankd->slot_mappings, list) {
+ if (bank_slot_equals(&map->bank, bank)) {
+ pthread_rwlock_unlock(&bankd->slot_mappings_rwlock);
+ return map;
+ }
+ }
+ pthread_rwlock_unlock(&bankd->slot_mappings_rwlock);
+ return NULL;
+
+}
+
+/* thread-safe creating of a new bank<->client map */
+int bankd_slotmap_add(struct bankd *bankd, const struct bank_slot *bank, const struct client_slot *client)
+{
+ struct bankd_slot_mapping *map;
+
+ /* We assume a single thread (main thread) will ever update the mappings,
+ * and hence we don't have any races by first grabbing + releasing the read
+ * lock twice before grabbing the writelock below */
+
+ map = bankd_slotmap_by_bank(bankd, bank);
+ if (map) {
+ fprintf(stderr, "BANKD %u:%u already in use, cannot add new map\n",
+ bank->bank_id, bank->slot_nr);
+ return -EBUSY;
+ }
+
+ map = bankd_slotmap_by_client(bankd, client);
+ if (map) {
+ fprintf(stderr, "CLIENT %u:%u already in use, cannot add new map\n",
+ client->client_id, client->slot_nr);
+ return -EBUSY;
+ }
+
+ /* allocate new mapping and add to list of mappings */
+ map = talloc_zero(bankd, struct bankd_slot_mapping);
+ if (!map)
+ return -ENOMEM;
+
+ map->bank = *bank;
+ map->client = *client;
+
+ pthread_rwlock_wrlock(&bankd->slot_mappings_rwlock);
+ llist_add_tail(&map->list, &bankd->slot_mappings);
+ pthread_rwlock_unlock(&bankd->slot_mappings_rwlock);
+
+ printf("Added Slot Map B(%u:%u) <-> C(%u:%u)\n",
+ map->client.client_id, map->client.slot_nr, map->bank.bank_id, map->bank.slot_nr);
+
+ return 0;
+}
+
+/* thread-safe removal of a bank<->client map */
+void bankd_slotmap_del(struct bankd *bankd, struct bankd_slot_mapping *map)
+{
+ printf("Deleting Slot Map B(%u:%u) <-> C(%u:%u)\n",
+ map->client.client_id, map->client.slot_nr, map->bank.bank_id, map->bank.slot_nr);
+
+ pthread_rwlock_wrlock(&bankd->slot_mappings_rwlock);
+ llist_del(&map->list);
+ pthread_rwlock_unlock(&bankd->slot_mappings_rwlock);
+
+ talloc_free(map);
+}
diff --git a/src/driver_pcsc.c b/src/driver_pcsc.c
index d028e55..5102512 100644
--- a/src/driver_pcsc.c
+++ b/src/driver_pcsc.c
@@ -83,6 +83,7 @@
static int pcsc_reader_open_slot(struct card_reader_slot *slot)
{
+#if 0
struct osim_card_hdl *card;
LONG rc;
@@ -101,7 +102,8 @@
rh->card = card;
end:
- return NULL;
+#endif
+ return -1;
}
diff --git a/src/main.c b/src/main.c
index 977785c..67b7a41 100644
--- a/src/main.c
+++ b/src/main.c
@@ -36,7 +36,12 @@
#include "internal.h"
+static void *g_ctx;
+__thread void *talloc_asn1_ctx;
+
int main(int argc, char **argv)
{
- card_readers_probe(NULL);
+ g_ctx = talloc_named_const(NULL, 0, "main");
+ talloc_asn1_ctx = talloc_named_const(g_ctx, 0, "asn1_context");
+ card_readers_probe(g_ctx);
}
diff --git a/src/rspro_client.c b/src/rspro_client.c
new file mode 100644
index 0000000..dcc9ed9
--- /dev/null
+++ b/src/rspro_client.c
@@ -0,0 +1,296 @@
+/* Generic Subscriber Update Protocol client */
+
+/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * 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 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/rspro/rspro_client.h>
+
+#include <osmocom/abis/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+
+#include <errno.h>
+#include <string.h>
+
+static void start_test_procedure(struct osmo_rspro_client *rsproc);
+
+static void rspro_client_send_ping(struct osmo_rspro_client *rsproc)
+{
+ struct msgb *msg = osmo_rspro_client_msgb_alloc();
+
+ msg->l2h = msgb_put(msg, 1);
+ msg->l2h[0] = IPAC_MSGT_PING;
+ ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS);
+ ipa_client_conn_send(rsproc->link, msg);
+}
+
+static int rspro_client_connect(struct osmo_rspro_client *rsproc)
+{
+ int rc;
+
+ if (rsproc->is_connected)
+ return 0;
+
+ if (osmo_timer_pending(&rsproc->connect_timer)) {
+ LOGP(DLRSPRO, LOGL_DEBUG,
+ "RSPRO connect: connect timer already running\n");
+ osmo_timer_del(&rsproc->connect_timer);
+ }
+
+ if (osmo_timer_pending(&rsproc->ping_timer)) {
+ LOGP(DLRSPRO, LOGL_DEBUG,
+ "RSPRO connect: ping timer already running\n");
+ osmo_timer_del(&rsproc->ping_timer);
+ }
+
+ if (ipa_client_conn_clear_queue(rsproc->link) > 0)
+ LOGP(DLRSPRO, LOGL_DEBUG, "RSPRO connect: discarded stored messages\n");
+
+ rc = ipa_client_conn_open(rsproc->link);
+
+ if (rc >= 0) {
+ LOGP(DLRSPRO, LOGL_NOTICE, "RSPRO connecting to %s:%d\n",
+ rsproc->link->addr, rsproc->link->port);
+ return 0;
+ }
+
+ LOGP(DLRSPRO, LOGL_ERROR, "RSPRO failed to connect to %s:%d: %s\n",
+ rsproc->link->addr, rsproc->link->port, strerror(-rc));
+
+ if (rc == -EBADF || rc == -ENOTSOCK || rc == -EAFNOSUPPORT ||
+ rc == -EINVAL)
+ return rc;
+
+ osmo_timer_schedule(&rsproc->connect_timer,
+ OSMO_RSPRO_CLIENT_RECONNECT_INTERVAL, 0);
+
+ LOGP(DLRSPRO, LOGL_INFO, "Scheduled timer to retry RSPRO connect to %s:%d\n",
+ rsproc->link->addr, rsproc->link->port);
+
+ return 0;
+}
+
+static void connect_timer_cb(void *rsproc_)
+{
+ struct osmo_rspro_client *rsproc = rsproc_;
+
+ if (rsproc->is_connected)
+ return;
+
+ rspro_client_connect(rsproc);
+}
+
+static void client_send(struct osmo_rspro_client *rsproc, int proto_ext,
+ struct msgb *msg_tx)
+{
+ ipa_prepend_header_ext(msg_tx, proto_ext);
+ ipa_msg_push_header(msg_tx, IPAC_PROTO_OSMO);
+ ipa_client_conn_send(rsproc->link, msg_tx);
+ /* msg_tx is now queued and will be freed. */
+}
+
+static void rspro_client_updown_cb(struct ipa_client_conn *link, int up)
+{
+ struct osmo_rspro_client *rsproc = link->data;
+
+ LOGP(DLRSPRO, LOGL_INFO, "RSPRO link to %s:%d %s\n",
+ link->addr, link->port, up ? "UP" : "DOWN");
+
+ rsproc->is_connected = up;
+
+ if (up) {
+ start_test_procedure(rsproc);
+ osmo_timer_del(&rsproc->connect_timer);
+ } else {
+ osmo_timer_del(&rsproc->ping_timer);
+
+ osmo_timer_schedule(&rsproc->connect_timer,
+ OSMO_RSPRO_CLIENT_RECONNECT_INTERVAL, 0);
+ }
+}
+
+static int rspro_client_read_cb(struct ipa_client_conn *link, 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 osmo_rspro_client *rsproc = (struct osmo_rspro_client *)link->data;
+ int rc;
+ struct ipaccess_unit ipa_dev = {
+ /* see rspro_client_create() on const vs non-const */
+ .unit_name = (char*)rsproc->unit_name,
+ };
+
+ OSMO_ASSERT(ipa_dev.unit_name);
+
+ msg->l2h = &hh->data[0];
+
+ rc = ipaccess_bts_handle_ccm(link, &ipa_dev, msg);
+
+ if (rc < 0) {
+ LOGP(DLRSPRO, LOGL_NOTICE,
+ "RSPRO received an invalid IPA/CCM message from %s:%d\n",
+ link->addr, link->port);
+ /* Link has been closed */
+ rsproc->is_connected = 0;
+ msgb_free(msg);
+ return -1;
+ }
+
+ if (rc == 1) {
+ uint8_t msg_type = *(msg->l2h);
+ /* CCM message */
+ if (msg_type == IPAC_MSGT_PONG) {
+ LOGP(DLRSPRO, LOGL_DEBUG, "RSPRO receiving PONG\n");
+ rsproc->got_ipa_pong = 1;
+ }
+
+ msgb_free(msg);
+ return 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) {
+ OSMO_ASSERT(rsproc->read_cb != NULL);
+ rsproc->read_cb(rsproc, msg);
+ /* expecting read_cb() to free msg */
+ } else
+ goto invalid;
+
+ return 0;
+
+invalid:
+ LOGP(DLRSPRO, LOGL_NOTICE,
+ "RSPRO received an invalid IPA message from %s:%d, size = %d\n",
+ link->addr, link->port, msgb_length(msg));
+
+ msgb_free(msg);
+ return -1;
+}
+
+static void ping_timer_cb(void *rsproc_)
+{
+ struct osmo_rspro_client *rsproc = rsproc_;
+
+ LOGP(DLRSPRO, LOGL_INFO, "RSPRO ping callback (%s, %s PONG)\n",
+ rsproc->is_connected ? "connected" : "not connected",
+ rsproc->got_ipa_pong ? "got" : "didn't get");
+
+ if (rsproc->got_ipa_pong) {
+ start_test_procedure(rsproc);
+ return;
+ }
+
+ LOGP(DLRSPRO, LOGL_NOTICE, "RSPRO ping timed out, reconnecting\n");
+ ipa_client_conn_close(rsproc->link);
+ rsproc->is_connected = 0;
+
+ rspro_client_connect(rsproc);
+}
+
+static void start_test_procedure(struct osmo_rspro_client *rsproc)
+{
+ osmo_timer_setup(&rsproc->ping_timer, ping_timer_cb, rsproc);
+
+ rsproc->got_ipa_pong = 0;
+ osmo_timer_schedule(&rsproc->ping_timer, OSMO_RSPRO_CLIENT_PING_INTERVAL, 0);
+ LOGP(DLRSPRO, LOGL_DEBUG, "RSPRO sending PING\n");
+ rspro_client_send_ping(rsproc);
+}
+
+struct osmo_rspro_client *osmo_rspro_client_create(void *talloc_ctx,
+ const char *unit_name,
+ const char *ip_addr,
+ unsigned int tcp_port,
+ osmo_rspro_client_read_cb_t read_cb)
+{
+ struct osmo_rspro_client *rsproc;
+ int rc;
+
+ rsproc = talloc_zero(talloc_ctx, struct osmo_rspro_client);
+ OSMO_ASSERT(rsproc);
+
+ /* struct ipaccess_unit has a non-const unit_name, so let's copy to be
+ * able to have a non-const unit_name here as well. To not taint the
+ * public rspro_client API, let's store it in a const char* anyway. */
+ rsproc->unit_name = talloc_strdup(rsproc, unit_name);
+ OSMO_ASSERT(rsproc->unit_name);
+
+ rsproc->link = ipa_client_conn_create(rsproc,
+ /* no e1inp */ NULL,
+ 0,
+ ip_addr, tcp_port,
+ rspro_client_updown_cb,
+ rspro_client_read_cb,
+ /* default write_cb */ NULL,
+ rsproc);
+ if (!rsproc->link)
+ goto failed;
+
+ osmo_timer_setup(&rsproc->connect_timer, connect_timer_cb, rsproc);
+
+ rc = rspro_client_connect(rsproc);
+ if (rc < 0)
+ goto failed;
+
+ rsproc->read_cb = read_cb;
+
+ return rsproc;
+
+failed:
+ osmo_rspro_client_destroy(rsproc);
+ return NULL;
+}
+
+void osmo_rspro_client_destroy(struct osmo_rspro_client *rsproc)
+{
+ osmo_timer_del(&rsproc->connect_timer);
+ osmo_timer_del(&rsproc->ping_timer);
+
+ if (rsproc->link) {
+ ipa_client_conn_close(rsproc->link);
+ ipa_client_conn_destroy(rsproc->link);
+ rsproc->link = NULL;
+ }
+ talloc_free(rsproc);
+}
+
+int osmo_rspro_client_send(struct osmo_rspro_client *rsproc, struct msgb *msg)
+{
+ if (!rsproc || !rsproc->is_connected) {
+ LOGP(DLRSPRO, LOGL_ERROR, "RSPRO not connected, unable to send %s\n", msgb_hexdump(msg));
+ msgb_free(msg);
+ return -ENOTCONN;
+ }
+
+ client_send(rsproc, IPAC_PROTO_EXT_RSPRO, msg);
+
+ return 0;
+}
+
+struct msgb *osmo_rspro_client_msgb_alloc(void)
+{
+ return msgb_alloc_headroom(4000, 64, __func__);
+}
diff --git a/src/rspro_util.c b/src/rspro_util.c
index 7a53859..7ff93c3 100644
--- a/src/rspro_util.c
+++ b/src/rspro_util.c
@@ -152,5 +152,3 @@
return pdu;
}
-
-