| 
									
										
										
										
											2019-07-17 20:47:50 -04:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Asterisk -- An open source telephony toolkit. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2019, CyCore Systems, Inc | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Seán C McCord <scm@cycoresys.com | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * See http://www.asterisk.org for more information about
 | 
					
						
							|  |  |  |  * the Asterisk project. Please do not directly contact | 
					
						
							|  |  |  |  * any of the maintainers of this project for assistance; | 
					
						
							|  |  |  |  * the project provides a web site, mailing lists and IRC | 
					
						
							|  |  |  |  * channels for your use. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software, distributed under the terms of | 
					
						
							|  |  |  |  * the GNU General Public License Version 2. See the LICENSE file | 
					
						
							|  |  |  |  * at the top of the source tree. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \file
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \brief AudioSocket support for Asterisk | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \author Seán C McCord <scm@cycoresys.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*** MODULEINFO
 | 
					
						
							|  |  |  | 	<support_level>extended</support_level> | 
					
						
							|  |  |  |  ***/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "asterisk.h"
 | 
					
						
							|  |  |  | #include "errno.h"
 | 
					
						
							|  |  |  | #include <uuid/uuid.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "asterisk/file.h"
 | 
					
						
							|  |  |  | #include "asterisk/res_audiosocket.h"
 | 
					
						
							|  |  |  | #include "asterisk/channel.h"
 | 
					
						
							|  |  |  | #include "asterisk/module.h"
 | 
					
						
							|  |  |  | #include "asterisk/uuid.h"
 | 
					
						
							|  |  |  | #include "asterisk/format_cache.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define	MODULE_DESCRIPTION	"AudioSocket support functions for Asterisk"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define MAX_CONNECT_TIMEOUT_MSEC 2000
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \internal | 
					
						
							|  |  |  |  * \brief Attempt to complete the audiosocket connection. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param server Url that we are trying to connect to. | 
					
						
							|  |  |  |  * \param addr Address that host was resolved to. | 
					
						
							|  |  |  |  * \param netsockfd File descriptor of socket. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \retval 0 when connection is succesful. | 
					
						
							|  |  |  |  * \retval 1 when there is an error. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int handle_audiosocket_connection(const char *server, | 
					
						
							|  |  |  | 	const struct ast_sockaddr addr, const int netsockfd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct pollfd pfds[1]; | 
					
						
							|  |  |  | 	int res, conresult; | 
					
						
							|  |  |  | 	socklen_t reslen; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reslen = sizeof(conresult); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pfds[0].fd = netsockfd; | 
					
						
							|  |  |  | 	pfds[0].events = POLLOUT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	while ((res = ast_poll(pfds, 1, MAX_CONNECT_TIMEOUT_MSEC)) != 1) { | 
					
						
							|  |  |  | 		if (errno != EINTR) { | 
					
						
							|  |  |  | 			if (!res) { | 
					
						
							|  |  |  | 				ast_log(LOG_WARNING, "AudioSocket connection to '%s' timed" | 
					
						
							|  |  |  | 					"out after MAX_CONNECT_TIMEOUT_MSEC (%d) milliseconds.\n", | 
					
						
							|  |  |  | 					server, MAX_CONNECT_TIMEOUT_MSEC); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", server, | 
					
						
							|  |  |  | 					strerror(errno)); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (getsockopt(pfds[0].fd, SOL_SOCKET, SO_ERROR, &conresult, &reslen) < 0) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Connection to %s failed with error: %s\n", | 
					
						
							|  |  |  | 			ast_sockaddr_stringify(&addr), strerror(errno)); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (conresult) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Connecting to '%s' failed for url '%s': %s\n", | 
					
						
							|  |  |  | 			ast_sockaddr_stringify(&addr), server, strerror(conresult)); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const int ast_audiosocket_connect(const char *server, struct ast_channel *chan) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int s = -1; | 
					
						
							| 
									
										
										
										
											2020-04-13 18:38:37 +02:00
										 |  |  | 	struct ast_sockaddr *addrs = NULL; | 
					
						
							| 
									
										
										
										
											2019-07-17 20:47:50 -04:00
										 |  |  | 	int num_addrs = 0, i = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (chan && ast_autoservice_start(chan) < 0) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to start autoservice for channel " | 
					
						
							|  |  |  | 			"%s\n", ast_channel_name(chan)); | 
					
						
							|  |  |  | 		goto end; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(server)) { | 
					
						
							|  |  |  | 		ast_log(LOG_ERROR, "No AudioSocket server provided\n"); | 
					
						
							|  |  |  | 		goto end; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!(num_addrs = ast_sockaddr_resolve(&addrs, server, PARSE_PORT_REQUIRE, | 
					
						
							|  |  |  | 		AST_AF_UNSPEC))) { | 
					
						
							|  |  |  | 		ast_log(LOG_ERROR, "Failed to resolve AudioSocket service using %s - " | 
					
						
							|  |  |  | 			"requires a valid hostname and port\n", server); | 
					
						
							|  |  |  | 		goto end; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Connect to AudioSocket service */ | 
					
						
							|  |  |  | 	for (i = 0; i < num_addrs; i++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!ast_sockaddr_port(&addrs[i])) { | 
					
						
							|  |  |  | 			/* If there's no port, other addresses should have the
 | 
					
						
							|  |  |  | 			 * same problem. Stop here. | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			ast_log(LOG_ERROR, "No port provided for %s\n", | 
					
						
							|  |  |  | 				ast_sockaddr_stringify(&addrs[i])); | 
					
						
							|  |  |  | 			s = -1; | 
					
						
							|  |  |  | 			goto end; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ((s = ast_socket_nonblock(addrs[i].ss.ss_family, SOCK_STREAM, | 
					
						
							|  |  |  | 			IPPROTO_TCP)) < 0) { | 
					
						
							|  |  |  | 			ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno)); | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ast_connect(s, &addrs[i]) && errno == EINPROGRESS) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (handle_audiosocket_connection(server, addrs[i], s)) { | 
					
						
							|  |  |  | 				close(s); | 
					
						
							|  |  |  | 				continue; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			ast_log(LOG_ERROR, "Connection to %s failed with unexpected error: %s\n", | 
					
						
							|  |  |  | 				ast_sockaddr_stringify(&addrs[i]), strerror(errno)); | 
					
						
							|  |  |  | 			close(s); | 
					
						
							|  |  |  | 			s = -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | end: | 
					
						
							|  |  |  | 	if (addrs) { | 
					
						
							|  |  |  | 		ast_free(addrs); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (chan && ast_autoservice_stop(chan) < 0) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to stop autoservice for channel %s\n", | 
					
						
							|  |  |  | 		ast_channel_name(chan)); | 
					
						
							|  |  |  | 		close(s); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (i == num_addrs) { | 
					
						
							|  |  |  | 		ast_log(LOG_ERROR, "Failed to connect to AudioSocket service\n"); | 
					
						
							|  |  |  | 		close(s); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return s; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const int ast_audiosocket_init(const int svc, const char *id) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	uuid_t uu; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 	uint8_t buf[3 + 16]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ast_strlen_zero(id)) { | 
					
						
							|  |  |  | 		ast_log(LOG_ERROR, "No UUID for AudioSocket\n"); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (uuid_parse(id, uu)) { | 
					
						
							|  |  |  | 		ast_log(LOG_ERROR, "Failed to parse UUID '%s'\n", id); | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	buf[0] = 0x01; | 
					
						
							|  |  |  | 	buf[1] = 0x00; | 
					
						
							|  |  |  | 	buf[2] = 0x10; | 
					
						
							|  |  |  | 	memcpy(buf + 3, uu, 16); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (write(svc, buf, 3 + 16) != 3 + 16) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to write data to AudioSocket\n"); | 
					
						
							|  |  |  | 		ret = -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const int ast_audiosocket_send_frame(const int svc, const struct ast_frame *f) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 	uint8_t kind = 0x10;	/* always 16-bit, 8kHz signed linear mono, for now */ | 
					
						
							|  |  |  | 	uint8_t *p; | 
					
						
							|  |  |  | 	uint8_t buf[3 + f->datalen]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	p = buf; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	*(p++) = kind; | 
					
						
							|  |  |  | 	*(p++) = f->datalen >> 8; | 
					
						
							|  |  |  | 	*(p++) = f->datalen & 0xff; | 
					
						
							|  |  |  | 	memcpy(p, f->data.ptr, f->datalen); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (write(svc, buf, 3 + f->datalen) != 3 + f->datalen) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to write data to AudioSocket\n"); | 
					
						
							|  |  |  | 		ret = -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct ast_frame *ast_audiosocket_receive_frame(const int svc) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	int i = 0, n = 0, ret = 0, not_audio = 0; | 
					
						
							|  |  |  | 	struct ast_frame f = { | 
					
						
							|  |  |  | 		.frametype = AST_FRAME_VOICE, | 
					
						
							|  |  |  | 		.subclass.format = ast_format_slin, | 
					
						
							|  |  |  | 		.src = "AudioSocket", | 
					
						
							|  |  |  | 		.mallocd = AST_MALLOCD_DATA, | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	uint8_t kind; | 
					
						
							|  |  |  | 	uint8_t len_high; | 
					
						
							|  |  |  | 	uint8_t len_low; | 
					
						
							|  |  |  | 	uint16_t len = 0; | 
					
						
							|  |  |  | 	uint8_t *data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	n = read(svc, &kind, 1); | 
					
						
							|  |  |  | 	if (n < 0 && errno == EAGAIN) { | 
					
						
							|  |  |  | 		return &ast_null_frame; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (n == 0) { | 
					
						
							|  |  |  | 		return &ast_null_frame; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (n != 1) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to read type header from AudioSocket\n"); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (kind == 0x00) { | 
					
						
							|  |  |  | 		/* AudioSocket ended by remote */ | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (kind != 0x10) { | 
					
						
							|  |  |  | 		/* read but ignore non-audio message */ | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Received non-audio AudioSocket message\n"); | 
					
						
							|  |  |  | 		not_audio = 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	n = read(svc, &len_high, 1); | 
					
						
							|  |  |  | 	if (n != 1) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to read data length from AudioSocket\n"); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	len += len_high * 256; | 
					
						
							|  |  |  | 	n = read(svc, &len_low, 1); | 
					
						
							|  |  |  | 	if (n != 1) { | 
					
						
							|  |  |  | 		ast_log(LOG_WARNING, "Failed to read data length from AudioSocket\n"); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	len += len_low; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (len < 1) { | 
					
						
							|  |  |  | 		return &ast_null_frame; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data = ast_malloc(len); | 
					
						
							|  |  |  | 	if (!data) { | 
					
						
							|  |  |  | 		ast_log(LOG_ERROR, "Failed to allocate for data from AudioSocket\n"); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = 0; | 
					
						
							|  |  |  | 	n = 0; | 
					
						
							|  |  |  | 	i = 0; | 
					
						
							|  |  |  | 	while (i < len) { | 
					
						
							|  |  |  | 		n = read(svc, data + i, len - i); | 
					
						
							|  |  |  | 		if (n < 0) { | 
					
						
							|  |  |  | 			ast_log(LOG_ERROR, "Failed to read data from AudioSocket\n"); | 
					
						
							|  |  |  | 			ret = n; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (n == 0) { | 
					
						
							|  |  |  | 			ast_log(LOG_ERROR, "Insufficient data read from AudioSocket\n"); | 
					
						
							|  |  |  | 			ret = -1; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		i += n; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ret != 0) { | 
					
						
							|  |  |  | 		ast_free(data); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (not_audio) { | 
					
						
							|  |  |  | 		ast_free(data); | 
					
						
							|  |  |  | 		return &ast_null_frame; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f.data.ptr = data; | 
					
						
							|  |  |  | 	f.datalen = len; | 
					
						
							|  |  |  | 	f.samples = len / 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* The frame steals data, so it doesn't need to be freed here */ | 
					
						
							|  |  |  | 	return ast_frisolate(&f); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int load_module(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2024-01-31 10:46:28 -07:00
										 |  |  | 	ast_verb(5, "Loading AudioSocket Support module\n"); | 
					
						
							| 
									
										
										
										
											2019-07-17 20:47:50 -04:00
										 |  |  | 	return AST_MODULE_LOAD_SUCCESS; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int unload_module(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2024-01-31 10:46:28 -07:00
										 |  |  | 	ast_verb(5, "Unloading AudioSocket Support module\n"); | 
					
						
							| 
									
										
										
										
											2019-07-17 20:47:50 -04:00
										 |  |  | 	return AST_MODULE_LOAD_SUCCESS; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-15 16:44:35 -04:00
										 |  |  | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "AudioSocket support", | 
					
						
							| 
									
										
										
										
											2019-07-17 20:47:50 -04:00
										 |  |  | 	.support_level = AST_MODULE_SUPPORT_EXTENDED, | 
					
						
							|  |  |  | 	.load = load_module, | 
					
						
							|  |  |  | 	.unload = unload_module, | 
					
						
							|  |  |  | 	.load_pri = AST_MODPRI_CHANNEL_DEPEND, | 
					
						
							|  |  |  | ); |