Newer
Older
#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"
#include "main.h"
#include "ubus.h"
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_DBG("%s: bad packet\n", __func__);
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
}
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();
100
101
102
103
104
105
106
107
108
109
110
111
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
break;
case EVENT_MAIN_WAKE: // A nop event. Do nothing but wake up.
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);
149
150
151
152
153
154
155
156
157
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
/* 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_DBG("%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);
}
}
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
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_DBG("%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_DBG("%s: out of memory\n", __func__);
return -1;
}
ENDPT_DBG("%s: hw_board_voice_names=[%s]\n", __func__, 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_DBG("%s: too many voice ports\n", __func__);
res = -1;
break;
}
if (strcasestr(token, "Tel") != NULL || strcasestr(token, "FXS") != NULL) {
ENDPT_DBG("%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_DBG("%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_DBG("%s: invalid port type, %s\n", __func__, token);
res = -1;
break;
}
}
free(duplication);
return res;
}
int main(void) {
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;
// Load the UCI package
context = uci_alloc_context();
if (!context) {
ENDPT_DBG("uci_alloc_context() failed\n");
return EXIT_FAILURE;
}
res = uci_load(context, uciStrConfig, &package);
if (res != 0 || !package) {
uci_get_errorstr(context, &error, "");
ENDPT_DBG("Failed to load uci package %s, %s\n", uciStrConfig, error);
free(error);
uci_free_context(context);
context = NULL;
return EXIT_FAILURE;
}
// Unbuffered stdout
if(isatty(STDOUT_FILENO))
setbuf(stdout, NULL);
// Check that ASCII to DTMF convertion works.
if(!isdigit('0') || !isalpha('a')) {
ENDPT_DBG("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;
if(perhaps_erase_unused_lines(context)) goto __error_ret;
// Enable UBUS when all initializations have finished
if(ubus_enable_receive()) goto __error_ret;
ENDPT_DBG("voicemngr has started successfully\n");
// Listen for events and dispatch them to handlers. Run forever and does not return.
ENDPT_DBG("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_engine_shutdown();
ubus_disable_receive();
ubus_close();
return EXIT_FAILURE;
}