/* CAPI based ISDN testing utility. * * (C) 2022 by Harald Welte * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include "capiconn.h" #include "bchan.h" #define CM(x) (1<<(x)) #define CIPMASK_ALL 0x1FFF03FF #define CIPMASK_VOICE (CM(1)|CM(4)|CM(5)|CM(16)|CM(26)) #define CIPMASK_DATA (CM(2)|CM(3)) void *g_ctx; struct capi_inst { unsigned int applid; capiconn_context *cc_ctx; struct osmo_fd ofd; }; static const char *conninfo(capi_connection *p) { static char buf[1024]; capi_conninfo *cp = capiconn_getinfo(p); char *callingnumber = ""; char *callednumber = ""; if (cp->callingnumber && cp->callingnumber[0] > 2) callingnumber = (char *) cp->callingnumber+3; if (cp->callednumber && cp->callednumber[0] > 1) callednumber = (char *) cp->callednumber+2; snprintf(buf, sizeof(buf), "\"%s\" -> \"%s\" %s (pcli=0x%x/ncci=0x%x)", callingnumber, callednumber, cp->isincoming ? "incoming" : "outgoing", cp->plci, cp->ncci ); buf[sizeof(buf)-1] = 0; return buf; } /*********************************************************************** * capiconn call-backs ***********************************************************************/ static void *cb_malloc(size_t size) { return talloc_size(g_ctx, size); } static void cb_free(void *buf) { talloc_free(buf); } static void cb_disconnected(capi_connection *cp, int localdisconnect, unsigned reason, unsigned reason_b3) { struct call_state *cst; LOGP(DCAPI, LOGL_DEBUG, "%s(local=%d, reason=0x%04x, reason_b3=0x%04x)\n", __func__, localdisconnect, reason, reason_b3); cst = capiconn_getpriv(cp); if (!cst) return; if (cst->bch && cst->bch->ops.fini) cst->bch->ops.fini(cst); talloc_free(cst); } static void cb_incoming(capi_connection *cp, unsigned contr, unsigned cipvalue, char *called, char *calling) { struct bchan_handler *bch; int rc; /* we should call capiconn_{accept,ignore,reject} */ LOGP(DCAPI, LOGL_INFO, "incoming call: %s (CIP=%u) %s -> %s\n", conninfo(cp), cipvalue, calling, called); bch = bchan_handler_for_call(cipvalue, called); if (bch) { struct call_state *cst = talloc_zero(g_ctx, struct call_state); if (!cst) { capiconn_reject(cp); return; } cst->bch = bch; cst->cc = cp; if (bch->ops.init) { rc = bch->ops.init(cst); if (rc < 0) { talloc_free(cst); capiconn_reject(cp); } } rc = capiconn_accept(cp, bch->cfg.proto.b1, bch->cfg.proto.b2, bch->cfg.proto.b3, 0, 0, 0, (uint8_t *)bch->cfg.ncpi); if (rc != CAPICONN_OK) { if (cst->bch->ops.fini) cst->bch->ops.fini(cst); talloc_free(cst); capiconn_reject(cp); } capiconn_setpriv(cp, cst); LOGP(DCAPI, LOGL_NOTICE, "call CIP=%u MSN=%s routed to handler '%s'\n", cipvalue, called, bch->name); } else { LOGP(DCAPI, LOGL_NOTICE, "call CIP=%u MSN=%s has no handler, ignoring\n", cipvalue, called); capiconn_ignore(cp); } } static void cb_connected(capi_connection *cp, _cstruct NCPI) { struct call_state *cst = capiconn_getpriv(cp); struct bchan_handler *bch = cst->bch; LOGP(DCAPI, LOGL_NOTICE, "connected: %s\n", conninfo(cp)); if (bch->ops.init) { int rc = bch->ops.init(cst); if (rc < 0) capiconn_disconnect(cp, NULL); } } /* user plane data received */ static void cb_datareceived(capi_connection *cp, unsigned char *data, unsigned int len) { struct call_state *cst = capiconn_getpriv(cp); LOGP(DCAPI, LOGL_DEBUG, "%s(%p, %u)\n", __func__, data, len); cst->bch->ops.rx_data(cst, data, len); } /* sent user plane data was confirmed */ static void cb_datasent(capi_connection *cp, unsigned char *data) { LOGP(DCAPI, LOGL_DEBUG, "%s(%p)\n", __func__, data); } /* capiconn wants to issue a CAPI_PUT_MESSAGE */ static void cb_put_message(unsigned int appid, unsigned char *msg) { int rc; const char *str = capi20_cmd2str(CAPIMSG_COMMAND(msg), CAPIMSG_SUBCOMMAND(msg)); LOGP(DCAPI, LOGL_DEBUG, "Tx to CAPI: %s\n", str); rc = capi20_put_message(appid, msg); if (rc) fprintf(stderr, "capi20_put_message: %s\n", capi_info2str(rc)); } static void cb_debugmsg(const char *fmt, ...) { va_list ap; va_start(ap, fmt); osmo_vlogp(DCAPI, LOGL_DEBUG, __FILE__, __LINE__, 0, fmt, ap); va_end(ap); LOGPC(DCAPI, LOGL_DEBUG, "\n"); } static void cb_infomsg(const char *fmt, ...) { va_list ap; va_start(ap, fmt); osmo_vlogp(DCAPI, LOGL_INFO, __FILE__, __LINE__, 0, fmt, ap); va_end(ap); LOGPC(DCAPI, LOGL_INFO, "\n"); } static void cb_errmsg(const char *fmt, ...) { va_list ap; va_start(ap, fmt); osmo_vlogp(DCAPI, LOGL_ERROR, __FILE__, __LINE__, 0, fmt, ap); va_end(ap); LOGPC(DCAPI, LOGL_ERROR, "\n"); } static capiconn_callbacks callbacks = { .malloc = cb_malloc, .free = cb_free, .disconnected = cb_disconnected, .incoming = cb_incoming, .connected = cb_connected, .received = cb_datareceived, .datasent = cb_datasent, .chargeinfo = NULL, .capi_put_message = cb_put_message, .debugmsg = cb_debugmsg, .infomsg = cb_infomsg, .errmsg = cb_errmsg, }; /*********************************************************************** * initialization / integration ***********************************************************************/ int bchan_call_tx(struct call_state *cst, const uint8_t *data, size_t len) { return capiconn_send(cst->cc, (uint8_t *)data, len); } /* debug print callback for capi20 to log via libosmocore logging framework */ static int cb_capi_dprintf(const char *file, int line, const char *func, const char *fmt, va_list va) { osmo_vlogp(DLCAPI20, LOGL_DEBUG, file, line, 0, fmt, va); return 0; } /* osmocom select file descriptor call-back for capi20 file descriptor */ static int capifd_cb(struct osmo_fd *ofd, unsigned int what) { struct capi_inst *ci = ofd->data; int rc; if (what & OSMO_FD_READ) { unsigned char *msg = NULL; rc = capi20_get_message(ci->applid, &msg); if (rc == 0) { const char *str = capi20_cmd2str(CAPIMSG_COMMAND(msg), CAPIMSG_SUBCOMMAND(msg)); LOGP(DCAPI, LOGL_DEBUG, "Rx from CAPI: %s\n", str); capiconn_inject(ci->applid, msg); } } return 0; } struct capi_inst *capi_init(void *ctx) { int rc; /* libcapi20 debug log print callback */ register_dbg_vprintf(cb_capi_dprintf); struct capi_inst *ci = talloc_zero(ctx, struct capi_inst); if (!ci) return NULL; rc = capi20_register(1, 8, 1280, &ci->applid); if (rc != 0) { fprintf(stderr, "Error in capi_register: %s\n", capi_info2str(rc)); talloc_free(ci); exit(1); } ci->cc_ctx = capiconn_getcontext(ci->applid, &callbacks); if (!ci->cc_ctx) { fprintf(stderr, "get_coontext\n"); capi20_release(ci->applid); talloc_free(ci); exit(1); } struct capi_contrinfo cinfo = {0, 0, 0}; capiconn_addcontr(ci->cc_ctx, 1, &cinfo); rc = capiconn_listen(ci->cc_ctx, 1, CIPMASK_ALL, 0); if (rc) { fprintf(stderr, "Error in capiconn_listen: %s\n", capi_info2str(rc)); capi20_release(ci->applid); talloc_free(ci); exit(1); } osmo_fd_setup(&ci->ofd, capi20_fileno(ci->applid), OSMO_FD_READ, capifd_cb, ci, 0); osmo_fd_register(&ci->ofd); return ci; } static const struct log_info_cat log_info_cat[] = { [DCAPI] = { .name = "DCAPI", .description = "ISDN CAPI Interface", .enabled = 1, .loglevel = LOGL_DEBUG, }, [DLCAPI20] = { .name = "DLCAPI", .description = "ISDN libcapi20", .enabled = 1, .loglevel = LOGL_DEBUG, }, }; static const struct log_info log_info = { .cat = log_info_cat, .num_cat = ARRAY_SIZE(log_info_cat), }; int main(int argc, char **argv) { void *ctx = talloc_named_const(NULL, 1, "capi-test"); osmo_init_logging2(ctx, &log_info); log_set_use_color(osmo_stderr_target, 1); log_set_print_level(osmo_stderr_target, 1); log_set_print_category(osmo_stderr_target, 1); log_set_print_category_hex(osmo_stderr_target, 0); log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); capi_init(ctx); bchan_route_add(-1, "142", "raw_prbs"); bchan_route_add(-1, NULL, "raw_loop"); while (1) { osmo_select_main(0); } }