mirror of https://github.com/asterisk/asterisk
This patch adds CHANNEL read support for chan_pjsip. This allows the dialplan to use the CHANNEL function on a chan_pjsip channel to obtain run-time information about the channel from the PJSIP channel driver and the PJSIP stack. This includes: * RTP information, including source/destination media addresses, whether or not the media is secure, held, and other properties. * RTCP information. This includes sets of parseable information, as well as individual statistic attriutes. * PJSIP information. This includes URIs, local/remote signalling addresses, whether or not the signalling is secure, and other properties. * The endpoint name. This can be used in conjunction with the PJSIP_ENDPOINT function to obtain more detailed endpoint information. Review: https://reviewboard.asterisk.org/r/3038/ ........ Merged revisions 403618 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@403619 65c4cc65-6c06-0410-ace0-fbb531ad65f3changes/97/197/1
parent
f46b30bd36
commit
ce423d2ea4
@ -0,0 +1,893 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013, Digium, Inc.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* \author \verbatim Joshua Colp <jcolp@digium.com> \endverbatim
|
||||
* \author \verbatim Matt Jordan <mjordan@digium.com> \endverbatim
|
||||
*
|
||||
* \ingroup functions
|
||||
*
|
||||
* \brief PJSIP channel dialplan functions
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<support_level>core</support_level>
|
||||
***/
|
||||
|
||||
/*** DOCUMENTATION
|
||||
<function name="PJSIP_DIAL_CONTACTS" language="en_US">
|
||||
<synopsis>
|
||||
Return a dial string for dialing all contacts on an AOR.
|
||||
</synopsis>
|
||||
<syntax>
|
||||
<parameter name="endpoint" required="true">
|
||||
<para>Name of the endpoint</para>
|
||||
</parameter>
|
||||
<parameter name="aor" required="false">
|
||||
<para>Name of an AOR to use, if not specified the configured AORs on the endpoint are used</para>
|
||||
</parameter>
|
||||
<parameter name="request_user" required="false">
|
||||
<para>Optional request user to use in the request URI</para>
|
||||
</parameter>
|
||||
</syntax>
|
||||
<description>
|
||||
<para>Returns a properly formatted dial string for dialing all contacts on an AOR.</para>
|
||||
</description>
|
||||
</function>
|
||||
<function name="PJSIP_MEDIA_OFFER" language="en_US">
|
||||
<synopsis>
|
||||
Media and codec offerings to be set on an outbound SIP channel prior to dialing.
|
||||
</synopsis>
|
||||
<syntax>
|
||||
<parameter name="media" required="true">
|
||||
<para>types of media offered</para>
|
||||
</parameter>
|
||||
</syntax>
|
||||
<description>
|
||||
<para>Returns the codecs offered based upon the media choice</para>
|
||||
</description>
|
||||
</function>
|
||||
<info name="PJSIPCHANNEL" language="en_US" tech="PJSIP">
|
||||
<enumlist>
|
||||
<enum name="rtp">
|
||||
<para>R/O Retrieve media related information.</para>
|
||||
<parameter name="type" required="true">
|
||||
<para>When <replaceable>rtp</replaceable> is specified, the
|
||||
<literal>type</literal> parameter must be provided. It specifies
|
||||
which RTP parameter to read.</para>
|
||||
<enumlist>
|
||||
<enum name="src">
|
||||
<para>Retrieve the local address for RTP.</para>
|
||||
</enum>
|
||||
<enum name="dest">
|
||||
<para>Retrieve the remote address for RTP.</para>
|
||||
</enum>
|
||||
<enum name="direct">
|
||||
<para>If direct media is enabled, this address is the remote address
|
||||
used for RTP.</para>
|
||||
</enum>
|
||||
<enum name="secure">
|
||||
<para>Whether or not the media stream is encrypted.</para>
|
||||
<enumlist>
|
||||
<enum name="0">
|
||||
<para>The media stream is not encrypted.</para>
|
||||
</enum>
|
||||
<enum name="1">
|
||||
<para>The media stream is encrypted.</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</enum>
|
||||
<enum name="hold">
|
||||
<para>Whether or not the media stream is currently restricted
|
||||
due to a call hold.</para>
|
||||
<enumlist>
|
||||
<enum name="0">
|
||||
<para>The media stream is not held.</para>
|
||||
</enum>
|
||||
<enum name="1">
|
||||
<para>The media stream is held.</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</parameter>
|
||||
<parameter name="media_type" required="false">
|
||||
<para>When <replaceable>rtp</replaceable> is specified, the
|
||||
<literal>media_type</literal> parameter may be provided. It specifies
|
||||
which media stream the chosen RTP parameter should be retrieved
|
||||
from.</para>
|
||||
<enumlist>
|
||||
<enum name="audio">
|
||||
<para>Retrieve information from the audio media stream.</para>
|
||||
<note><para>If not specified, <literal>audio</literal> is used
|
||||
by default.</para></note>
|
||||
</enum>
|
||||
<enum name="video">
|
||||
<para>Retrieve information from the video media stream.</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</parameter>
|
||||
</enum>
|
||||
<enum name="rtcp">
|
||||
<para>R/O Retrieve RTCP statistics.</para>
|
||||
<parameter name="statistic" required="true">
|
||||
<para>When <replaceable>rtcp</replaceable> is specified, the
|
||||
<literal>statistic</literal> parameter must be provided. It specifies
|
||||
which RTCP statistic parameter to read.</para>
|
||||
<enumlist>
|
||||
<enum name="all">
|
||||
<para>Retrieve a summary of all RTCP statistics.</para>
|
||||
<para>The following data items are returned in a semi-colon
|
||||
delineated list:</para>
|
||||
<enumlist>
|
||||
<enum name="ssrc">
|
||||
<para>Our Synchronization Source identifier</para>
|
||||
</enum>
|
||||
<enum name="themssrc">
|
||||
<para>Their Synchronization Source identifier</para>
|
||||
</enum>
|
||||
<enum name="lp">
|
||||
<para>Our lost packet count</para>
|
||||
</enum>
|
||||
<enum name="rxjitter">
|
||||
<para>Received packet jitter</para>
|
||||
</enum>
|
||||
<enum name="rxcount">
|
||||
<para>Received packet count</para>
|
||||
</enum>
|
||||
<enum name="txjitter">
|
||||
<para>Transmitted packet jitter</para>
|
||||
</enum>
|
||||
<enum name="txcount">
|
||||
<para>Transmitted packet count</para>
|
||||
</enum>
|
||||
<enum name="rlp">
|
||||
<para>Remote lost packet count</para>
|
||||
</enum>
|
||||
<enum name="rtt">
|
||||
<para>Round trip time</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</enum>
|
||||
<enum name="all_jitter">
|
||||
<para>Retrieve a summary of all RTCP Jitter statistics.</para>
|
||||
<para>The following data items are returned in a semi-colon
|
||||
delineated list:</para>
|
||||
<enumlist>
|
||||
<enum name="minrxjitter">
|
||||
<para>Our minimum jitter</para>
|
||||
</enum>
|
||||
<enum name="maxrxjitter">
|
||||
<para>Our max jitter</para>
|
||||
</enum>
|
||||
<enum name="avgrxjitter">
|
||||
<para>Our average jitter</para>
|
||||
</enum>
|
||||
<enum name="stdevrxjitter">
|
||||
<para>Our jitter standard deviation</para>
|
||||
</enum>
|
||||
<enum name="reported_minjitter">
|
||||
<para>Their minimum jitter</para>
|
||||
</enum>
|
||||
<enum name="reported_maxjitter">
|
||||
<para>Their max jitter</para>
|
||||
</enum>
|
||||
<enum name="reported_avgjitter">
|
||||
<para>Their average jitter</para>
|
||||
</enum>
|
||||
<enum name="reported_stdevjitter">
|
||||
<para>Their jitter standard deviation</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</enum>
|
||||
<enum name="all_loss">
|
||||
<para>Retrieve a summary of all RTCP packet loss statistics.</para>
|
||||
<para>The following data items are returned in a semi-colon
|
||||
delineated list:</para>
|
||||
<enumlist>
|
||||
<enum name="minrxlost">
|
||||
<para>Our minimum lost packets</para>
|
||||
</enum>
|
||||
<enum name="maxrxlost">
|
||||
<para>Our max lost packets</para>
|
||||
</enum>
|
||||
<enum name="avgrxlost">
|
||||
<para>Our average lost packets</para>
|
||||
</enum>
|
||||
<enum name="stdevrxlost">
|
||||
<para>Our lost packets standard deviation</para>
|
||||
</enum>
|
||||
<enum name="reported_minlost">
|
||||
<para>Their minimum lost packets</para>
|
||||
</enum>
|
||||
<enum name="reported_maxlost">
|
||||
<para>Their max lost packets</para>
|
||||
</enum>
|
||||
<enum name="reported_avglost">
|
||||
<para>Their average lost packets</para>
|
||||
</enum>
|
||||
<enum name="reported_stdevlost">
|
||||
<para>Their lost packets standard deviation</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</enum>
|
||||
<enum name="all_rtt">
|
||||
<para>Retrieve a summary of all RTCP round trip time information.</para>
|
||||
<para>The following data items are returned in a semi-colon
|
||||
delineated list:</para>
|
||||
<enumlist>
|
||||
<enum name="minrtt">
|
||||
<para>Minimum round trip time</para>
|
||||
</enum>
|
||||
<enum name="maxrtt">
|
||||
<para>Maximum round trip time</para>
|
||||
</enum>
|
||||
<enum name="avgrtt">
|
||||
<para>Average round trip time</para>
|
||||
</enum>
|
||||
<enum name="stdevrtt">
|
||||
<para>Standard deviation round trip time</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</enum>
|
||||
<enum name="txcount"><para>Transmitted packet count</para></enum>
|
||||
<enum name="rxcount"><para>Received packet count</para></enum>
|
||||
<enum name="txjitter"><para>Transmitted packet jitter</para></enum>
|
||||
<enum name="rxjitter"><para>Received packet jitter</para></enum>
|
||||
<enum name="remote_maxjitter"><para>Their max jitter</para></enum>
|
||||
<enum name="remote_minjitter"><para>Their minimum jitter</para></enum>
|
||||
<enum name="remote_normdevjitter"><para>Their average jitter</para></enum>
|
||||
<enum name="remote_stdevjitter"><para>Their jitter standard deviation</para></enum>
|
||||
<enum name="local_maxjitter"><para>Our max jitter</para></enum>
|
||||
<enum name="local_minjitter"><para>Our minimum jitter</para></enum>
|
||||
<enum name="local_normdevjitter"><para>Our average jitter</para></enum>
|
||||
<enum name="local_stdevjitter"><para>Our jitter standard deviation</para></enum>
|
||||
<enum name="txploss"><para>Transmitted packet loss</para></enum>
|
||||
<enum name="rxploss"><para>Received packet loss</para></enum>
|
||||
<enum name="remote_maxrxploss"><para>Their max lost packets</para></enum>
|
||||
<enum name="remote_minrxploss"><para>Their minimum lost packets</para></enum>
|
||||
<enum name="remote_normdevrxploss"><para>Their average lost packets</para></enum>
|
||||
<enum name="remote_stdevrxploss"><para>Their lost packets standard deviation</para></enum>
|
||||
<enum name="local_maxrxploss"><para>Our max lost packets</para></enum>
|
||||
<enum name="local_minrxploss"><para>Our minimum lost packets</para></enum>
|
||||
<enum name="local_normdevrxploss"><para>Our average lost packets</para></enum>
|
||||
<enum name="local_stdevrxploss"><para>Our lost packets standard deviation</para></enum>
|
||||
<enum name="rtt"><para>Round trip time</para></enum>
|
||||
<enum name="maxrtt"><para>Maximum round trip time</para></enum>
|
||||
<enum name="minrtt"><para>Minimum round trip time</para></enum>
|
||||
<enum name="normdevrtt"><para>Average round trip time</para></enum>
|
||||
<enum name="stdevrtt"><para>Standard deviation round trip time</para></enum>
|
||||
<enum name="local_ssrc"><para>Our Synchronization Source identifier</para></enum>
|
||||
<enum name="remote_ssrc"><para>Their Synchronization Source identifier</para></enum>
|
||||
</enumlist>
|
||||
</parameter>
|
||||
<parameter name="media_type" required="false">
|
||||
<para>When <replaceable>rtcp</replaceable> is specified, the
|
||||
<literal>media_type</literal> parameter may be provided. It specifies
|
||||
which media stream the chosen RTCP parameter should be retrieved
|
||||
from.</para>
|
||||
<enumlist>
|
||||
<enum name="audio">
|
||||
<para>Retrieve information from the audio media stream.</para>
|
||||
<note><para>If not specified, <literal>audio</literal> is used
|
||||
by default.</para></note>
|
||||
</enum>
|
||||
<enum name="video">
|
||||
<para>Retrieve information from the video media stream.</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</parameter>
|
||||
</enum>
|
||||
<enum name="endpoint">
|
||||
<para>R/O The name of the endpoint associated with this channel.
|
||||
Use the <replaceable>PJSIP_ENDPOINT</replaceable> function to obtain
|
||||
further endpoint related information.</para>
|
||||
</enum>
|
||||
<enum name="pjsip">
|
||||
<para>R/O Obtain information about the current PJSIP channel and its
|
||||
session.</para>
|
||||
<parameter name="type" required="true">
|
||||
<para>When <replaceable>pjsip</replaceable> is specified, the
|
||||
<literal>type</literal> parameter must be provided. It specifies
|
||||
which signalling parameter to read.</para>
|
||||
<enumlist>
|
||||
<enum name="secure">
|
||||
<para>Whether or not the signalling uses a secure transport.</para>
|
||||
<enumlist>
|
||||
<enum name="0"><para>The signalling uses a non-secure transport.</para></enum>
|
||||
<enum name="1"><para>The signalling uses a secure transport.</para></enum>
|
||||
</enumlist>
|
||||
</enum>
|
||||
<enum name="target_uri">
|
||||
<para>The request URI of the <literal>INVITE</literal> request associated with the creation of this channel.</para>
|
||||
</enum>
|
||||
<enum name="local_uri">
|
||||
<para>The local URI.</para>
|
||||
</enum>
|
||||
<enum name="remote_uri">
|
||||
<para>The remote URI.</para>
|
||||
</enum>
|
||||
<enum name="t38state">
|
||||
<para>The current state of any T.38 fax on this channel.</para>
|
||||
<enumlist>
|
||||
<enum name="DISABLED"><para>T.38 faxing is disabled on this channel.</para></enum>
|
||||
<enum name="LOCAL_REINVITE"><para>Asterisk has sent a <literal>re-INVITE</literal> to the remote end to initiate a T.38 fax.</para></enum>
|
||||
<enum name="REMOTE_REINVITE"><para>The remote end has sent a <literal>re-INVITE</literal> to Asterisk to initiate a T.38 fax.</para></enum>
|
||||
<enum name="ENABLED"><para>A T.38 fax session has been enabled.</para></enum>
|
||||
<enum name="REJECTED"><para>A T.38 fax session was attempted but was rejected.</para></enum>
|
||||
</enumlist>
|
||||
</enum>
|
||||
<enum name="local_addr">
|
||||
<para>On inbound calls, the full IP address and port number that
|
||||
the <literal>INVITE</literal> request was received on. On outbound
|
||||
calls, the full IP address and port number that the <literal>INVITE</literal>
|
||||
request was transmitted from.</para>
|
||||
</enum>
|
||||
<enum name="remote_addr">
|
||||
<para>On inbound calls, the full IP address and port number that
|
||||
the <literal>INVITE</literal> request was received from. On outbound
|
||||
calls, the full IP address and port number that the <literal>INVITE</literal>
|
||||
request was transmitted to.</para>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</parameter>
|
||||
</enum>
|
||||
</enumlist>
|
||||
</info>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
#include <pjsip.h>
|
||||
#include <pjlib.h>
|
||||
#include <pjsip_ua.h>
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/astobj2.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/acl.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/format.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/res_pjsip.h"
|
||||
#include "asterisk/res_pjsip_session.h"
|
||||
#include "include/chan_pjsip.h"
|
||||
#include "include/dialplan_functions.h"
|
||||
|
||||
/*!
|
||||
* \brief String representations of the T.38 state enum
|
||||
*/
|
||||
static const char *t38state_to_string[T38_MAX_ENUM] = {
|
||||
[T38_DISABLED] = "DISABLED",
|
||||
[T38_LOCAL_REINVITE] = "LOCAL_REINVITE",
|
||||
[T38_PEER_REINVITE] = "REMOTE_REINVITE",
|
||||
[T38_ENABLED] = "ENABLED",
|
||||
[T38_REJECTED] = "REJECTED",
|
||||
};
|
||||
|
||||
/*!
|
||||
* \internal \brief Handle reading RTP information
|
||||
*/
|
||||
static int channel_read_rtp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
|
||||
{
|
||||
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
|
||||
struct chan_pjsip_pvt *pvt;
|
||||
struct ast_sip_session_media *media = NULL;
|
||||
struct ast_sockaddr addr;
|
||||
|
||||
if (!channel) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
|
||||
pvt = channel->pvt;
|
||||
if (!pvt) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(type)) {
|
||||
ast_log(AST_LOG_WARNING, "You must supply a type field for 'rtp' information\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(field) || !strcmp(field, "audio")) {
|
||||
media = pvt->media[SIP_MEDIA_AUDIO];
|
||||
} else if (!strcmp(field, "video")) {
|
||||
media = pvt->media[SIP_MEDIA_VIDEO];
|
||||
} else {
|
||||
ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!media || !media->rtp) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no %s media/RTP session\n",
|
||||
ast_channel_name(chan), S_OR(field, "audio"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!strcmp(type, "src")) {
|
||||
ast_rtp_instance_get_local_address(media->rtp, &addr);
|
||||
ast_copy_string(buf, ast_sockaddr_stringify(&addr), buflen);
|
||||
} else if (!strcmp(type, "dest")) {
|
||||
ast_rtp_instance_get_remote_address(media->rtp, &addr);
|
||||
ast_copy_string(buf, ast_sockaddr_stringify(&addr), buflen);
|
||||
} else if (!strcmp(type, "direct")) {
|
||||
ast_copy_string(buf, ast_sockaddr_stringify(&media->direct_media_addr), buflen);
|
||||
} else if (!strcmp(type, "secure")) {
|
||||
snprintf(buf, buflen, "%u", media->srtp ? 1 : 0);
|
||||
} else if (!strcmp(type, "hold")) {
|
||||
snprintf(buf, buflen, "%u", media->held ? 1 : 0);
|
||||
} else {
|
||||
ast_log(AST_LOG_WARNING, "Unknown type field '%s' specified for 'rtp' information\n", type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal \brief Handle reading RTCP information
|
||||
*/
|
||||
static int channel_read_rtcp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
|
||||
{
|
||||
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
|
||||
struct chan_pjsip_pvt *pvt;
|
||||
struct ast_sip_session_media *media = NULL;
|
||||
|
||||
if (!channel) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
|
||||
pvt = channel->pvt;
|
||||
if (!pvt) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(type)) {
|
||||
ast_log(AST_LOG_WARNING, "You must supply a type field for 'rtcp' information\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(field) || !strcmp(field, "audio")) {
|
||||
media = pvt->media[SIP_MEDIA_AUDIO];
|
||||
} else if (!strcmp(field, "video")) {
|
||||
media = pvt->media[SIP_MEDIA_VIDEO];
|
||||
} else {
|
||||
ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!media || !media->rtp) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no %s media/RTP session\n",
|
||||
ast_channel_name(chan), S_OR(field, "audio"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!strncasecmp(type, "all", 3)) {
|
||||
enum ast_rtp_instance_stat_field stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY;
|
||||
|
||||
if (!strcasecmp(type, "all_jitter")) {
|
||||
stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER;
|
||||
} else if (!strcasecmp(type, "all_rtt")) {
|
||||
stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT;
|
||||
} else if (!strcasecmp(type, "all_loss")) {
|
||||
stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS;
|
||||
}
|
||||
|
||||
if (!ast_rtp_instance_get_quality(media->rtp, stat_field, buf, buflen)) {
|
||||
ast_log(AST_LOG_WARNING, "Unable to retrieve 'rtcp' statistics for %s\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
struct ast_rtp_instance_stats stats;
|
||||
int i;
|
||||
struct {
|
||||
const char *name;
|
||||
enum { INT, DBL } type;
|
||||
union {
|
||||
unsigned int *i4;
|
||||
double *d8;
|
||||
};
|
||||
} lookup[] = {
|
||||
{ "txcount", INT, { .i4 = &stats.txcount, }, },
|
||||
{ "rxcount", INT, { .i4 = &stats.rxcount, }, },
|
||||
{ "txjitter", DBL, { .d8 = &stats.txjitter, }, },
|
||||
{ "rxjitter", DBL, { .d8 = &stats.rxjitter, }, },
|
||||
{ "remote_maxjitter", DBL, { .d8 = &stats.remote_maxjitter, }, },
|
||||
{ "remote_minjitter", DBL, { .d8 = &stats.remote_minjitter, }, },
|
||||
{ "remote_normdevjitter", DBL, { .d8 = &stats.remote_normdevjitter, }, },
|
||||
{ "remote_stdevjitter", DBL, { .d8 = &stats.remote_stdevjitter, }, },
|
||||
{ "local_maxjitter", DBL, { .d8 = &stats.local_maxjitter, }, },
|
||||
{ "local_minjitter", DBL, { .d8 = &stats.local_minjitter, }, },
|
||||
{ "local_normdevjitter", DBL, { .d8 = &stats.local_normdevjitter, }, },
|
||||
{ "local_stdevjitter", DBL, { .d8 = &stats.local_stdevjitter, }, },
|
||||
{ "txploss", INT, { .i4 = &stats.txploss, }, },
|
||||
{ "rxploss", INT, { .i4 = &stats.rxploss, }, },
|
||||
{ "remote_maxrxploss", DBL, { .d8 = &stats.remote_maxrxploss, }, },
|
||||
{ "remote_minrxploss", DBL, { .d8 = &stats.remote_minrxploss, }, },
|
||||
{ "remote_normdevrxploss", DBL, { .d8 = &stats.remote_normdevrxploss, }, },
|
||||
{ "remote_stdevrxploss", DBL, { .d8 = &stats.remote_stdevrxploss, }, },
|
||||
{ "local_maxrxploss", DBL, { .d8 = &stats.local_maxrxploss, }, },
|
||||
{ "local_minrxploss", DBL, { .d8 = &stats.local_minrxploss, }, },
|
||||
{ "local_normdevrxploss", DBL, { .d8 = &stats.local_normdevrxploss, }, },
|
||||
{ "local_stdevrxploss", DBL, { .d8 = &stats.local_stdevrxploss, }, },
|
||||
{ "rtt", DBL, { .d8 = &stats.rtt, }, },
|
||||
{ "maxrtt", DBL, { .d8 = &stats.maxrtt, }, },
|
||||
{ "minrtt", DBL, { .d8 = &stats.minrtt, }, },
|
||||
{ "normdevrtt", DBL, { .d8 = &stats.normdevrtt, }, },
|
||||
{ "stdevrtt", DBL, { .d8 = &stats.stdevrtt, }, },
|
||||
{ "local_ssrc", INT, { .i4 = &stats.local_ssrc, }, },
|
||||
{ "remote_ssrc", INT, { .i4 = &stats.remote_ssrc, }, },
|
||||
{ NULL, },
|
||||
};
|
||||
|
||||
if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
|
||||
ast_log(AST_LOG_WARNING, "Unable to retrieve 'rtcp' statistics for %s\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; !ast_strlen_zero(lookup[i].name); i++) {
|
||||
if (!strcasecmp(type, lookup[i].name)) {
|
||||
if (lookup[i].type == INT) {
|
||||
snprintf(buf, buflen, "%u", *lookup[i].i4);
|
||||
} else {
|
||||
snprintf(buf, buflen, "%f", *lookup[i].d8);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ast_log(AST_LOG_WARNING, "Unrecognized argument '%s' for 'rtcp' information\n", type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal \brief Handle reading signalling information
|
||||
*/
|
||||
static int channel_read_pjsip(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
|
||||
{
|
||||
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
|
||||
char *buf_copy;
|
||||
pjsip_dialog *dlg;
|
||||
|
||||
if (!channel) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
|
||||
dlg = channel->session->inv_session->dlg;
|
||||
|
||||
if (!strcmp(type, "secure")) {
|
||||
snprintf(buf, buflen, "%u", dlg->secure ? 1 : 0);
|
||||
} else if (!strcmp(type, "target_uri")) {
|
||||
pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->target, buf, sizeof(buflen));
|
||||
buf_copy = ast_strdupa(buf);
|
||||
ast_escape_quoted(buf_copy, buf, buflen);
|
||||
} else if (!strcmp(type, "local_uri")) {
|
||||
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, dlg->local.info->uri, buf, sizeof(buflen));
|
||||
buf_copy = ast_strdupa(buf);
|
||||
ast_escape_quoted(buf_copy, buf, buflen);
|
||||
} else if (!strcmp(type, "remote_uri")) {
|
||||
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, dlg->remote.info->uri, buf, sizeof(buflen));
|
||||
buf_copy = ast_strdupa(buf);
|
||||
ast_escape_quoted(buf_copy, buf, buflen);
|
||||
} else if (!strcmp(type, "t38state")) {
|
||||
ast_copy_string(buf, t38state_to_string[channel->session->t38state], buflen);
|
||||
} else if (!strcmp(type, "local_addr")) {
|
||||
RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
|
||||
struct transport_info_data *transport_data;
|
||||
|
||||
datastore = ast_sip_session_get_datastore(channel->session, "transport_info");
|
||||
if (!datastore) {
|
||||
ast_log(AST_LOG_WARNING, "No transport information for channel %s\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
transport_data = datastore->data;
|
||||
|
||||
if (pj_sockaddr_has_addr(&transport_data->local_addr)) {
|
||||
pj_sockaddr_print(&transport_data->local_addr, buf, buflen, 3);
|
||||
}
|
||||
} else if (!strcmp(type, "remote_addr")) {
|
||||
RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
|
||||
struct transport_info_data *transport_data;
|
||||
|
||||
datastore = ast_sip_session_get_datastore(channel->session, "transport_info");
|
||||
if (!datastore) {
|
||||
ast_log(AST_LOG_WARNING, "No transport information for channel %s\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
transport_data = datastore->data;
|
||||
|
||||
if (pj_sockaddr_has_addr(&transport_data->remote_addr)) {
|
||||
pj_sockaddr_print(&transport_data->remote_addr, buf, buflen, 3);
|
||||
}
|
||||
} else {
|
||||
ast_log(AST_LOG_WARNING, "Unrecognized argument '%s' for 'pjsip' information\n", type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief Struct used to push function arguments to task processor */
|
||||
struct pjsip_func_args {
|
||||
struct ast_channel *chan;
|
||||
const char *param;
|
||||
const char *type;
|
||||
const char *field;
|
||||
char *buf;
|
||||
size_t len;
|
||||
int ret;
|
||||
};
|
||||
|
||||
/*! \internal \brief Taskprocessor callback that handles the read on a PJSIP thread */
|
||||
static int read_pjsip(void *data)
|
||||
{
|
||||
struct pjsip_func_args *func_args = data;
|
||||
|
||||
if (!strcmp(func_args->param, "rtp")) {
|
||||
func_args->ret = channel_read_rtp(func_args->chan, func_args->type,
|
||||
func_args->field, func_args->buf,
|
||||
func_args->len);
|
||||
} else if (!strcmp(func_args->param, "rtcp")) {
|
||||
func_args->ret = channel_read_rtcp(func_args->chan, func_args->type,
|
||||
func_args->field, func_args->buf,
|
||||
func_args->len);
|
||||
} else if (!strcmp(func_args->param, "endpoint")) {
|
||||
struct ast_sip_channel_pvt *pvt = ast_channel_tech_pvt(func_args->chan);
|
||||
|
||||
if (!pvt) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(func_args->chan));
|
||||
return -1;
|
||||
}
|
||||
if (!pvt->session || !pvt->session->endpoint) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no endpoint!\n", ast_channel_name(func_args->chan));
|
||||
return -1;
|
||||
}
|
||||
snprintf(func_args->buf, func_args->len, "%s", ast_sorcery_object_get_id(pvt->session->endpoint));
|
||||
} else if (!strcmp(func_args->param, "pjsip")) {
|
||||
func_args->ret = channel_read_pjsip(func_args->chan, func_args->type,
|
||||
func_args->field, func_args->buf,
|
||||
func_args->len);
|
||||
} else {
|
||||
func_args->ret = -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int pjsip_acf_channel_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
|
||||
{
|
||||
struct pjsip_func_args func_args = { 0, };
|
||||
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
|
||||
char *parse = ast_strdupa(data);
|
||||
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(param);
|
||||
AST_APP_ARG(type);
|
||||
AST_APP_ARG(field);
|
||||
);
|
||||
|
||||
/* Check for zero arguments */
|
||||
if (ast_strlen_zero(parse)) {
|
||||
ast_log(LOG_ERROR, "Cannot call %s without arguments\n", cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
/* Sanity check */
|
||||
if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) {
|
||||
ast_log(LOG_ERROR, "Cannot call %s on a non-PJSIP channel\n", cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(buf, 0, len);
|
||||
|
||||
func_args.chan = chan;
|
||||
func_args.param = args.param;
|
||||
func_args.type = args.type;
|
||||
func_args.field = args.field;
|
||||
func_args.buf = buf;
|
||||
func_args.len = len;
|
||||
if (ast_sip_push_task_synchronous(channel->session->serializer, read_pjsip, &func_args)) {
|
||||
ast_log(LOG_WARNING, "Unable to read properties of channel %s: failed to push task\n", ast_channel_name(chan));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return func_args.ret;
|
||||
}
|
||||
|
||||
int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
|
||||
{
|
||||
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_str *, dial, NULL, ast_free_ptr);
|
||||
const char *aor_name;
|
||||
char *rest;
|
||||
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(endpoint_name);
|
||||
AST_APP_ARG(aor_name);
|
||||
AST_APP_ARG(request_user);
|
||||
);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, data);
|
||||
|
||||
if (ast_strlen_zero(args.endpoint_name)) {
|
||||
ast_log(LOG_WARNING, "An endpoint name must be specified when using the '%s' dialplan function\n", cmd);
|
||||
return -1;
|
||||
} else if (!(endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", args.endpoint_name))) {
|
||||
ast_log(LOG_WARNING, "Specified endpoint '%s' was not found\n", args.endpoint_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
aor_name = S_OR(args.aor_name, endpoint->aors);
|
||||
|
||||
if (ast_strlen_zero(aor_name)) {
|
||||
ast_log(LOG_WARNING, "No AOR has been provided and no AORs are configured on endpoint '%s'\n", args.endpoint_name);
|
||||
return -1;
|
||||
} else if (!(dial = ast_str_create(len))) {
|
||||
ast_log(LOG_WARNING, "Could not get enough buffer space for dialing contacts\n");
|
||||
return -1;
|
||||
} else if (!(rest = ast_strdupa(aor_name))) {
|
||||
ast_log(LOG_WARNING, "Could not duplicate provided AORs\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
while ((aor_name = strsep(&rest, ","))) {
|
||||
RAII_VAR(struct ast_sip_aor *, aor, ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
|
||||
RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
|
||||
struct ao2_iterator it_contacts;
|
||||
struct ast_sip_contact *contact;
|
||||
|
||||
if (!aor) {
|
||||
/* If the AOR provided is not found skip it, there may be more */
|
||||
continue;
|
||||
} else if (!(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
|
||||
/* No contacts are available, skip it as well */
|
||||
continue;
|
||||
} else if (!ao2_container_count(contacts)) {
|
||||
/* We were given a container but no contacts are in it... */
|
||||
continue;
|
||||
}
|
||||
|
||||
it_contacts = ao2_iterator_init(contacts, 0);
|
||||
for (; (contact = ao2_iterator_next(&it_contacts)); ao2_ref(contact, -1)) {
|
||||
ast_str_append(&dial, -1, "PJSIP/");
|
||||
|
||||
if (!ast_strlen_zero(args.request_user)) {
|
||||
ast_str_append(&dial, -1, "%s@", args.request_user);
|
||||
}
|
||||
ast_str_append(&dial, -1, "%s/%s&", args.endpoint_name, contact->uri);
|
||||
}
|
||||
ao2_iterator_destroy(&it_contacts);
|
||||
}
|
||||
|
||||
/* Trim the '&' at the end off */
|
||||
ast_str_truncate(dial, ast_str_strlen(dial) - 1);
|
||||
|
||||
ast_copy_string(buf, ast_str_buffer(dial), len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int media_offer_read_av(struct ast_sip_session *session, char *buf,
|
||||
size_t len, enum ast_format_type media_type)
|
||||
{
|
||||
int i, size = 0;
|
||||
struct ast_format fmt;
|
||||
const char *name;
|
||||
|
||||
for (i = 0; ast_codec_pref_index(&session->override_prefs, i, &fmt); ++i) {
|
||||
if (AST_FORMAT_GET_TYPE(fmt.id) != media_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
name = ast_getformatname(&fmt);
|
||||
|
||||
if (ast_strlen_zero(name)) {
|
||||
ast_log(LOG_WARNING, "PJSIP_MEDIA_OFFER unrecognized format %s\n", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* add one since we'll include a comma */
|
||||
size = strlen(name) + 1;
|
||||
len -= size;
|
||||
if ((len) < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* no reason to use strncat here since we have already ensured buf has
|
||||
enough space, so strcat can be safely used */
|
||||
strcat(buf, name);
|
||||
strcat(buf, ",");
|
||||
}
|
||||
|
||||
if (size) {
|
||||
/* remove the extra comma */
|
||||
buf[strlen(buf) - 1] = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct media_offer_data {
|
||||
struct ast_sip_session *session;
|
||||
enum ast_format_type media_type;
|
||||
const char *value;
|
||||
};
|
||||
|
||||
static int media_offer_write_av(void *obj)
|
||||
{
|
||||
struct media_offer_data *data = obj;
|
||||
int i;
|
||||
struct ast_format fmt;
|
||||
/* remove all of the given media type first */
|
||||
for (i = 0; ast_codec_pref_index(&data->session->override_prefs, i, &fmt); ++i) {
|
||||
if (AST_FORMAT_GET_TYPE(fmt.id) == data->media_type) {
|
||||
ast_codec_pref_remove(&data->session->override_prefs, &fmt);
|
||||
}
|
||||
}
|
||||
ast_format_cap_remove_bytype(data->session->req_caps, data->media_type);
|
||||
ast_parse_allow_disallow(&data->session->override_prefs, data->session->req_caps, data->value, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pjsip_acf_media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
|
||||
{
|
||||
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
|
||||
|
||||
if (!strcmp(data, "audio")) {
|
||||
return media_offer_read_av(channel->session, buf, len, AST_FORMAT_TYPE_AUDIO);
|
||||
} else if (!strcmp(data, "video")) {
|
||||
return media_offer_read_av(channel->session, buf, len, AST_FORMAT_TYPE_VIDEO);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
|
||||
{
|
||||
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
|
||||
|
||||
struct media_offer_data mdata = {
|
||||
.session = channel->session,
|
||||
.value = value
|
||||
};
|
||||
|
||||
if (!strcmp(data, "audio")) {
|
||||
mdata.media_type = AST_FORMAT_TYPE_AUDIO;
|
||||
} else if (!strcmp(data, "video")) {
|
||||
mdata.media_type = AST_FORMAT_TYPE_VIDEO;
|
||||
}
|
||||
|
||||
return ast_sip_push_task_synchronous(channel->session->serializer, media_offer_write_av, &mdata);
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013, Digium, Inc.
|
||||
*
|
||||
* 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 PJSIP Channel Driver shared data structures
|
||||
*/
|
||||
|
||||
#ifndef _CHAN_PJSIP_HEADER
|
||||
#define _CHAN_PJSIP_HEADER
|
||||
|
||||
struct ast_sip_session_media;
|
||||
|
||||
/*!
|
||||
* \brief Transport information stored in transport_info datastore
|
||||
*/
|
||||
struct transport_info_data {
|
||||
/*! \brief The address that sent the request */
|
||||
pj_sockaddr remote_addr;
|
||||
/*! \brief Our address that received the request */
|
||||
pj_sockaddr local_addr;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Positions of various media
|
||||
*/
|
||||
enum sip_session_media_position {
|
||||
/*! \brief First is audio */
|
||||
SIP_MEDIA_AUDIO = 0,
|
||||
/*! \brief Second is video */
|
||||
SIP_MEDIA_VIDEO,
|
||||
/*! \brief Last is the size for media details */
|
||||
SIP_MEDIA_SIZE,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The PJSIP channel driver pvt, stored in the \ref ast_sip_channel_pvt
|
||||
* data structure
|
||||
*/
|
||||
struct chan_pjsip_pvt {
|
||||
/*! \brief The available media sessions */
|
||||
struct ast_sip_session_media *media[SIP_MEDIA_SIZE];
|
||||
};
|
||||
|
||||
#endif /* _CHAN_PJSIP_HEADER */
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013, Digium, Inc.
|
||||
*
|
||||
* 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 PJSIP dialplan functions header file
|
||||
*/
|
||||
|
||||
#ifndef _PJSIP_DIALPLAN_FUNCTIONS
|
||||
#define _PJSIP_DIALPLAN_FUNCTIONS
|
||||
|
||||
/*!
|
||||
* \brief CHANNEL function read callback
|
||||
* \param chan The channel the function is called on
|
||||
* \param cmd The name of the function
|
||||
* \param data Arguments passed to the function
|
||||
* \param buf Out buffer that should be populated with the data
|
||||
* \param len Size of the buffer
|
||||
*
|
||||
* \retval 0 on success
|
||||
* \retval -1 on failure
|
||||
*/
|
||||
int pjsip_acf_channel_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len);
|
||||
|
||||
/*!
|
||||
* \brief PJSIP_MEDIA_OFFER function write callback
|
||||
* \param chan The channel the function is called on
|
||||
* \param cmd The name of the function
|
||||
* \param data Arguments passed to the function
|
||||
* \param value Value to be set by the function
|
||||
*
|
||||
* \retval 0 on success
|
||||
* \retval -1 on failure
|
||||
*/
|
||||
int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value);
|
||||
|
||||
/*!
|
||||
* \brief PJSIP_MEDIA_OFFER function read callback
|
||||
* \param chan The channel the function is called on
|
||||
* \param cmd The name of the function
|
||||
* \param data Arguments passed to the function
|
||||
* \param buf Out buffer that should be populated with the data
|
||||
* \param len Size of the buffer
|
||||
*
|
||||
* \retval 0 on success
|
||||
* \retval -1 on failure
|
||||
*/
|
||||
int pjsip_acf_media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len);
|
||||
|
||||
/*!
|
||||
* \brief PJSIP_DIAL_CONTACTS function read callback
|
||||
* \param chan The channel the function is called on
|
||||
* \param cmd The name of the function
|
||||
* \param data Arguments passed to the function
|
||||
* \param buf Out buffer that should be populated with the data
|
||||
* \param len Size of the buffer
|
||||
*
|
||||
* \retval 0 on success
|
||||
* \retval -1 on failure
|
||||
*/
|
||||
int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len);
|
||||
|
||||
#endif /* _PJSIP_DIALPLAN_FUNCTIONS */
|
Loading…
Reference in new issue