Timur Davydov has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmo-abis/+/42652?usp=email )
Change subject: abis: add optional WebSDR E1 input driver ......................................................................
abis: add optional WebSDR E1 input driver
Add a new optional E1 input backend for WebSDR, enabled via --with-websdr.
Introduce input/websdr.c and public callback API to replace socket-based IPA transport with a callback-driven interface for OML/RSL and OSMUX.
Intended for WebAssembly builds where Osmocom components run in a browser and communicate with SDR via WebUSB.
Change-Id: Ib804dc8eb67a91678603f54ea0beccd5c61e4a80 --- M configure.ac M include/Makefile.am A include/osmocom/abis/websdr.h M src/Makefile.am M src/e1_input.c M src/e1_input_vty.c M src/input/ipaccess.c A src/input/websdr.c 8 files changed, 775 insertions(+), 2 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmo-abis refs/changes/52/42652/1
diff --git a/configure.ac b/configure.ac index e38201f..bc700d5 100644 --- a/configure.ac +++ b/configure.ac @@ -194,6 +194,16 @@
fi # ENABLE_ORTP
+# Enable WebSDR Support +AC_ARG_WITH(websdr, [ + AS_HELP_STRING([--with-websdr], + [enable WebUSB based transceiver]) +]) +AM_CONDITIONAL(ENABLE_WEBSDR, [test "x$with_websdr" = "xyes"]) +AS_IF([test "x$with_websdr" = "xyes"], [ + AC_DEFINE(ENABLE_WEBSDR, 1, Define to 1 for enable WebUSB based transceiver) +]) + AC_MSG_RESULT([CFLAGS="$CFLAGS"]) AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
diff --git a/include/Makefile.am b/include/Makefile.am index 36d3259..cec2432 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -40,3 +40,7 @@ if ENABLE_ORTP nobase_include_HEADERS += osmocom/trau/osmo_ortp.h endif + +if ENABLE_WEBSDR +nobase_include_HEADERS += osmocom/abis/websdr.h +endif diff --git a/include/osmocom/abis/websdr.h b/include/osmocom/abis/websdr.h new file mode 100644 index 0000000..2c43e6e --- /dev/null +++ b/include/osmocom/abis/websdr.h @@ -0,0 +1,60 @@ +/* OpenBSC Abis input driver API for WebSDR */ + +/* (C) 2026 by Timur Davydov dtv.comp@gmail.com + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 http://www.gnu.org/licenses/. + * + */ + +#ifndef OSMO_ABIS_WEBSDR_H +#define OSMO_ABIS_WEBSDR_H + +#include <unistd.h> + +/** + * @brief Callback function for sending media data via WebSDR. + * Call for sending media data to WebSDR client. + * + * @param msg Pointer to the message buffer. + * @param msglen Size of the message buffer. + * @return int Status code. 0 on success, negative on error. + */ +int ws_osmux_deliver_cb(const void *msg, unsigned msglen); + +/** + * @brief Callback function for sending ABIS messages via WebSDR. + * Call for sending ABIS messages to WebSDR client. + * + * @param ts_nr Transport stream number. 1 - ABIS OML messages, 2 - ABIS RSL messages. + * @param msg Pointer to the message buffer. + * @param msglen Size of the message buffer. + * @return int Status code. Send length on success, negative on error. + */ +int ws_ipa_send(unsigned ts_nr, const void *msg, unsigned msglen); + +/** + * @brief Callback function for asking RSL data via WebSDR. + * Call for asking RSL data from WebSDR client. + * The ws_ipa_push_raw_data will be called by the WebSDR client. + * + * @param ts_nr Transport stream number. + * @return int Status code. 0 on success, negative on error. + */ +int ws_rsl_connect_cb(unsigned ts_nr); + +#endif /* OSMO_ABIS_WEBSDR_H */ \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index 4dc7cfe..4b77a52 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -46,6 +46,10 @@ libosmoabis_la_SOURCES += input/dahdi.c endif
+if ENABLE_WEBSDR +libosmoabis_la_SOURCES += input/websdr.c +endif + libosmotrau_la_CFLAGS = $(AM_CFLAGS) $(ORTP_CFLAGS) libosmotrau_la_LDFLAGS = $(AM_LDFLAGS) \ -version-info $(TRAU_LIBVERSION) \ diff --git a/src/e1_input.c b/src/e1_input.c index c5ee38f..c80989a 100644 --- a/src/e1_input.c +++ b/src/e1_input.c @@ -1156,6 +1156,9 @@ void e1inp_ipaccess_init(void); void e1inp_rs232_init(void); void e1inp_unixsocket_init(void); +#ifdef ENABLE_WEBSDR +int e1inp_websdr_init(void); +#endif /* ENABLE_WEBSDR */
void e1inp_init(void) { @@ -1174,4 +1177,7 @@ e1inp_ipaccess_init(); e1inp_rs232_init(); e1inp_unixsocket_init(); +#ifdef ENABLE_WEBSDR + e1inp_websdr_init(); +#endif /* ENABLE_WEBSDR */ } diff --git a/src/e1_input_vty.c b/src/e1_input_vty.c index de92359..291b7b9 100644 --- a/src/e1_input_vty.c +++ b/src/e1_input_vty.c @@ -42,11 +42,17 @@
#include <osmocom/abis/e1_input.h>
+#include "config.h" + #define X(x) (1 << x)
/* CONFIG */
+#ifndef ENABLE_WEBSDR #define E1_DRIVER_NAMES "(misdn|misdn_lapd|dahdi|e1d|ipa|unixsocket)" +#else /* ENABLE_WEBSDR */ +#define E1_DRIVER_NAMES "(misdn|misdn_lapd|dahdi|e1d|ipa|unixsocket|websdr)" +#endif /* ENABLE_WEBSDR */ #define E1_DRIVER_HELP "mISDN supported E1 Card (kernel LAPD)\n" \ "mISDN supported E1 Card (userspace LAPD)\n" \ "DAHDI supported E1/T1/J1 Card\n" \ diff --git a/src/input/ipaccess.c b/src/input/ipaccess.c index 6f3c980..28fd11f 100644 --- a/src/input/ipaccess.c +++ b/src/input/ipaccess.c @@ -25,6 +25,7 @@ */
#include "internal.h" +#include "config.h"
#include <stdio.h> #include <unistd.h> @@ -56,6 +57,10 @@ #include <osmocom/netif/ipa.h> #include <osmocom/abis/e1_input.h>
+#ifdef ENABLE_WEBSDR +#include <osmocom/abis/websdr.h> +#endif /* ENABLE_WEBSDR */ + /* global parameters of IPA input driver */ struct ipa_pars g_e1inp_ipaccess_pars;
@@ -1161,11 +1166,13 @@ const char *rem_addr, uint16_t rem_port, uint8_t trx_nr) { - struct osmo_stream_cli *cli; + int rc; struct e1inp_ts *e1i_ts = e1inp_line_ipa_rsl_ts(line, trx_nr); +#ifndef ENABLE_WEBSDR + struct osmo_stream_cli *cli; struct ipaccess_line *il; char cli_name[128]; - int rc; +#endif
if (E1INP_SIGN_RSL+trx_nr-1 >= NUM_E1_TS) { LOGPITS(e1i_ts, DLINP, LOGL_ERROR, "cannot create RSL BTS link: " @@ -1173,6 +1180,7 @@ return -EINVAL; }
+#ifndef ENABLE_WEBSDR /* Drop previous line */ if ((rc = e1inp_ipa_bts_rsl_close_n(line, trx_nr)) < 0) return rc; @@ -1219,6 +1227,14 @@
ipaccess_bts_keepalive_fsm_alloc(e1i_ts, cli, "rsl_bts_to_bsc"); il->ipa_cli[1 + trx_nr] = cli; +#else /* ENABLE_WEBSDR */ + rc = ws_rsl_connect_cb(E1INP_SIGN_RSL + trx_nr); + if (rc < 0) { + LOGP(DLINP, LOGL_ERROR, "cannot create RSL BTS link\n"); + return -EIO; + } +#endif /* ENABLE_WEBSDR */ + return 0; }
diff --git a/src/input/websdr.c b/src/input/websdr.c new file mode 100644 index 0000000..1382f3b --- /dev/null +++ b/src/input/websdr.c @@ -0,0 +1,667 @@ +/* OpenBSC Abis input driver for WebSDR */ + +/* (C) 2026 by Timur Davydov dtv.comp@gmail.com + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 http://www.gnu.org/licenses/. + * + */ + +#include "internal.h" +#include "../config.h" + +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/protocol/ipaccess.h> +#include <osmocom/abis/e1_input.h> + +#include <osmocom/abis/websdr.h> + +#ifdef __EMSCRIPTEN__ +#include <emscripten.h> + +EM_JS(int, ws_osmux_deliver_cb_wrapper, (const void *msg, unsigned msglen), { + return ws_osmux_deliver_cb(msg, msglen); +}); + +EM_JS(int, ws_ipa_send_wrapper, (unsigned ts_nr, const void *msg, unsigned msglen), { + return ws_ipa_send(ts_nr, msg, msglen); +}); + +EM_JS(int, ws_rsl_connect_cb_wrapper, (unsigned ts_nr), { + return ws_rsl_connect_cb(ts_nr); +}); +#endif /* __EMSCRIPTEN__ */ + +#define CB_BUF_SZ 8192 + +static struct e1inp_line *g_line_cb = NULL; +static int write_suppressed = 0; + +static const uint8_t websdr_pong_msg[] = { + 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG +}; + +static char cb_data[CB_BUF_SZ * NUM_E1_TS]; +static size_t cb_len[NUM_E1_TS] = {0}; + +struct websdr_line { + bool line_already_initialized; + struct osmo_stream_cli *ipa_cli[NUM_E1_TS]; /* 0=OML, 1+N=TRX_N */ +}; + +static inline struct e1inp_ts *websdr_line_ts(unsigned ts_nr, struct e1inp_line *line) +{ + if (ts_nr == E1INP_SIGN_OML) + return e1inp_line_ipa_oml_ts(line); + else + return e1inp_line_ipa_rsl_ts(line, ts_nr - E1INP_SIGN_RSL); +} + +static int handle_ts1_write(unsigned int ts_nr, struct e1inp_line *line) +{ + struct e1inp_ts *e1i_ts; + struct e1inp_sign_link *sign_link; + struct msgb *msg; + int ret; + + e1i_ts = websdr_line_ts(ts_nr, line); + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + return 0; + } + + switch (sign_link->type) { + case E1INP_SIGN_OML: + case E1INP_SIGN_RSL: + case E1INP_SIGN_OSMO: + break; + default: + /* leave WRITE flag enabled, come back for more msg */ + ret = -EINVAL; + goto out; + } + + msg->l2h = msg->data; + ipa_prepend_header(msg, sign_link->tei); + + LOGPITS(e1i_ts, DLMI, LOGL_DEBUG, "TX %u: %s\n", ts_nr, + osmo_hexdump(msg->l2h, msgb_l2len(msg))); + + ret = ws_ipa_send(ts_nr, msg->data, msg->len); +out: + msgb_free(msg); + return ret; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + struct e1inp_line *line = e1i_ts->line; + unsigned ts_nr = e1i_ts - line->ts + 1; + int res; + + if (write_suppressed) + return 0; + + do { + res = handle_ts1_write(ts_nr, line); + } while (res > 0); + + return 0; +} + +static int websdr_line_update(struct e1inp_line *line) +{ + int ret = -ENOENT; + struct websdr_line *il; + + if (!line->driver_data) + line->driver_data = talloc_zero(line, struct websdr_line); + + if (!line->driver_data) { + LOGP(DLINP, LOGL_ERROR, "WebSDR: OOM in line update\n"); + return -ENOMEM; + } + il = line->driver_data; + + switch(line->ops->cfg.ipa.role) { + case E1INP_LINE_R_BSC: { + g_line_cb = line; + LOGP(DLINP, LOGL_NOTICE, "enabling WebSDR BSC mode, OML connecting\n"); + ret = 0; + break; + } + case E1INP_LINE_R_BTS: { + g_line_cb = line; + LOGP(DLINP, LOGL_NOTICE, "enabling WebSDR BTS mode, OML connecting\n"); + ret = 0; + break; + } + default: + break; + } + + il->line_already_initialized = true; + return ret; +} + +static void websdr_close(struct e1inp_sign_link *sign_link) +{ + struct e1inp_ts *e1i_ts = sign_link->ts; + + e1inp_int_snd_event(e1i_ts, sign_link, S_L_INP_TEI_DN); + + g_line_cb = NULL; +} + +static int cb_push_data(unsigned ts_nr, const void *buf, size_t buflen) +{ + if (cb_len[ts_nr] + buflen > CB_BUF_SZ) + return -EOVERFLOW; + + memcpy(cb_data + CB_BUF_SZ * ts_nr + cb_len[ts_nr], buf, buflen); + cb_len[ts_nr] += buflen; + return 0; +} + +static ssize_t cb_recv_data(unsigned ts_nr, void *buf, size_t buflen) +{ + size_t avail = buflen > cb_len[ts_nr] ? cb_len[ts_nr] : buflen; + memcpy(buf, cb_data + CB_BUF_SZ * ts_nr, avail); + + cb_len[ts_nr] -= avail; + memmove(cb_data + CB_BUF_SZ * ts_nr, cb_data + avail + CB_BUF_SZ * ts_nr, cb_len[ts_nr]); + + if (avail == 0) { + errno = EAGAIN; + return -1; + } + return (ssize_t)avail; +} + +static int websdr_ccm_send_pong(unsigned ts_nr) +{ + return ws_ipa_send(ts_nr, websdr_pong_msg, sizeof(websdr_pong_msg)); +} + +static int websdr_ccm_rcvmsg_bts_base(struct msgb *msg, unsigned ts_nr) +{ + uint8_t msg_type = *(msg->l2h); + int ret = 0; + + switch (msg_type) { + case IPAC_MSGT_PING: + ret = websdr_ccm_send_pong(ts_nr); + if (ret < 0) { + LOGP(DLINP, LOGL_ERROR, "Cannot send PONG " + "message. Reason: %s\n", strerror(errno)); + } + break; + case IPAC_MSGT_PONG: + DEBUGP(DLMI, "PONG!\n"); + break; + case IPAC_MSGT_ID_ACK: + DEBUGP(DLMI, "ID_ACK\n"); + break; + } + return ret; +} + +void websdr_msg_push_header(struct msgb *msg, uint8_t proto) +{ + struct ipaccess_head *hh; + + msg->l2h = msg->data; + hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh)); + hh->proto = proto; + hh->len = htons(msgb_l2len(msg)); +} + +#define IPA_STRING_MAX 64 + +static struct msgb *websdr_bts_id_resp(const struct ipaccess_unit *dev, uint8_t *data, int len, int trx_nr) +{ + struct msgb *nmsg; + char str[IPA_STRING_MAX]; + uint8_t *tag; + + memset(str, 0, sizeof(str)); + + nmsg = ipa_msg_alloc(0); + if (!nmsg) + return NULL; + + *msgb_put(nmsg, 1) = IPAC_MSGT_ID_RESP; + while (len) { + if (len < 2) { + LOGP(DLINP, LOGL_NOTICE, + "Short read of ipaccess tag\n"); + msgb_free(nmsg); + return NULL; + } + switch (data[1]) { + case IPAC_IDTAG_UNIT: + snprintf(str, sizeof(str), "%u/%u/%u", + dev->site_id, dev->bts_id, trx_nr); + break; + case IPAC_IDTAG_MACADDR: + snprintf(str, sizeof(str), + "%02x:%02x:%02x:%02x:%02x:%02x", + dev->mac_addr[0], dev->mac_addr[1], + dev->mac_addr[2], dev->mac_addr[3], + dev->mac_addr[4], dev->mac_addr[5]); + break; + case IPAC_IDTAG_LOCATION1: + if (dev->location1) + osmo_strlcpy(str, dev->location1, sizeof(str)); + break; + case IPAC_IDTAG_LOCATION2: + if (dev->location2) + osmo_strlcpy(str, dev->location2, sizeof(str)); + break; + case IPAC_IDTAG_EQUIPVERS: + if (dev->equipvers) + osmo_strlcpy(str, dev->equipvers, sizeof(str)); + break; + case IPAC_IDTAG_SWVERSION: + if (dev->swversion) + osmo_strlcpy(str, dev->swversion, sizeof(str)); + break; + case IPAC_IDTAG_UNITNAME: + snprintf(str, sizeof(str), + "%s-%02x-%02x-%02x-%02x-%02x-%02x", + dev->unit_name, + dev->mac_addr[0], dev->mac_addr[1], + dev->mac_addr[2], dev->mac_addr[3], + dev->mac_addr[4], dev->mac_addr[5]); + break; + case IPAC_IDTAG_SERNR: + if (dev->serno) + osmo_strlcpy(str, dev->serno, sizeof(str)); + break; + default: + LOGP(DLINP, LOGL_NOTICE, + "Unknown ipaccess tag 0x%02x\n", *data); + msgb_free(nmsg); + return NULL; + } + + LOGP(DLINP, LOGL_INFO, " tag %d: %s\n", data[1], str); + tag = msgb_put(nmsg, 3 + strlen(str) + 1); + tag[0] = 0x00; + tag[1] = 1 + strlen(str) + 1; + tag[2] = data[1]; + memcpy(tag + 3, str, strlen(str) + 1); + data += 2; + len -= 2; + } + websdr_msg_push_header(nmsg, IPAC_PROTO_IPACCESS); + return nmsg; +} + +static struct msgb *websdr_bts_id_ack(void) +{ + struct msgb *nmsg2; + + nmsg2 = ipa_msg_alloc(0); + if (!nmsg2) + return NULL; + + *msgb_put(nmsg2, 1) = IPAC_MSGT_ID_ACK; + websdr_msg_push_header(nmsg2, IPAC_PROTO_IPACCESS); + + return nmsg2; +} + +/* handle incoming message to BTS, check if it is an IPA CCM, and if yes, + * handle it accordingly (PING/PONG/ID_REQ/ID_RESP/ID_ACK) */ +static int websdr_bts_handle_ccm(struct e1inp_line *line, unsigned ts_nr, + struct ipaccess_unit *dev, struct msgb *msg) +{ + struct ipaccess_head *hh = (struct ipaccess_head *)msg->data; + struct msgb *rmsg; + int ret = 0; + /* line might not exist if != bsc||bts */ + + /* special handling for IPA CCM. */ + if (hh->proto == IPAC_PROTO_IPACCESS) { + uint8_t msg_type = *(msg->l2h); + // struct osmo_fsm_inst* ka_fsm = NULL; + + /* peek the pong for our keepalive fsm */ + if (line && msg_type == IPAC_MSGT_PONG) { + //ka_fsm = websdr_line_ts(link->ofd, line)->driver.ipaccess.ka_fsm; + //ipa_keepalive_fsm_pong_received(ka_fsm); + } + + /* ping, pong and acknowledgment cases. */ + ret = websdr_ccm_rcvmsg_bts_base(msg, ts_nr); + if (ret < 0) + goto err; + + /* this is a request for identification from the BSC. */ + if (msg_type == IPAC_MSGT_ID_GET) { + uint8_t *data = msgb_l2(msg); + int len = msgb_l2len(msg); + int trx_nr = 0; + + if (ts_nr >= E1INP_SIGN_RSL) + trx_nr = ts_nr - E1INP_SIGN_RSL; + + LOGP(DLINP, LOGL_NOTICE, "received ID_GET for unit ID %u/%u/%u\n", + dev->site_id, dev->bts_id, trx_nr); + // dev->trx_id = trx_nr; + rmsg = websdr_bts_id_resp(dev, data + 1, len - 1, trx_nr); + ret = ws_ipa_send(ts_nr, rmsg->data, rmsg->len); + if (ret != rmsg->len) { + LOGP(DLINP, LOGL_ERROR, "cannot send ID_RESP " + "message. Reason: %s\n", strerror(errno)); + goto err_rmsg; + } + msgb_free(rmsg); + + /* send ID_ACK. */ + rmsg = websdr_bts_id_ack(); + ret = ws_ipa_send(ts_nr, rmsg->data, rmsg->len); + if (ret != rmsg->len) { + LOGP(DLINP, LOGL_ERROR, "cannot send ID_ACK " + "message. Reason: %s\n", strerror(errno)); + goto err_rmsg; + } + msgb_free(rmsg); + } + return 1; + } + + return 0; + +err_rmsg: + msgb_free(rmsg); +err: + //ipa_client_conn_close(link); + return -1; +} + +static int websdr_bts_read_cb(unsigned ts_nr, struct msgb *msg) +{ + struct e1inp_line* line = g_line_cb; + struct e1inp_ts *e1i_ts = NULL; + struct e1inp_sign_link *sign_link; + int ret = 0; + struct ipaccess_head *hh = (struct ipaccess_head *) msg->data; + uint8_t msg_type = *(msg->l2h); + + //fprintf(stderr, "IPA PROTP=%d SZ=%d e1i_ts=%p\n", + // hh->proto, msgb_l1len(msg), websdr_line_ts(ts_nr, line)); + + /* special handling for IPA CCM. */ + if (hh->proto == IPAC_PROTO_IPACCESS) { + /* this is a request for identification from the BSC. */ + if (msg_type == IPAC_MSGT_ID_GET) { + if (!line->ops->sign_link_up) { + LOGP(DLINP, LOGL_ERROR, + "Unable to set signal link, " + "closing socket.\n"); + goto err; + } + } + } + + /* core CCM handling */ + ret = websdr_bts_handle_ccm(line, ts_nr, line->ops->cfg.ipa.dev, msg); + if (ret < 0) + goto err; + + if (ret == 1 && hh->proto == IPAC_PROTO_IPACCESS) { + if (msg_type == IPAC_MSGT_ID_GET) { + sign_link = line->ops->sign_link_up(msg, + line, + ts_nr); + if (sign_link == NULL) { + LOGP(DLINP, LOGL_ERROR, + "Unable to set signal link, " + "closing socket.\n"); + goto err; + } + } + msgb_free(msg); + return ret; + } else { + e1i_ts = websdr_line_ts(ts_nr, line); + } + + OSMO_ASSERT(e1i_ts != NULL); + + if (e1i_ts->type == E1INP_TS_TYPE_NONE) { + LOGP(DLINP, LOGL_ERROR, "Signalling link not initialized. Discarding." + " msg_type=%u\n", msg_type); + goto err; + } + + /* look up for some existing signaling link. */ + sign_link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0); + if (sign_link == NULL) { + LOGP(DLINP, LOGL_ERROR, "no matching signalling link for " + "hh->proto=0x%02x\n", hh->proto); + goto err; + } + msg->dst = sign_link; + + /* XXX better use e1inp_ts_rx? */ + if (!line->ops->sign_link) { + LOGP(DLINP, LOGL_ERROR, "Fix your application, " + "no action set for signalling messages.\n"); + goto err; + } + ret = line->ops->sign_link(msg); + return ret; +err: + // ipa_client_conn_close(link); + msgb_free(msg); + return -EBADF; +} + +static int websdr_msg_recv_buffered(unsigned ts_nr, struct msgb **rmsg, struct msgb **tmp_msg) +{ + struct msgb *msg = tmp_msg ? *tmp_msg : NULL; + struct ipaccess_head *hh; + int len, ret; + int needed; + + if (msg == NULL) { + msg = ipa_msg_alloc(0); + if (msg == NULL) { + ret = -ENOMEM; + goto discard_msg; + } + msg->l1h = msg->tail; + } + + if (msg->l2h == NULL) { + /* first read our 3-byte header */ + needed = sizeof(*hh) - msg->len; + ret = cb_recv_data(ts_nr, msg->tail, needed); + if (ret == 0) + goto discard_msg; + + if (ret < 0) { + if (errno == EAGAIN || errno == EINTR) + ret = 0; + else { + ret = -errno; + goto discard_msg; + } + } + + msgb_put(msg, ret); + + if (ret < needed) { + if (msg->len == 0) { + ret = -EAGAIN; + goto discard_msg; + } + + LOGP(DLINP, LOGL_INFO, + "Received part of IPA message header (%d/%zu)\n", + msg->len, sizeof(*hh)); + if (!tmp_msg) { + ret = -EIO; + goto discard_msg; + } + *tmp_msg = msg; + return -EAGAIN; + } + + msg->l2h = msg->tail; + } + + hh = (struct ipaccess_head *) msg->data; + + /* then read the length as specified in header */ + len = osmo_ntohs(hh->len); + + if (len < 0 || CB_BUF_SZ < len + sizeof(*hh)) { + LOGP(DLINP, LOGL_ERROR, "bad message length of %d bytes, " + "received %d bytes\n", len, msg->len); + ret = -EIO; + goto discard_msg; + } + + needed = len - msgb_l2len(msg); + + if (needed > 0) { + ret = cb_recv_data(ts_nr, msg->tail, needed); + + if (ret == 0) + goto discard_msg; + + if (ret < 0) { + if (errno == EAGAIN || errno == EINTR) + ret = 0; + else { + ret = -errno; + goto discard_msg; + } + } + + msgb_put(msg, ret); + + if (ret < needed) { + LOGP(DLINP, LOGL_INFO, + "Received part of IPA message L2 data (%d/%d)\n", + msgb_l2len(msg), len); + if (!tmp_msg) { + ret = -EIO; + goto discard_msg; + } + *tmp_msg = msg; + return -EAGAIN; + } + } + + ret = msgb_l2len(msg); + + if (ret == 0) { + LOGP(DLINP, LOGL_INFO, + "Discarding IPA message without payload\n"); + ret = -EAGAIN; + goto discard_msg; + } + + if (tmp_msg) + *tmp_msg = NULL; + *rmsg = msg; + return ret; + +discard_msg: + if (tmp_msg) + *tmp_msg = NULL; + msgb_free(msg); + return ret; +} + +int ws_ipa_push_raw_data(unsigned ts_nr, const char* data, unsigned sz) +{ + static struct msgb *tmpmsg[NUM_E1_TS] = { NULL }; + struct msgb *msg; + int ret; + + cb_push_data(ts_nr, data, sz); + write_suppressed = 1; + + for (;;) { + ret = websdr_msg_recv_buffered(ts_nr, &msg, &tmpmsg[ts_nr]); + if (ret < 0) + break; + + //fprintf(stderr, "ws_ipa_push_raw_data(TS_NR=%d) => buff %d REM %p\n", ts_nr, ret, tmpmsg[ts_nr]); + ret = websdr_bts_read_cb(ts_nr, msg); + } + + write_suppressed = 0; + ts_want_write(websdr_line_ts(ts_nr, g_line_cb)); + return ret; +} + +int ws_osmux_deliver_cb(const void *msg, unsigned msglen) +{ + LOGP(DLIO, LOGL_DEBUG, "WebSDR: ws_osmux_deliver_cb called with msg=%p msglen=%u", msg, msglen); +#ifdef __EMSCRIPTEN__ + return ws_osmux_deliver_cb_wrapper(msg, msglen); +#else /* __EMSCRIPTEN__ */ + /* For non-WebAssembly builds, we just return success. */ + return 0; +#endif /* __EMSCRIPTEN__ */ +} + +int ws_ipa_send(unsigned ts_nr, const void *msg, unsigned msglen) +{ + LOGP(DLIO, LOGL_DEBUG, "WebSDR: ws_ipa_send called with ts_nr=%u msg=%p msglen=%u", ts_nr, msg, msglen); +#ifdef __EMSCRIPTEN__ + return ws_ipa_send_wrapper(ts_nr, msg, msglen); +#else /* __EMSCRIPTEN__ */ + /* For non-WebAssembly builds, we just return success. */ + return 0; +#endif /* __EMSCRIPTEN__ */ +} + +int ws_rsl_connect_cb(unsigned ts_nr) +{ + LOGP(DLIO, LOGL_DEBUG, "WebSDR: ws_rsl_connect_cb called with ts_nr=%u", ts_nr); +#ifdef __EMSCRIPTEN__ + return ws_rsl_connect_cb_wrapper(ts_nr); +#else /* __EMSCRIPTEN__ */ + /* For non-WebAssembly builds, we just return success. */ + return 0; +#endif /* __EMSCRIPTEN__ */ +} + +static struct e1inp_driver websdr_driver = { + .name = "websdr", + .want_write = ts_want_write, + .line_update = websdr_line_update, + .close = websdr_close, +}; + +int e1inp_websdr_init(void) +{ + return e1inp_driver_register(&websdr_driver); +}