Newer
Older
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2013, Digium, Inc.
*
* Joshua Colp <jcolp@digium.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
*
* \author Joshua Colp <jcolp@digium.com>
*
* \brief PSJIP SIP Channel Driver
*
* \ingroup channel_drivers
*/
/*** MODULEINFO
<depend>pjproject</depend>
<depend>res_pjsip</depend>
<depend>res_pjsip_session</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include <pjsip.h>
#include <pjsip_ua.h>
#include <pjlib.h>
#include "asterisk/lock.h"
#include "asterisk/channel.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/rtp_engine.h"
#include "asterisk/acl.h"
#include "asterisk/callerid.h"
#include "asterisk/file.h"
#include "asterisk/cli.h"
#include "asterisk/app.h"
#include "asterisk/musiconhold.h"
#include "asterisk/causes.h"
#include "asterisk/taskprocessor.h"
Joshua Colp
committed
#include "asterisk/stasis_endpoints.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/indications.h"
#include "asterisk/format_cache.h"
#include "asterisk/translate.h"
#include "asterisk/threadstorage.h"
#include "asterisk/features_config.h"
#include "asterisk/pickup.h"
#include "asterisk/test.h"
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
#include "asterisk/stream.h"
#include "pjsip/include/chan_pjsip.h"
#include "pjsip/include/dialplan_functions.h"
#include "pjsip/include/cli_functions.h"
AST_THREADSTORAGE(uniqueid_threadbuf);
#define UNIQUEID_BUFSIZE 256
static const char channel_type[] = "PJSIP";
Joshua Colp
committed
static unsigned int chan_idx;
static void chan_pjsip_pvt_dtor(void *obj)
{
}
/* \brief Asterisk core interaction functions */
static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type,
struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids,
const struct ast_channel *requestor, const char *data, int *cause);
static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text);
static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit);
static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
static int chan_pjsip_call(struct ast_channel *ast, const char *dest, int timeout);
static int chan_pjsip_hangup(struct ast_channel *ast);
static int chan_pjsip_answer(struct ast_channel *ast);
static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast);
static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f);
static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *f);
static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
static int chan_pjsip_transfer(struct ast_channel *ast, const char *target);
static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
static int chan_pjsip_devicestate(const char *data);
static int chan_pjsip_queryoption(struct ast_channel *ast, int option, void *data, int *datalen);
static const char *chan_pjsip_get_uniqueid(struct ast_channel *ast);
/*! \brief PBX interface structure for channel registration */
struct ast_channel_tech chan_pjsip_tech = {
.description = "PJSIP Channel Driver",
.requester = chan_pjsip_request,
.requester_with_stream_topology = chan_pjsip_request_with_stream_topology,
.send_text = chan_pjsip_sendtext,
.send_digit_begin = chan_pjsip_digit_begin,
.send_digit_end = chan_pjsip_digit_end,
.call = chan_pjsip_call,
.hangup = chan_pjsip_hangup,
.answer = chan_pjsip_answer,
.read_stream = chan_pjsip_read_stream,
.write_stream = chan_pjsip_write_stream,
.exception = chan_pjsip_read_stream,
.indicate = chan_pjsip_indicate,
.transfer = chan_pjsip_transfer,
.fixup = chan_pjsip_fixup,
.devicestate = chan_pjsip_devicestate,
.queryoption = chan_pjsip_queryoption,
.func_channel_read = pjsip_acf_channel_read,
.get_pvt_uniqueid = chan_pjsip_get_uniqueid,
.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER
};
/*! \brief SIP session interaction functions */
static void chan_pjsip_session_begin(struct ast_sip_session *session);
static void chan_pjsip_session_end(struct ast_sip_session *session);
static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata);
static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata);
/*! \brief SIP session supplement structure */
static struct ast_sip_session_supplement chan_pjsip_supplement = {
.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL,
.session_begin = chan_pjsip_session_begin,
.session_end = chan_pjsip_session_end,
.incoming_request = chan_pjsip_incoming_request,
.incoming_response = chan_pjsip_incoming_response,
Mark Michelson
committed
/* It is important that this supplement runs after media has been negotiated */
.response_priority = AST_SIP_SESSION_AFTER_MEDIA,
static int chan_pjsip_incoming_ack(struct ast_sip_session *session, struct pjsip_rx_data *rdata);
static struct ast_sip_session_supplement chan_pjsip_ack_supplement = {
.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL,
.incoming_request = chan_pjsip_incoming_ack,
};
/*! \brief Function called by RTP engine to get local audio RTP peer */
static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
struct ast_sip_endpoint *endpoint;
struct ast_datastore *datastore;
struct ast_sip_session_media *media;
if (!channel || !channel->session) {
return AST_RTP_GLUE_RESULT_FORBID;
}
/* XXX Getting the first RTP instance for direct media related stuff seems just
* absolutely wrong. But the native RTP bridge knows no other method than single-stream
* for direct media. So this is the best we can do.
*/
media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
if (!media || !media->rtp) {
return AST_RTP_GLUE_RESULT_FORBID;
}
datastore = ast_sip_session_get_datastore(channel->session, "t38");
if (datastore) {
ao2_ref(datastore, -1);
return AST_RTP_GLUE_RESULT_FORBID;
}
endpoint = channel->session->endpoint;
*instance = media->rtp;
ao2_ref(*instance, +1);
ast_assert(endpoint != NULL);
if (endpoint->media.rtp.encryption != AST_SIP_MEDIA_ENCRYPT_NONE) {
return AST_RTP_GLUE_RESULT_FORBID;
}
if (endpoint->media.direct_media.enabled) {
return AST_RTP_GLUE_RESULT_REMOTE;
}
return AST_RTP_GLUE_RESULT_LOCAL;
}
/*! \brief Function called by RTP engine to get local video RTP peer */
static enum ast_rtp_glue_result chan_pjsip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
struct ast_sip_session_media *media;
if (!channel || !channel->session) {
return AST_RTP_GLUE_RESULT_FORBID;
}
media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
if (!media || !media->rtp) {
return AST_RTP_GLUE_RESULT_FORBID;
}
endpoint = channel->session->endpoint;
*instance = media->rtp;
if (endpoint->media.rtp.encryption != AST_SIP_MEDIA_ENCRYPT_NONE) {
return AST_RTP_GLUE_RESULT_FORBID;
}
return AST_RTP_GLUE_RESULT_LOCAL;
}
/*! \brief Function called by RTP engine to get peer capabilities */
static void chan_pjsip_get_codec(struct ast_channel *chan, struct ast_format_cap *result)
ast_format_cap_append_from_cap(result, ast_channel_nativeformats(chan), AST_MEDIA_TYPE_UNKNOWN);
/*! \brief Destructor function for \ref transport_info_data */
static void transport_info_destroy(void *obj)
{
struct transport_info_data *data = obj;
ast_free(data);
}
/*! \brief Datastore used to store local/remote addresses for the
* INVITE request that created the PJSIP channel */
static struct ast_datastore_info transport_info = {
.type = "chan_pjsip_transport_info",
.destroy = transport_info_destroy,
};
static struct ast_datastore_info direct_media_mitigation_info = { };
static int direct_media_mitigate_glare(struct ast_sip_session *session)
{
RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
if (session->endpoint->media.direct_media.glare_mitigation ==
AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_NONE) {
return 0;
}
datastore = ast_sip_session_get_datastore(session, "direct_media_glare_mitigation");
if (!datastore) {
return 0;
}
/* Removing the datastore ensures we won't try to mitigate glare on subsequent reinvites */
ast_sip_session_remove_datastore(session, "direct_media_glare_mitigation");
if ((session->endpoint->media.direct_media.glare_mitigation ==
AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_OUTGOING &&
session->inv_session->role == PJSIP_ROLE_UAC) ||
(session->endpoint->media.direct_media.glare_mitigation ==
AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_INCOMING &&
session->inv_session->role == PJSIP_ROLE_UAS)) {
return 1;
}
return 0;
}
/*! \brief Helper function to find the position for RTCP */
static int rtp_find_rtcp_fd_position(struct ast_sip_session *session, struct ast_rtp_instance *rtp)
{
int index;
for (index = 0; index < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++index) {
struct ast_sip_session_media_read_callback_state *callback_state =
AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, index);
if (callback_state->fd != ast_rtp_instance_fd(rtp, 1)) {
continue;
}
return index;
}
return -1;
}
/*!
* \pre chan is locked
*/
static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instance *rtp,
struct ast_sip_session_media *media, struct ast_sip_session *session)
int changed = 0, position = -1;
if (media->rtp) {
position = rtp_find_rtcp_fd_position(session, media->rtp);
}
if (rtp) {
changed = ast_rtp_instance_get_and_cmp_remote_address(rtp, &media->direct_media_addr);
if (media->rtp) {
if (position != -1) {
ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, -1);
}
ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 0);
}
} else if (!ast_sockaddr_isnull(&media->direct_media_addr)){
ast_sockaddr_setnull(&media->direct_media_addr);
changed = 1;
if (media->rtp) {
ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 1);
if (position != -1) {
ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, ast_rtp_instance_fd(media->rtp, 1));
}
}
}
return changed;
}
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
struct rtp_direct_media_data {
struct ast_channel *chan;
struct ast_rtp_instance *rtp;
struct ast_rtp_instance *vrtp;
struct ast_format_cap *cap;
struct ast_sip_session *session;
};
static void rtp_direct_media_data_destroy(void *data)
{
struct rtp_direct_media_data *cdata = data;
ao2_cleanup(cdata->session);
ao2_cleanup(cdata->cap);
ao2_cleanup(cdata->vrtp);
ao2_cleanup(cdata->rtp);
ao2_cleanup(cdata->chan);
}
static struct rtp_direct_media_data *rtp_direct_media_data_create(
struct ast_channel *chan, struct ast_rtp_instance *rtp, struct ast_rtp_instance *vrtp,
const struct ast_format_cap *cap, struct ast_sip_session *session)
{
struct rtp_direct_media_data *cdata = ao2_alloc(sizeof(*cdata), rtp_direct_media_data_destroy);
if (!cdata) {
return NULL;
}
cdata->chan = ao2_bump(chan);
cdata->rtp = ao2_bump(rtp);
cdata->vrtp = ao2_bump(vrtp);
cdata->cap = ao2_bump((struct ast_format_cap *)cap);
cdata->session = ao2_bump(session);
return cdata;
}
static int send_direct_media_request(void *data)
{
struct rtp_direct_media_data *cdata = data;
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(cdata->chan);
struct ast_sip_session *session;
int changed = 0;
int res = 0;
/* XXX In an ideal world each media stream would be direct, but for now preserve behavior
* and connect only the default media sessions for audio and video.
*/
/* The channel needs to be locked when checking for RTP changes.
* Otherwise, we could end up destroying an underlying RTCP structure
* at the same time that the channel thread is attempting to read RTCP
*/
ast_channel_lock(cdata->chan);
session = channel->session;
if (session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {
changed |= check_for_rtp_changes(
cdata->chan, cdata->rtp, session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO], session);
}
if (session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]) {
changed |= check_for_rtp_changes(
cdata->chan, cdata->vrtp, session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO], session);
}
ast_channel_unlock(cdata->chan);
if (direct_media_mitigate_glare(cdata->session)) {
ast_debug(4, "Disregarding setting RTP on %s: mitigating re-INVITE glare\n", ast_channel_name(cdata->chan));
ao2_ref(cdata, -1);
return 0;
}
if (cdata->cap && ast_format_cap_count(cdata->cap) &&
!ast_format_cap_identical(cdata->session->direct_media_cap, cdata->cap)) {
ast_format_cap_remove_by_type(cdata->session->direct_media_cap, AST_MEDIA_TYPE_UNKNOWN);
ast_format_cap_append_from_cap(cdata->session->direct_media_cap, cdata->cap, AST_MEDIA_TYPE_UNKNOWN);
changed = 1;
}
if (changed) {
ast_debug(4, "RTP changed on %s; initiating direct media update\n", ast_channel_name(cdata->chan));
res = ast_sip_session_refresh(cdata->session, NULL, NULL, NULL,
cdata->session->endpoint->media.direct_media.method, 1, NULL);
}
ao2_ref(cdata, -1);
return res;
}
/*! \brief Function called by RTP engine to change where the remote party should send media */
static int chan_pjsip_set_rtp_peer(struct ast_channel *chan,
struct ast_rtp_instance *rtp,
struct ast_rtp_instance *vrtp,
struct ast_rtp_instance *tpeer,
const struct ast_format_cap *cap,
int nat_active)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
struct ast_sip_session *session = channel->session;
struct rtp_direct_media_data *cdata;
/* Don't try to do any direct media shenanigans on early bridges */
if ((rtp || vrtp || tpeer) && !ast_channel_is_bridged(chan)) {
ast_debug(4, "Disregarding setting RTP on %s: channel is not bridged\n", ast_channel_name(chan));
if (nat_active && session->endpoint->media.direct_media.disable_on_nat) {
ast_debug(4, "Disregarding setting RTP on %s: NAT is active\n", ast_channel_name(chan));
cdata = rtp_direct_media_data_create(chan, rtp, vrtp, cap, session);
if (!cdata) {
if (ast_sip_push_task(session->serializer, send_direct_media_request, cdata)) {
ast_log(LOG_ERROR, "Unable to send direct media request for channel %s\n", ast_channel_name(chan));
ao2_ref(cdata, -1);
}
return 0;
}
/*! \brief Local glue for interacting with the RTP engine core */
static struct ast_rtp_glue chan_pjsip_rtp_glue = {
.type = "PJSIP",
.get_rtp_info = chan_pjsip_get_rtp_peer,
.get_vrtp_info = chan_pjsip_get_vrtp_peer,
.get_codec = chan_pjsip_get_codec,
.update_peer = chan_pjsip_set_rtp_peer,
static void set_channel_on_rtp_instance(const struct ast_sip_session *session,
const char *channel_id)
{
int i;
for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->sessions); ++i) {
struct ast_sip_session_media *session_media;
session_media = AST_VECTOR_GET(&session->active_media_state->sessions, i);
if (!session_media || !session_media->rtp) {
continue;
}
ast_rtp_instance_set_channel_id(session_media->rtp, channel_id);
}
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
}
/*!
* \brief Determine if a topology is compatible with format capabilities
*
* This will return true if ANY formats in the topology are compatible with the format
* capabilities.
*
* XXX When supporting true multistream, we will need to be sure to mark which streams from
* top1 are compatible with which streams from top2. Then the ones that are not compatible
* will need to be marked as "removed" so that they are negotiated as expected.
*
* \param top Topology
* \param cap Format capabilities
* \retval 1 The topology has at least one compatible format
* \retval 0 The topology has no compatible formats or an error occurred.
*/
static int compatible_formats_exist(struct ast_stream_topology *top, struct ast_format_cap *cap)
{
struct ast_format_cap *cap_from_top;
int res;
cap_from_top = ast_format_cap_from_stream_topology(top);
if (!cap_from_top) {
return 0;
}
res = ast_format_cap_iscompatible(cap_from_top, cap);
ao2_ref(cap_from_top, -1);
return res;
}
/*! \brief Function called to create a new PJSIP Asterisk channel */
static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int state, const char *exten, const char *title, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *cid_name)
{
struct ast_channel *chan;
struct ast_format_cap *caps;
RAII_VAR(struct chan_pjsip_pvt *, pvt, NULL, ao2_cleanup);
struct ast_sip_channel_pvt *channel;
struct ast_stream_topology *topology;
if (!(pvt = ao2_alloc_options(sizeof(*pvt), chan_pjsip_pvt_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
return NULL;
}
chan = ast_channel_alloc_with_endpoint(1, state,
S_COR(session->id.number.valid, session->id.number.str, ""),
S_COR(session->id.name.valid, session->id.name.str, ""),
session->endpoint->accountcode,
exten, session->endpoint->context,
assignedids, requestor, 0,
session->endpoint->persistent, "PJSIP/%s-%08x",
ast_sorcery_object_get_id(session->endpoint),
(unsigned) ast_atomic_fetchadd_int((int *) &chan_idx, +1));
Matthew Jordan
committed
if (!chan) {
ast_channel_tech_set(chan, &chan_pjsip_tech);
if (!(channel = ast_sip_channel_pvt_alloc(pvt, session))) {
ast_hangup(chan);
return NULL;
}
ast_channel_tech_pvt_set(chan, channel);
if (!ast_stream_topology_get_count(session->pending_media_state->topology) ||
!compatible_formats_exist(session->pending_media_state->topology, session->endpoint->media.codecs)) {
caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
if (!caps) {
ast_channel_unlock(chan);
ast_hangup(chan);
return NULL;
}
ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);
topology = ast_stream_topology_clone(session->endpoint->media.topology);
caps = ast_format_cap_from_stream_topology(session->pending_media_state->topology);
topology = ast_stream_topology_clone(session->pending_media_state->topology);
}
if (!topology || !caps) {
ao2_cleanup(caps);
ast_stream_topology_free(topology);
ast_channel_unlock(chan);
ast_hangup(chan);
return NULL;
ast_channel_stage_snapshot(chan);
ast_channel_nativeformats_set(chan, caps);
ast_channel_set_stream_topology(chan, topology);
Richard Mudgett
committed
if (!ast_format_cap_empty(caps)) {
struct ast_format *fmt;
fmt = ast_format_cap_get_best_by_type(caps, AST_MEDIA_TYPE_AUDIO);
if (!fmt) {
/* Since our capabilities aren't empty, this will succeed */
fmt = ast_format_cap_get_format(caps, 0);
}
ast_channel_set_writeformat(chan, fmt);
ast_channel_set_rawwriteformat(chan, fmt);
ast_channel_set_readformat(chan, fmt);
ast_channel_set_rawreadformat(chan, fmt);
ao2_ref(fmt, -1);
}
ao2_ref(caps, -1);
if (state == AST_STATE_RING) {
ast_channel_rings_set(chan, 1);
}
ast_channel_adsicpe_set(chan, AST_ADSI_UNAVAILABLE);
ast_party_id_copy(&ast_channel_caller(chan)->id, &session->id);
ast_party_id_copy(&ast_channel_caller(chan)->ani, &session->id);
if (!ast_strlen_zero(exten)) {
/* Set provided DNID on the new channel. */
ast_channel_dialed(chan)->number.str = ast_strdup(exten);
}
ast_channel_priority_set(chan, 1);
ast_channel_callgroup_set(chan, session->endpoint->pickup.callgroup);
ast_channel_pickupgroup_set(chan, session->endpoint->pickup.pickupgroup);
ast_channel_named_callgroups_set(chan, session->endpoint->pickup.named_callgroups);
ast_channel_named_pickupgroups_set(chan, session->endpoint->pickup.named_pickupgroups);
if (!ast_strlen_zero(session->endpoint->language)) {
ast_channel_language_set(chan, session->endpoint->language);
}
if (!ast_strlen_zero(session->endpoint->zone)) {
struct ast_tone_zone *zone = ast_get_indication_zone(session->endpoint->zone);
if (!zone) {
ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone. Check indications.conf for available country codes.\n", session->endpoint->zone);
}
ast_channel_zone_set(chan, zone);
}
Mark Michelson
committed
for (var = session->endpoint->channel_vars; var; var = var->next) {
char buf[512];
pbx_builtin_setvar_helper(chan, var->name, ast_get_encoded_str(
var->value, buf, sizeof(buf)));
}
ast_channel_stage_snapshot_done(chan);
set_channel_on_rtp_instance(session, ast_channel_uniqueid(chan));
return chan;
}
static int answer(void *data)
{
pj_status_t status = PJ_SUCCESS;
pjsip_tx_data *packet = NULL;
struct ast_sip_session *session = data;
Joshua Colp
committed
if (session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n",
session->inv_session->cause,
pjsip_get_status_text(session->inv_session->cause)->ptr);
#ifdef HAVE_PJSIP_INV_SESSION_REF
pjsip_inv_dec_ref(session->inv_session);
#endif
Joshua Colp
committed
return 0;
}
pjsip_dlg_inc_lock(session->inv_session->dlg);
if (session->inv_session->invite_tsx) {
status = pjsip_inv_answer(session->inv_session, 200, NULL, NULL, &packet);
} else {
ast_log(LOG_ERROR,"Cannot answer '%s' because there is no associated SIP transaction\n",
ast_channel_name(session->channel));
}
pjsip_dlg_dec_lock(session->inv_session->dlg);
if (status == PJ_SUCCESS && packet) {
ast_sip_session_send_response(session, packet);
}
#ifdef HAVE_PJSIP_INV_SESSION_REF
pjsip_inv_dec_ref(session->inv_session);
#endif
if (status != PJ_SUCCESS) {
char err[PJ_ERR_MSG_SIZE];
pj_strerror(status, err, sizeof(err));
ast_log(LOG_WARNING,"Cannot answer '%s': %s\n",
ast_channel_name(session->channel), err);
/*
* Return this value so we can distinguish between this
* failure and the threadpool synchronous push failing.
*/
return -2;
}
return 0;
/*! \brief Function called by core when we should answer a PJSIP session */
static int chan_pjsip_answer(struct ast_channel *ast)
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
Kevin Harwell
committed
struct ast_sip_session *session;
if (ast_channel_state(ast) == AST_STATE_UP) {
return 0;
}
ast_setstate(ast, AST_STATE_UP);
Kevin Harwell
committed
session = ao2_bump(channel->session);
#ifdef HAVE_PJSIP_INV_SESSION_REF
if (pjsip_inv_add_ref(session->inv_session) != PJ_SUCCESS) {
ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
ao2_ref(session, -1);
return -1;
}
#endif
Kevin Harwell
committed
/* the answer task needs to be pushed synchronously otherwise a race condition
can occur between this thread and bridging (specifically when native bridging
attempts to do direct media) */
ast_channel_unlock(ast);
res = ast_sip_push_task_synchronous(session->serializer, answer, session);
if (res) {
if (res == -1) {
ast_log(LOG_ERROR,"Cannot answer '%s': Unable to push answer task to the threadpool.\n",
ast_channel_name(session->channel));
#ifdef HAVE_PJSIP_INV_SESSION_REF
pjsip_inv_dec_ref(session->inv_session);
Kevin Harwell
committed
ao2_ref(session, -1);
ast_channel_lock(ast);
Kevin Harwell
committed
ao2_ref(session, -1);
ast_channel_lock(ast);
/*! \brief Internal helper function called when CNG tone is detected */
static struct ast_frame *chan_pjsip_cng_tone_detected(struct ast_sip_session *session, struct ast_frame *f)
{
const char *target_context;
int exists;
dsp_features = ast_dsp_get_features(session->dsp);
dsp_features &= ~DSP_FEATURE_FAX_DETECT;
if (dsp_features) {
ast_dsp_set_features(session->dsp, dsp_features);
} else {
ast_dsp_free(session->dsp);
session->dsp = NULL;
}
/* If already executing in the fax extension don't do anything */
if (!strcmp(ast_channel_exten(session->channel), "fax")) {
return f;
}
target_context = S_OR(ast_channel_macrocontext(session->channel), ast_channel_context(session->channel));
/*
* We need to unlock the channel here because ast_exists_extension has the
* potential to start and stop an autoservice on the channel. Such action
* is prone to deadlock if the channel is locked.
*
* ast_async_goto() has its own restriction on not holding the channel lock.
*/
ast_channel_unlock(session->channel);
ast_frfree(f);
f = &ast_null_frame;
exists = ast_exists_extension(session->channel, target_context, "fax", 1,
S_COR(ast_channel_caller(session->channel)->id.number.valid,
ast_channel_caller(session->channel)->id.number.str, NULL));
if (exists) {
ast_verb(2, "Redirecting '%s' to fax extension due to CNG detection\n",
ast_channel_name(session->channel));
pbx_builtin_setvar_helper(session->channel, "FAXEXTEN", ast_channel_exten(session->channel));
if (ast_async_goto(session->channel, target_context, "fax", 1)) {
ast_log(LOG_ERROR, "Failed to async goto '%s' into fax extension in '%s'\n",
ast_channel_name(session->channel), target_context);
}
} else {
ast_log(LOG_NOTICE, "FAX CNG detected on '%s' but no fax extension in '%s'\n",
ast_channel_name(session->channel), target_context);
}
ast_channel_lock(session->channel);
/*!
* \brief Function called by core to read any waiting frames
*
* \note The channel is already locked.
*/
static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast)
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
struct ast_sip_session *session = channel->session;
struct ast_sip_session_media_read_callback_state *callback_state;
int fdno = ast_channel_fdno(ast) - AST_EXTENDED_FDS;
if (fdno >= AST_VECTOR_SIZE(&session->active_media_state->read_callbacks)) {
return &ast_null_frame;
}
callback_state = AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, fdno);
f = callback_state->read_callback(session, callback_state->session);
if (!f) {
if (f->frametype != AST_FRAME_VOICE ||
callback_state->session != session->active_media_state->default_session[callback_state->session->type]) {
session = channel->session;
Joshua Colp
committed
/*
* Asymmetric RTP only has one native format set at a time.
* Therefore we need to update the native format to the current
* raw read format BEFORE the native format check
*/
if (!session->endpoint->asymmetric_rtp_codec &&
ast_format_cmp(ast_channel_rawwriteformat(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
struct ast_format_cap *caps;
/* For maximum compatibility we ensure that the formats match that of the received media */
ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when we're sending '%s', switching to match\n",
ast_format_get_name(f->subclass.format), ast_channel_name(ast),
ast_format_get_name(ast_channel_rawwriteformat(ast)));
caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
if (caps) {
ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(ast), AST_MEDIA_TYPE_UNKNOWN);
ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_AUDIO);
ast_format_cap_append(caps, f->subclass.format, 0);
ast_channel_nativeformats_set(ast, caps);
ao2_ref(caps, -1);
}
ast_set_write_format_path(ast, ast_channel_writeformat(ast), f->subclass.format);
ast_set_read_format_path(ast, ast_channel_readformat(ast), f->subclass.format);
if (ast_channel_is_bridged(ast)) {
ast_channel_set_unbridged_nolock(ast, 1);
}
}
if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when it has not been negotiated\n",
ast_format_get_name(f->subclass.format), ast_channel_name(ast));
ast_frfree(f);
return &ast_null_frame;
}
if (session->dsp) {
int dsp_features;
dsp_features = ast_dsp_get_features(session->dsp);
if ((dsp_features & DSP_FEATURE_FAX_DETECT)
&& session->endpoint->faxdetect_timeout
&& session->endpoint->faxdetect_timeout <= ast_channel_get_up_time(ast)) {
dsp_features &= ~DSP_FEATURE_FAX_DETECT;
if (dsp_features) {
ast_dsp_set_features(session->dsp, dsp_features);
} else {
ast_dsp_free(session->dsp);
session->dsp = NULL;
}
ast_debug(3, "Channel driver fax CNG detection timeout on %s\n",
ast_channel_name(ast));
}
}
if (session->dsp) {
f = ast_dsp_process(ast, session->dsp, f);
if (f && (f->frametype == AST_FRAME_DTMF)) {
ast_debug(3, "Channel driver fax CNG detected on %s\n",
ast_channel_name(ast));
f = chan_pjsip_cng_tone_detected(session, f);
} else {
ast_debug(3, "* Detected inband DTMF '%c' on '%s'\n", f->subclass.integer,
ast_channel_name(ast));
}
}
}
return f;
}
static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *frame)
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
struct ast_sip_session *session = channel->session;
struct ast_sip_session_media *media = NULL;
/* The core provides a guarantee that the stream will exist when we are called if stream_num is provided */
if (stream_num >= 0) {
/* What is not guaranteed is that a media session will exist */
if (stream_num < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions)) {
media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, stream_num);
}
}
switch (frame->frametype) {
case AST_FRAME_VOICE:
if (!media) {
return 0;
} else if (media->type != AST_MEDIA_TYPE_AUDIO) {
ast_debug(3, "Channel %s stream %d is of type '%s', not audio!\n",
ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
return 0;
} else if (media == channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO] &&
ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
Alexander Traud
committed
struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
struct ast_str *write_transpath = ast_str_alloca(256);
struct ast_str *read_transpath = ast_str_alloca(256);
"Channel %s asked to send %s frame when native formats are %s (rd:%s->%s;%s wr:%s->%s;%s)\n",
ast_channel_name(ast),
ast_format_get_name(frame->subclass.format),
ast_format_cap_get_names(ast_channel_nativeformats(ast), &cap_buf),
ast_format_get_name(ast_channel_rawreadformat(ast)),
ast_format_get_name(ast_channel_readformat(ast)),
ast_translate_path_to_str(ast_channel_readtrans(ast), &read_transpath),
ast_format_get_name(ast_channel_writeformat(ast)),
ast_format_get_name(ast_channel_rawwriteformat(ast)),
ast_translate_path_to_str(ast_channel_writetrans(ast), &write_transpath));
} else if (media->write_callback) {
res = media->write_callback(session, media, frame);
}
break;
case AST_FRAME_VIDEO:
if (!media) {
return 0;
} else if (media->type != AST_MEDIA_TYPE_VIDEO) {
ast_debug(3, "Channel %s stream %d is of type '%s', not video!\n",
ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
return 0;
} else if (media->write_callback) {
res = media->write_callback(session, media, frame);
if (!media) {
return 0;
} else if (media->type != AST_MEDIA_TYPE_IMAGE) {
ast_debug(3, "Channel %s stream %d is of type '%s', not image!\n",
ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
return 0;
} else if (media->write_callback) {
res = media->write_callback(session, media, frame);
}
ast_log(LOG_WARNING, "Can't send %u type frames with PJSIP\n", frame->frametype);
break;
}
return res;
}
static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
{
return chan_pjsip_write_stream(ast, -1, frame);
}
/*! \brief Function called by core to change the underlying owner channel */
static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(newchan);
/*
* The masquerade has suspended the channel's session
* serializer so we can safely change it outside of
* the serializer thread.
*/
channel->session->channel = newchan;
set_channel_on_rtp_instance(channel->session, ast_channel_uniqueid(newchan));