Newer
Older
#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 "line.h"
#include "line-dect.h"
#ifndef UCI_CONFIG_DIR
#define UCI_CONFIG_DIR "/etc/config/"
#endif
int log_level = LOG_INFO;
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;

Yalu Zhang
committed
// Send a media packet to Asterisk
static void send_media_to_asterisk(const struct media_packet_t *packet, int size) {
pe_bus_send(audio_tx_bus, (uint8_t *)packet, size);

Yalu Zhang
committed
// 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__);

Yalu Zhang
committed
return;
}
voice_write_media_packet(packet);
}
// 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();
ENDPT_WARN("unknown event '%c'\n", *event->in);
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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");

Yalu Zhang
committed
pe_bus_add_handler(audio_rx_bus, send_media_to_voice_engine);
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/* 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);
res = -1;
break;
}
}
free(duplication);
return res;
}
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;
int res, has_dect;
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");
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));
// Unbuffered stdout
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");
/* 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;
// Get configuration
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.
ENDPT_WARN("pe_base_dispatch() exited unexpectedly\n");
if (package)
uci_unload(context, package);
if (context)
uci_free_context(context);
line_dect_deinit();
voice_line_deinit();
voice_connection_deinit();
voice_engine_shutdown();
ubus_disable_receive();
ubus_close();
return EXIT_FAILURE;
}