res_aeap & res_speech_aeap: Add Asterisk External Application Protocol

Add framework to connect to, and read and write protocol based
messages from and to an external application using an Asterisk
External Application Protocol (AEAP). This has been divided into
several abstractions:

 1. transport - base communication layer (currently websocket only)
 2. message - AEAP description and data (currently JSON only)
 3. transaction - links/binds requests and responses
 4. aeap - transport, message, and transaction handler/manager

This patch also adds an AEAP implementation for speech to text.
Existing speech API callbacks for speech to text have been completed
making it possible for Asterisk to connect to a configured external
translator service and provide audio for STT. Results can also be
received from the external translator, and made available as speech
results in Asterisk.

Unit tests have also been created that test the AEAP framework, and
also the speech to text implementation.

ASTERISK-29726 #close

Change-Id: Iaa4b259f84aa63501e5fd2a6fb107f900b4d4ed2
This commit is contained in:
Kevin Harwell
2021-06-18 12:54:10 -05:00
committed by Kevin Harwell
parent 6ac08fdcf8
commit 2fb8667908
25 changed files with 4831 additions and 57 deletions

252
tests/test_aeap.c Normal file
View File

@@ -0,0 +1,252 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2021, Sangoma Technologies Corporation
*
* Kevin Harwell <kharwell@sangoma.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.
*/
/*** MODULEINFO
<depend>TEST_FRAMEWORK</depend>
<depend>res_aeap</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include "asterisk/test.h"
#include "asterisk/module.h"
#include "asterisk/file.h"
#include "asterisk/http_websocket.h"
#include "asterisk/json.h"
#include "asterisk/res_aeap.h"
#include "asterisk/res_aeap_message.h"
#define CATEGORY "/res/aeap/"
#define ADDR "127.0.0.1:8088"
#define AEAP_TRANSPORT_TYPE "ws"
#define AEAP_REMOTE_URL "ws://" ADDR "/ws"
#define AEAP_REMOTE_PROTOCOL "echo"
#define AEAP_MESSAGE_ID "foo"
#define AEAP_CONNECTION_TIMEOUT 2000
AST_TEST_DEFINE(create_and_connect)
{
RAII_VAR(struct ast_aeap *, aeap, NULL, ao2_cleanup);
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test creating and connecting to an AEAP application";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
ast_test_validate(test, (aeap = ast_aeap_create_and_connect(AEAP_TRANSPORT_TYPE,
NULL, AEAP_REMOTE_URL, AEAP_REMOTE_PROTOCOL, AEAP_CONNECTION_TIMEOUT)));
return AST_TEST_PASS;
}
static void handle_string(struct ast_aeap *aeap, const char *buf, intmax_t size)
{
int *passed = ast_aeap_user_data_object_by_id(aeap, AEAP_MESSAGE_ID);
if (strstr(buf, AEAP_MESSAGE_ID)) {
++*passed;
}
}
static void handle_timeout(struct ast_aeap *aeap, struct ast_aeap_message *message, void *data)
{
int *passed = ast_aeap_user_data_object_by_id(aeap, AEAP_MESSAGE_ID);
++*passed;
}
AST_TEST_DEFINE(send_msg_handle_string)
{
int passed = 0;
RAII_VAR(struct ast_aeap *, aeap, NULL, ao2_cleanup);
struct ast_aeap_tsx_params tsx_params = {0};
struct ast_aeap_params aeap_params = {
.on_string = handle_string,
};
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test an AEAP application string handler";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
tsx_params.timeout = 2000; /* Test will end by timing out */
tsx_params.on_timeout = handle_timeout;
tsx_params.wait = 1;
ast_test_validate(test, (aeap = ast_aeap_create_and_connect(AEAP_TRANSPORT_TYPE,
&aeap_params, AEAP_REMOTE_URL, AEAP_REMOTE_PROTOCOL, AEAP_CONNECTION_TIMEOUT)));
ast_test_validate(test, (!ast_aeap_user_data_register(aeap, AEAP_MESSAGE_ID, &passed, NULL)));
ast_test_validate(test, (tsx_params.msg = ast_aeap_message_create_request(
ast_aeap_message_type_json, "foo", AEAP_MESSAGE_ID, NULL)));
ast_test_validate(test, ast_aeap_send_msg_tsx(aeap, &tsx_params)); /* Returns fail on timeout */
ast_aeap_user_data_unregister(aeap, AEAP_MESSAGE_ID);
return passed == 2 ? AST_TEST_PASS : AST_TEST_FAIL;
}
static int handle_msg(struct ast_aeap *aeap, struct ast_aeap_message *message, void *data)
{
int *passed = ast_aeap_user_data_object_by_id(aeap, AEAP_MESSAGE_ID);
*passed = !strcmp(ast_aeap_message_id(message), AEAP_MESSAGE_ID) &&
ast_aeap_message_is_named(message, data);
if (!*passed) {
ast_log(LOG_ERROR, "Name '%s' did not equal '%s' for message '%s'",
ast_aeap_message_name(message), (char *)data, ast_aeap_message_id(message));
}
return 0;
}
static const struct ast_aeap_message_handler handlers[] = {
{ "foo", handle_msg },
};
AST_TEST_DEFINE(send_msg_handle_response)
{
int passed = 0;
RAII_VAR(struct ast_aeap *, aeap, NULL, ao2_cleanup);
char *name = "foo";
struct ast_aeap_params aeap_params = {
.response_handlers = handlers,
.response_handlers_size = ARRAY_LEN(handlers),
};
struct ast_aeap_tsx_params tsx_params = {0};
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test an AEAP application response handler";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
aeap_params.msg_type = ast_aeap_message_type_json;
tsx_params.timeout = 2000;
tsx_params.wait = 1;
tsx_params.obj = name;
ast_test_validate(test, (aeap = ast_aeap_create_and_connect(AEAP_TRANSPORT_TYPE,
&aeap_params, AEAP_REMOTE_URL, AEAP_REMOTE_PROTOCOL, AEAP_CONNECTION_TIMEOUT)));
ast_test_validate(test, (!ast_aeap_user_data_register(aeap, AEAP_MESSAGE_ID, &passed, NULL)));
ast_test_validate(test, (tsx_params.msg = ast_aeap_message_create_response(
ast_aeap_message_type_json, name, AEAP_MESSAGE_ID, NULL)));
ast_test_validate(test, !ast_aeap_send_msg_tsx(aeap, &tsx_params));
ast_aeap_user_data_unregister(aeap, AEAP_MESSAGE_ID);
return passed ? AST_TEST_PASS : AST_TEST_FAIL;
}
AST_TEST_DEFINE(send_msg_handle_request)
{
int passed = 0;
RAII_VAR(struct ast_aeap *, aeap, NULL, ao2_cleanup);
char *name = "foo";
struct ast_aeap_params aeap_params = {
.request_handlers = handlers,
.request_handlers_size = ARRAY_LEN(handlers),
};
struct ast_aeap_tsx_params tsx_params = {0};
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test an AEAP application request handler";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
aeap_params.msg_type = ast_aeap_message_type_json;
tsx_params.timeout = 2000;
tsx_params.wait = 1;
tsx_params.obj = name;
ast_test_validate(test, (aeap = ast_aeap_create_and_connect(AEAP_TRANSPORT_TYPE,
&aeap_params, AEAP_REMOTE_URL, AEAP_REMOTE_PROTOCOL, AEAP_CONNECTION_TIMEOUT)));
ast_test_validate(test, (!ast_aeap_user_data_register(aeap, AEAP_MESSAGE_ID, &passed, NULL)));
ast_test_validate(test, (tsx_params.msg = ast_aeap_message_create_request(
ast_aeap_message_type_json, name, AEAP_MESSAGE_ID, NULL)));
ast_test_validate(test, !ast_aeap_send_msg_tsx(aeap, &tsx_params));
ast_aeap_user_data_unregister(aeap, AEAP_MESSAGE_ID);
return passed ? AST_TEST_PASS : AST_TEST_FAIL;
}
static struct ast_http_server *http_server;
static int load_module(void)
{
if (!(http_server = ast_http_test_server_get("aeap transport http server", NULL))) {
return AST_MODULE_LOAD_DECLINE;
}
AST_TEST_REGISTER(create_and_connect);
AST_TEST_REGISTER(send_msg_handle_string);
AST_TEST_REGISTER(send_msg_handle_response);
AST_TEST_REGISTER(send_msg_handle_request);
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(send_msg_handle_request);
AST_TEST_UNREGISTER(send_msg_handle_response);
AST_TEST_UNREGISTER(send_msg_handle_string);
AST_TEST_UNREGISTER(create_and_connect);
ast_http_test_server_discard(http_server);
return 0;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Object Tests",
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
.requires = "res_aeap",
);

287
tests/test_aeap_speech.c Normal file
View File

@@ -0,0 +1,287 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2021, Sangoma Technologies Corporation
*
* Kevin Harwell <kharwell@sangoma.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.
*/
/*** MODULEINFO
<depend>TEST_FRAMEWORK</depend>
<depend>res_aeap</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include "asterisk/test.h"
#include "asterisk/module.h"
#include "asterisk/file.h"
#include "asterisk/format_cap.h"
#include "asterisk/http.h"
#include "asterisk/http_websocket.h"
#include "asterisk/json.h"
#include "asterisk/speech.h"
#include "asterisk/res_aeap.h"
#include "asterisk/res_aeap_message.h"
#define ADDR "127.0.0.1:8088"
static int speech_test_server_setup(struct ast_json *req, struct ast_json *resp)
{
struct ast_json *params;
if (ast_json_object_set(resp, "codecs", ast_json_ref(ast_json_object_get(req, "codecs")))) {
return -1;
}
params = ast_json_object_get(req, "params"); /* Optional */
if (params && ast_json_object_set(resp, "params", ast_json_ref(params))) {
return -1;
}
return 0;
}
#define TEST_SPEECH_RESULTS_TEXT "foo"
#define TEST_SPEECH_RESULTS_SCORE 7
#define TEST_SPEECH_RESULTS_GRAMMAR "bar"
#define TEST_SPEECH_RESULTS_BEST 1
static int speech_test_server_get(struct ast_json *req, struct ast_json *resp)
{
const char *param;
struct ast_json *json = NULL;
param = ast_json_string_get(ast_json_array_get(ast_json_object_get(req, "params"), 0));
if (!param) {
return -1;
}
if (!strcmp(param, "results")) {
json = ast_json_pack("{s:[{s:s,s:i,s:s,s:i}]}",
param,
"text", TEST_SPEECH_RESULTS_TEXT,
"score", TEST_SPEECH_RESULTS_SCORE,
"grammar", TEST_SPEECH_RESULTS_GRAMMAR,
"best", TEST_SPEECH_RESULTS_BEST);
} else {
/* Assume setting */
json = ast_json_pack("{s:s}", param, "bar");
}
if (!json || ast_json_object_set(resp, "params", json)) {
return -1;
}
return 0;
}
static int speech_test_server_set(struct ast_json *req, struct ast_json *resp)
{
if (ast_json_object_set(resp, "params", ast_json_ref(ast_json_object_get(req, "params")))) {
return -1;
}
return 0;
}
static int speech_test_server_handle_request(struct ast_websocket *ws, const void *buf, uint64_t size)
{
struct ast_json *req;
struct ast_json *resp;
const char *name;
char *resp_buf;
int res = 0;
req = ast_json_load_buf(buf, size, NULL);
if (!req) {
ast_log(LOG_ERROR, "speech test handle request: unable to load json\n");
return -1;
}
name = ast_json_object_string_get(req, "request");
if (!name) {
ast_log(LOG_ERROR, "speech test handle request: no name\n");
ast_json_unref(req);
return -1;
}
resp = ast_json_pack("{s:s, s:s}", "response", name,
"id", ast_json_object_string_get(req, "id"));
if (!resp) {
ast_log(LOG_ERROR, "speech test handle request: unable to create response '%s'\n", name);
ast_json_unref(req);
return -1;
}
if (!strcmp(name, "setup")) {
res = speech_test_server_setup(req, resp);
} else if (!strcmp(name, "get")) {
res = speech_test_server_get(req, resp);
} else if (!strcmp(name, "set")) {
res = speech_test_server_set(req, resp);
} else {
ast_log(LOG_ERROR, "speech test handle request: unsupported request '%s'\n", name);
return -1;
}
if (res) {
ast_log(LOG_ERROR, "speech test handle request: unable to build response '%s'\n", name);
ast_json_unref(resp);
ast_json_unref(req);
return -1;
}
resp_buf = ast_json_dump_string(resp);
ast_json_unref(resp);
if (!resp_buf) {
ast_log(LOG_ERROR, "speech test handle request: unable to dump response '%s'\n", name);
ast_json_unref(req);
return -1;
}
res = ast_websocket_write_string(ws, resp_buf);
if (res) {
ast_log(LOG_ERROR, "speech test handle request: unable to write response '%s'\n", name);
}
ast_json_unref(req);
ast_free(resp_buf);
return res;
}
static void speech_test_server_cb(struct ast_websocket *ws, struct ast_variable *parameters,
struct ast_variable *headers)
{
int res;
if (ast_fd_set_flags(ast_websocket_fd(ws), O_NONBLOCK)) {
ast_websocket_unref(ws);
return;
}
while ((res = ast_websocket_wait_for_input(ws, -1)) > 0) {
char *payload;
uint64_t payload_len;
enum ast_websocket_opcode opcode;
int fragmented;
if (ast_websocket_read(ws, &payload, &payload_len, &opcode, &fragmented)) {
ast_log(LOG_ERROR, "speech test: Read failure in server loop\n");
break;
}
switch (opcode) {
case AST_WEBSOCKET_OPCODE_CLOSE:
ast_websocket_unref(ws);
return;
case AST_WEBSOCKET_OPCODE_BINARY:
ast_websocket_write(ws, opcode, payload, payload_len);
break;
case AST_WEBSOCKET_OPCODE_TEXT:
ast_debug(3, "payload=%.*s\n", (int)payload_len, payload);
if (speech_test_server_handle_request(ws, payload, payload_len)) {
ast_websocket_unref(ws);
return;
}
break;
default:
break;
}
}
ast_websocket_unref(ws);
}
AST_TEST_DEFINE(res_speech_aeap_test)
{
RAII_VAR(struct ast_format_cap *, cap, NULL, ao2_cleanup);
RAII_VAR(struct ast_speech_result *, results, NULL, ast_speech_results_free);
struct ast_speech *speech = NULL;
enum ast_test_result_state res = AST_TEST_PASS;
char buf[8] = "";
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = "/res/aeap/speech/";
info->summary = "test the speech AEAP interface";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
ast_test_validate(test, !ast_websocket_add_protocol("_aeap_test_speech_", speech_test_server_cb));
ast_test_validate(test, (cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)));
ast_test_validate(test, !ast_format_cap_update_by_allow_disallow(cap, "ulaw", 1));
ast_test_validate_cleanup(test, (speech = ast_speech_new("_aeap_test_speech_", cap)), res, cleanup);
ast_speech_start(speech);
ast_test_validate_cleanup(test, !ast_speech_dtmf(speech, "1"), res, cleanup);
ast_test_validate_cleanup(test, !ast_speech_change(speech, "foo", "bar"), res, cleanup);
ast_test_validate_cleanup(test, !ast_speech_change_results_type(
speech, AST_SPEECH_RESULTS_TYPE_NBEST), res, cleanup);
ast_test_validate_cleanup(test, !ast_speech_get_setting(
speech, "foo", buf, sizeof(buf)), res, cleanup);
ast_test_validate_cleanup(test, !strcmp(buf, "bar"), res, cleanup);
ast_test_validate_cleanup(test, (results = ast_speech_results_get(speech)), res, cleanup);
ast_test_validate_cleanup(test, !strcmp(results->text, TEST_SPEECH_RESULTS_TEXT), res, cleanup);
ast_test_validate_cleanup(test, results->score == TEST_SPEECH_RESULTS_SCORE, res, cleanup);
ast_test_validate_cleanup(test, !strcmp(results->grammar, TEST_SPEECH_RESULTS_GRAMMAR), res, cleanup);
ast_test_validate_cleanup(test, results->nbest_num == TEST_SPEECH_RESULTS_BEST, res, cleanup);
cleanup:
if (speech) {
ast_speech_destroy(speech);
}
ast_websocket_remove_protocol("_aeap_test_speech_", speech_test_server_cb);
return res;
}
static struct ast_http_server *http_server;
static int load_module(void)
{
if (!(http_server = ast_http_test_server_get("aeap transport http server", NULL))) {
return AST_MODULE_LOAD_DECLINE;
}
AST_TEST_REGISTER(res_speech_aeap_test);
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(res_speech_aeap_test);
ast_http_test_server_discard(http_server);
return 0;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Speech test(s)",
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
.requires = "res_speech_aeap",
);

View File

@@ -0,0 +1,179 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2021, Sangoma Technologies Corporation
*
* Kevin Harwell <kharwell@sangoma.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.
*/
/*** MODULEINFO
<depend>TEST_FRAMEWORK</depend>
<depend>res_aeap</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include <pthread.h>
#include "asterisk/lock.h"
#include "asterisk/test.h"
#include "asterisk/module.h"
#include "asterisk/res_aeap.h"
#include "asterisk/res_aeap_message.h"
#include "../res/res_aeap/general.h"
#include "../res/res_aeap/transaction.h"
#define CATEGORY "/res/aeap/transaction/"
#define AEAP_TRANSACTION_ID "foo"
static void handle_timeout(struct ast_aeap *aeap, struct ast_aeap_message *msg, void *obj)
{
int *passed = obj;
++*passed;
}
static void *end_transaction(void *data)
{
/* Delay a second before ending transaction */
struct timespec delay = { 1, 0 };
int *passed = aeap_transaction_user_obj(data);
while (nanosleep(&delay, &delay));
++*passed;
aeap_transaction_end(data, 0);
return NULL;
}
static enum ast_test_result_state exec(struct ast_test *test,
struct ast_aeap_tsx_params *params)
{
pthread_t thread_id = AST_PTHREADT_NULL;
struct ao2_container *tsxs = NULL;
struct aeap_transaction *tsx = NULL;
enum ast_test_result_state res = AST_TEST_FAIL;
int passed = 0;
tsxs = aeap_transactions_create();
if (!tsxs) {
ast_test_status_update(test, "Failed to create transactions object\n");
goto exec_cleanup;
}
params->wait = 1;
params->obj = &passed;
tsx = aeap_transaction_create_and_add(tsxs, AEAP_TRANSACTION_ID, params, NULL);
if (!tsx) {
ast_test_status_update(test, "Failed to create transaction object\n");
goto exec_cleanup;
}
if (ast_pthread_create(&thread_id, NULL, end_transaction, ao2_bump(tsx))) {
ast_test_status_update(test, "Failed to create response thread\n");
ao2_ref(tsx, -1);
goto exec_cleanup;
}
if (aeap_transaction_start(tsx)) {
ast_test_status_update(test, "Failed to start transaction request\n");
goto exec_cleanup;
}
if (passed == 1) {
res = AST_TEST_PASS;
}
exec_cleanup:
if (thread_id != AST_PTHREADT_NULL) {
pthread_cancel(thread_id);
pthread_join(thread_id, NULL);
}
aeap_transaction_end(tsx, 0);
ao2_cleanup(tsxs);
return res;
}
AST_TEST_DEFINE(transaction_exec)
{
struct ast_aeap_tsx_params params = {
.timeout = 5000, /* Give plenty of time for test thread to end */
};
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test creating a basic AEAP transaction request";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
return exec(test, &params);
}
AST_TEST_DEFINE(transaction_exec_timeout)
{
struct ast_aeap_tsx_params params = {
.timeout = 100, /* Ensure timeout occurs before test thread ends */
.on_timeout = handle_timeout,
};
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test creating a AEAP transaction request that times out";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
return exec(test, &params);
}
static int load_module(void)
{
AST_TEST_REGISTER(transaction_exec);
AST_TEST_REGISTER(transaction_exec_timeout);
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(transaction_exec_timeout);
AST_TEST_UNREGISTER(transaction_exec);
return 0;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Transaction Tests",
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
.requires = "res_aeap",
);

249
tests/test_aeap_transport.c Normal file
View File

@@ -0,0 +1,249 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2021, Sangoma Technologies Corporation
*
* Kevin Harwell <kharwell@sangoma.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.
*/
/*** MODULEINFO
<depend>TEST_FRAMEWORK</depend>
<depend>res_aeap</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include "asterisk/http.h"
#include "asterisk/test.h"
#include "asterisk/module.h"
#include "../res/res_aeap/transport.h"
#define CATEGORY "/res/aeap/transport/"
#define ADDR "127.0.0.1:8088"
#define TRANSPORT_URL "ws://" ADDR "/ws"
#define TRANSPORT_URL_INVALID "ws://" ADDR "/invalid"
#define TRANSPORT_PROTOCOL "echo"
#define TRANSPORT_PROTOCOL_INVALID "invalid"
#define TRANSPORT_TIMEOUT 2000
AST_TEST_DEFINE(transport_create_invalid)
{
RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test creating an AEAP invalid transport type";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
/* Transport is expected to be NULL here */
ast_test_validate(test, !(transport = aeap_transport_create("invalid")));
return AST_TEST_PASS;
}
AST_TEST_DEFINE(transport_create)
{
RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test creating an AEAP transport";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
/* Type is based off the scheme, so just pass in the URL here */
ast_test_validate(test, (transport = aeap_transport_create(TRANSPORT_URL)));
return AST_TEST_PASS;
}
AST_TEST_DEFINE(transport_connect)
{
RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test connecting to an AEAP transport";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
/* Type is based off the scheme, so just pass in the URL for the type */
ast_test_validate(test, (transport = aeap_transport_create_and_connect(
TRANSPORT_URL, TRANSPORT_URL, TRANSPORT_PROTOCOL, TRANSPORT_TIMEOUT)));
ast_test_validate(test, aeap_transport_is_connected(transport));
ast_test_validate(test, !aeap_transport_disconnect(transport));
ast_test_validate(test, !aeap_transport_is_connected(transport));
return AST_TEST_PASS;
}
AST_TEST_DEFINE(transport_connect_fail)
{
RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test connecting failure for an AEAP transport";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
/* Test invalid address */
ast_test_validate(test, (transport = aeap_transport_create(TRANSPORT_URL)));
ast_test_validate(test, aeap_transport_connect(transport,
TRANSPORT_URL_INVALID, TRANSPORT_PROTOCOL, TRANSPORT_TIMEOUT));
ast_test_validate(test, !aeap_transport_is_connected(transport));
aeap_transport_destroy(transport);
/* Test invalid protocol */
ast_test_validate(test, (transport = aeap_transport_create(TRANSPORT_URL)));
ast_test_validate(test, aeap_transport_connect(transport,
TRANSPORT_URL, TRANSPORT_PROTOCOL_INVALID, TRANSPORT_TIMEOUT));
ast_test_validate(test, !aeap_transport_is_connected(transport));
return AST_TEST_PASS;
}
AST_TEST_DEFINE(transport_binary)
{
RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
int num = 38;
enum AST_AEAP_DATA_TYPE rtype;
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test binary I/O from an AEAP transport";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
ast_test_validate(test, (transport = aeap_transport_create_and_connect(
TRANSPORT_URL, TRANSPORT_URL, TRANSPORT_PROTOCOL, TRANSPORT_TIMEOUT)));
ast_test_validate(test, aeap_transport_write(transport, &num, sizeof(num),
AST_AEAP_DATA_TYPE_BINARY) == sizeof(num));
ast_test_validate(test, aeap_transport_read(transport, &num,
sizeof(num), &rtype) == sizeof(num));
ast_test_validate(test, rtype == AST_AEAP_DATA_TYPE_BINARY);
ast_test_validate(test, num == 38);
return AST_TEST_PASS;
}
AST_TEST_DEFINE(transport_string)
{
RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
char buf[16];
enum AST_AEAP_DATA_TYPE rtype;
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->explicit_only = 0;
info->category = CATEGORY;
info->summary = "test string I/O from an AEAP transport";
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
ast_test_validate(test, (transport = aeap_transport_create_and_connect(
TRANSPORT_URL, TRANSPORT_URL, TRANSPORT_PROTOCOL, TRANSPORT_TIMEOUT)));
ast_test_validate(test, aeap_transport_write(transport, "foo bar baz", 11,
AST_AEAP_DATA_TYPE_STRING) == 11);
ast_test_validate(test, aeap_transport_read(transport, buf,
sizeof(buf) / sizeof(char), &rtype) == 11);
ast_test_validate(test, rtype == AST_AEAP_DATA_TYPE_STRING);
ast_test_validate(test, !strcmp(buf, "foo bar baz"));
return AST_TEST_PASS;
}
static struct ast_http_server *http_server;
static int load_module(void)
{
if (!(http_server = ast_http_test_server_get("aeap transport http server", NULL))) {
return AST_MODULE_LOAD_DECLINE;
}
AST_TEST_REGISTER(transport_string);
AST_TEST_REGISTER(transport_binary);
AST_TEST_REGISTER(transport_connect_fail);
AST_TEST_REGISTER(transport_connect);
AST_TEST_REGISTER(transport_create);
AST_TEST_REGISTER(transport_create_invalid);
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(transport_string);
AST_TEST_UNREGISTER(transport_binary);
AST_TEST_UNREGISTER(transport_connect_fail);
AST_TEST_UNREGISTER(transport_connect);
AST_TEST_UNREGISTER(transport_create);
AST_TEST_UNREGISTER(transport_create_invalid);
ast_http_test_server_discard(http_server);
return 0;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Transport Tests",
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
.requires = "res_aeap",
);