Skip to content
Snippets Groups Projects
main.c 11.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • #ifndef _GNU_SOURCE
    #define _GNU_SOURCE
    #endif
    
    #include <stdio.h>
    #include <ctype.h>
    #include <unistd.h>
    #include <signal.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <fcntl.h>
    
    #include <libpicoevent.h>
    
    #include "libvoice.h"
    
    #include "line.h"
    #include "line-dect.h"
    
    #include "main.h"
    #include "ubus.h"
    
    
    #ifndef UCI_CONFIG_DIR
    #define UCI_CONFIG_DIR "/etc/config/"
    #endif
    
    
    static int audio_rx_fd;
    static int audio_tx_fd;
    static int event_fd;
    static const char event_str[] = "/tmp/endpt_event";
    static const char audio_tx_str[] = "/tmp/endpt_audio_tx";
    static const char audio_rx_str[] = "/tmp/endpt_audio_rx";
    static pe_stream_t *event_stream;
    static pe_stream_t *audio_rx_stream;
    static pe_list_t *event_list;
    static pe_bus_t *audio_rx_bus;
    static pe_bus_t *audio_tx_bus;
    int picoBase = -1;
    
    
    static void send_media_to_asterisk(const struct media_packet_t *packet, int size) {
    	pe_bus_send(audio_tx_bus, (uint8_t *)packet, size);
    
    // Send a media packet to voice engine
    void send_media_to_voice_engine(pe_packet_t *p) {
    	int conIdx;
    	struct media_packet_t *packet = (struct media_packet_t *)p->data;
    
    	conIdx = voice_connection_find(packet->line, packet->connection_id);
    	if (conIdx == -1 || (packet->rtp[0] != 0x80 && packet->rtp[0] != 0x81) || !packet->rtp_size) {
    
    		ENDPT_ERR("%s: bad packet\n", __func__);
    
    // Handle media stream from Asterisk on audio rx socket and re-posts it to internal audio rx bus
    
    static void audio_rx_stream_handler(pe_stream_t *stream __attribute__((unused)), pe_event_t *event) {
    	if (pe_bus_receive(audio_rx_bus, (pe_event_t *)event) < 0) {
    		exit_failure("audio_bus rx buffer full\n");
    
    		return; // Drop packets if we can't cope the pace
    
    	}
    
    	pe_bus_dispatch(audio_rx_bus);
    }
    
    // Notify main thread there is a new event. We need to
    // make the ubus call from the main context. This function
    // is executed by a libvoip thread.
    int send_event_main(struct line_event_t *ev, char x) {
    	if (!event_list || !event_fd) return -1;
    
    	pe_list_add(event_list, (void*)ev);
    
    	/* Trigger event loop to read event from queue. */
    	write(event_fd, &x, sizeof(char));
    
    	return 0;
    }
    
    // Handler for events sent to the main thread (in
    // general the events originates from libvoip).
    static void event_stream_handler(pe_stream_t *stream __attribute__((unused)), pe_event_t *event) {
    	struct line_event_t *ev;
    
    	if(!event || !event->in || event->count <= 0) return;
    
    	switch(*event->in) {
    		case EVENT_MAIN_LINE:
    			ev = (struct line_event_t*) pe_list_get(event_list);
    			if(!ev) break;
    			ENDPT_DBG("Main event handler line %d event %s\n", ev->line, ev->name);
    			ubus_call_asterisk(ev);
    
    			ubus_broadcast_event(ev); // Also broadcast event to all
    
    			break;
    
    		case EVENT_MAIN_QUIT:
    			ENDPT_DBG("Shutting down on quit event\n");
    			ubus_disable_receive();
    			voice_connection_deinit();
    
    			line_dect_deinit();
    			voice_line_deinit();
    			voice_engine_shutdown();
    
    			ubus_close();
    
    			exit_succes("voicemngr exits");
    
    			ENDPT_WARN("unknown event '%c'\n", *event->in);
    
    			break;
    	}
    }
    
    static void fifos_init(void) {
    	int ret;
    
    	/* Create event list for event fifo. */
    	event_list = pe_list_new();
    	if (!event_list)
    		exit_failure("pe_list_new\n");
    
    	/* Create event fifo. */
    	ret = mkfifo(event_str, 0666);
    	if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create event pipe");
    	event_fd = open(event_str, O_RDWR|O_NONBLOCK);
    	if(event_fd == -1) exit_failure("Failed to open event pipe");
    
    	/* Create audio fifos. Both ends open in R/W mode to become
    	 * independent of daemon startup order and peer restart
    	 * recovery. However, later each end use unidirectional flow. */
    	ret = mkfifo(audio_tx_str, 0666);
    	if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create tx pipe");
    	audio_tx_fd = open(audio_tx_str, O_RDWR|O_LARGEFILE|O_NONBLOCK);
    	if(audio_tx_fd == -1) exit_failure("Failed to open tx pipe");
    	ret = mkfifo(audio_rx_str, 0666);
    	if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create rx pipe");
    	audio_rx_fd = open(audio_rx_str, O_RDWR|O_LARGEFILE|O_NONBLOCK);
    	if(audio_rx_fd == -1) exit_failure("Failed to open rx pipe");
    }
    
    static void streams_and_buses_init(void) {
    	/* Listen for callback data from libvoip in our main event loop.
    	   We need to do it this way as ubus is not thread safe. */
    	event_stream = pe_stream_new(event_fd);
    	pe_stream_add_handler(event_stream, sizeof(char), event_stream_handler);
    	pe_base_add_stream(picoBase, event_stream);
    
    	/* Listen for audio frames from asterisk. */
    	audio_rx_stream = pe_stream_new(audio_rx_fd);
    	pe_stream_add_handler(audio_rx_stream, 4096, audio_rx_stream_handler);
    	pe_base_add_stream(picoBase, audio_rx_stream);
    
    	audio_rx_bus = pe_bus_new(audio_rx_fd);
    	if (!audio_rx_bus) exit_failure("Failed to create audio_rx bus");
    
    	pe_bus_add_handler(audio_rx_bus, send_media_to_voice_engine);
    
    
    	/* Send audio frames to asterisk. */
    	audio_tx_bus = pe_bus_new(audio_tx_fd);
    	if (!audio_tx_bus) exit_failure("Failed to create audio_tx bus");
    }
    
    // Posix signal handler. Wake up main event loop to exit prog.
    static void signal_handler(int signum __attribute__((unused)), siginfo_t *info __attribute__((unused)), void *ucontext __attribute__((unused))) {
    	if(event_fd) send_event_main(NULL, EVENT_MAIN_QUIT);
    }
    
    // Setup Posix signal handling
    static int signals_init(void) {
    	const int signals_handled[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGPWR };
    	struct sigaction action;
    	unsigned int i;
    
    	memset(&action, 0, sizeof(action));
    	action.sa_sigaction = signal_handler;
    	action.sa_flags = SA_SIGINFO | SA_RESTART;
    	sigemptyset(&action.sa_mask);
    
    	for(i = 0; i < ARRAY_SIZE(signals_handled); i++) {
    		sigaddset(&action.sa_mask, signals_handled[i]);
    	}
    
    	for(i = 0; i < ARRAY_SIZE(signals_handled); i++) {
    		if(sigaction(signals_handled[i], &action, NULL)) {
    			perror("Error trapping signals");
    			return -1;
    		}
    	}
    
    	return 0;
    }
    
    
    static void voicemngr_event_report(int line, const char *event, int data) {
    	/* Re-send the event to the main thread, which in turn re-send
    	 * to Asterisk. We need to do this due to UBUS is not thread safe,
    	 * so all UBUS calls must be done from a single thread. */
    	if(!DEBUG_LOOPBACK) { // Skip when debugging
    		struct line_event_t *msg = malloc(sizeof(struct line_event_t));
    
    		if (!msg) {
    
    			ENDPT_ERR("%s: out of memory\n", __func__);
    
    			return;
    		}
    		msg->name = event;
    		msg->data = (data < 0 ? 0 : data); // Discard negative values
    		msg->line = line;
    		send_event_main(msg, EVENT_MAIN_LINE);
    	}
    }
    
    
    static int voicemngr_get_voice_port_cfg(const char *hw_board_voice_names, struct terminal_info_t *voice_port_cfg)
    {
    	int res = 0;
    	char *duplication;
    	char *str, *token, *saveptr;
    
    	if (!hw_board_voice_names || *hw_board_voice_names == '\0') {
    
    		ENDPT_ERR("%s: environmental variable hw_board_VoicePortNames is not defined or empty\n", __func__);
    
    		return -1;
    	}
    
    	memset(voice_port_cfg, 0, sizeof(*voice_port_cfg));
    
    	/* Duplicate the string to prevent environmental variable's value being modified by strtok_r(). That
    	 * might cause the wrong value being returned when running the program multiple times */
    	duplication = strdup(hw_board_voice_names);
    	if (!duplication) {
    
    		ENDPT_ERR("%s: out of memory\n", __func__);
    
    	ENDPT_DBG("hw_board_voice_names=[%s]\n", hw_board_voice_names);
    
    	for (str = duplication; (token = strtok_r(str, " ,;:\t", &saveptr)) != NULL; str = NULL) {
    		if ((size_t)voice_port_cfg->num_voice_ports >=
    			sizeof(voice_port_cfg->voice_ports) / sizeof(voice_port_cfg->voice_ports)[0]) {
    
    			ENDPT_ERR("%s: too many voice ports\n", __func__);
    
    			res = -1;
    			break;
    		}
    
    		if (strcasestr(token, "Tel") != NULL || strcasestr(token, "FXS") != NULL) {
    
    			ENDPT_INFO("%s: port %d is FXS\n", __func__, voice_port_cfg->num_voice_ports);
    
    			voice_port_cfg->voice_ports[voice_port_cfg->num_voice_ports++] = VOICE_LINE_FXS;
    			voice_port_cfg->num_fxs++;
    		} else if (strcasestr(token, "DECT") != NULL) {
    
    			ENDPT_INFO("%s: port %d is DECT\n", __func__, voice_port_cfg->num_voice_ports);
    
    			voice_port_cfg->voice_ports[voice_port_cfg->num_voice_ports++] = VOICE_LINE_DECT;
    			voice_port_cfg->num_dect++;
    		} else {
    
    			ENDPT_ERR("%s: invalid port type, %s\n", __func__, token);
    
    int main(int argc, char **argv) {
    	(void) argc;
    
    	struct uci_context *context = NULL;
    	struct uci_package *package = NULL;
    
    	struct terminal_info_t voice_port_cfg;
    
    	char *error;
    
    	const char *hw_board_has_dect;
    
    	const char *hw_board_voice_names;
    
    	openlog(argv[0], LOG_PID, LOG_DAEMON);
    
    
    	// Load the UCI package
    	context = uci_alloc_context();
    	if (!context) {
    
    		ENDPT_ERR("uci_alloc_context() failed\n");
    
    		return EXIT_FAILURE;
    	}
    
    	uci_set_confdir(context, UCI_CONFIG_DIR);
    
    	res = uci_load(context, uciStrConfig, &package);
    	if (res != 0 || !package) {
    		uci_get_errorstr(context, &error, "");
    
    		ENDPT_ERR("Failed to load uci package %s, %s\n", uciStrConfig, error);
    
    		free(error);
    		uci_free_context(context);
    		context = NULL;
    		return EXIT_FAILURE;
    	}
    
    
    	config_syslog(context, package);
    	ENDPT_INFO("syslog level is set to %d\n", log_level);
    	setlogmask(LOG_UPTO(log_level));
    
    
    	if(isatty(STDOUT_FILENO))
    		setbuf(stdout, NULL);
    
    
    	// Check that ASCII to DTMF convertion works.
    	if(!isdigit('0') || !isalpha('a')) {
    
    		ENDPT_ERR("Source has invalid encoding.\n");
    
    		goto __error_ret;
    
    	/* Does the HW has DECT populated? This is stored in the
    	 * "db", but that can't be queried via the normal uci API so we
    	 * export it as an environment var in the startup script. */
    	hw_board_has_dect = getenv("hw_board_hasDect");
    	has_dect = hw_board_has_dect && *hw_board_has_dect == '1';
    
    
    	hw_board_voice_names = getenv("hw_board_VoicePortNames");
    	if (voicemngr_get_voice_port_cfg(hw_board_voice_names, &voice_port_cfg)) goto __error_ret;
    
    
    	// Call this first. Find out how many phone lines we have
    
    	if(voice_get_terminal_info(&voice_port_cfg, &terminal_info)) goto __error_ret;
    
    	if(voice_register_cb_event_report(voicemngr_event_report)) goto __error_ret;
    	if(voice_register_cb_egress_media(send_media_to_asterisk)) goto __error_ret;
    	if(voice_line_preinit()) goto __error_ret;
    	if(signals_init()) goto __error_ret;
    	if((picoBase = pe_base_new()) < 0) goto __error_ret;
    
    	fifos_init();
    	streams_and_buses_init();
    
    	if(ubus_init()) goto __error_ret;
    
    	if(config_init(context, package)) goto __error_ret;
    
    	uci_free_context(context);
    	context = NULL;
    	package = NULL;
    
    
    	if(voice_line_init(has_dect)) goto __error_ret;
    	if(line_dect_init()) goto __error_ret;
    	if(voice_connection_init()) goto __error_ret;
    
    	// Enable UBUS when all initializations have finished
    	if(ubus_enable_receive()) goto __error_ret;
    
    	ENDPT_INFO("voicemngr has started successfully\n");
    
    	// Listen for events and dispatch them to handlers. Run forever and does not return.
    
    	pe_base_dispatch(picoBase);
    
    
    	ENDPT_WARN("pe_base_dispatch() exited unexpectedly\n");
    
    __error_ret:
    
    	if (package)
    		uci_unload(context, package);
    	if (context)
    		uci_free_context(context);
    
    	line_dect_deinit();
    	voice_line_deinit();
    
    	voice_connection_deinit();
    
    	ubus_disable_receive();
    	ubus_close();
    	return EXIT_FAILURE;
    }