aboutsummaryrefslogtreecommitdiffstats
path: root/channels
diff options
context:
space:
mode:
authormmichelson <mmichelson@f38db490-d61c-443f-a65b-d21fe96a405b>2010-04-09 15:31:32 +0000
committermmichelson <mmichelson@f38db490-d61c-443f-a65b-d21fe96a405b>2010-04-09 15:31:32 +0000
commit0eb1e5407a6eacd46d98e134dc81e8b857c103b7 (patch)
tree0b1d16ff83df2f35441f03a082b848262b8a2557 /channels
parent6c57cdc6ac82a6a6700ebdb788d690471d8fc49d (diff)
Merge Call completion support into trunk.
From Reviewboard: CCSS stands for Call Completion Supplementary Services. An admittedly out-of-date overview of the architecture can be found in the file doc/CCSS_architecture.pdf in the CCSS branch. Off the top of my head, the big differences between what is implemented and what is in the document are as follows: 1. We did not end up modifying the Hangup application at all. 2. The document states that a single call completion monitor may be used across multiple calls to the same device. This proved to not be such a good idea when implementing protocol-specific monitors, and so we ended up using one monitor per-device per-call. 3. There are some configuration options which were conceived after the document was written. These are documented in the ccss.conf.sample that is on this review request. For some basic understanding of terminology used throughout this code, see the ccss.tex document that is on this review. This implements CCBS and CCNR in several flavors. First up is a "generic" implementation, which can work over any channel technology provided that the channel technology can accurately report device state. Call completion is requested using the dialplan application CallCompletionRequest and can be canceled using CallCompletionCancel. Device state subscriptions are used in order to monitor the state of called parties. Next, there is a SIP-specific implementation of call completion. This method uses the methods outlined in draft-ietf-bliss-call-completion-06 to implement call completion using SIP signaling. There are a few things to note here: * The agent/monitor terminology used throughout Asterisk sometimes is the reverse of what is defined in the referenced draft. * Implementation of the draft required support for SIP PUBLISH. I attempted to write this in a generic-enough fashion such that if someone were to want to write PUBLISH support for other event packages, such as dialog-state or presence, most of the effort would be in writing callbacks specific to the event package. * A subportion of supporting PUBLISH reception was that we had to implement a PIDF parser. The PIDF support added is a bit minimal. I first wrote a validation routine to ensure that the PIDF document is formatted properly. The rest of the PIDF reading is done in-line in the call-completion-specific PUBLISH-handling code. In other words, while there is PIDF support here, it is not in any state where it could easily be applied to other event packages as is. Finally, there are a variety of ISDN-related call completion protocols supported. These were written by Richard Mudgett, and as such I can't really say much about their implementation. There are notes in the CHANGES file that indicate the ISDN protocols over which call completion is supported. Review: https://reviewboard.asterisk.org/r/523 git-svn-id: http://svn.digium.com/svn/asterisk/trunk@256528 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'channels')
-rw-r--r--channels/chan_dahdi.c915
-rw-r--r--channels/chan_local.c16
-rw-r--r--channels/chan_sip.c1914
-rw-r--r--channels/sig_analog.c26
-rw-r--r--channels/sig_analog.h2
-rw-r--r--channels/sig_pri.c1552
-rw-r--r--channels/sig_pri.h102
-rw-r--r--channels/sip/include/sip.h365
8 files changed, 4699 insertions, 193 deletions
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 356106e4a..d9ae8a6f5 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -116,6 +116,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/event.h"
#include "asterisk/devicestate.h"
#include "asterisk/paths.h"
+#include "asterisk/ccss.h"
/*** DOCUMENTATION
<application name="DAHDISendKeypadFacility" language="en_US">
@@ -608,6 +609,11 @@ struct dahdi_pri {
static struct dahdi_pri pris[NUM_SPANS];
+#if defined(HAVE_PRI_CCSS)
+/*! DAHDI PRI CCSS agent and monitor type name. */
+static const char dahdi_pri_cc_type[] = "DAHDI/PRI";
+#endif /* defined(HAVE_PRI_CCSS) */
+
#else
/*! Shut up the compiler */
struct dahdi_pri;
@@ -1252,6 +1258,14 @@ struct dahdi_pvt {
/*! \brief TRUE if confrence is muted. */
int muting;
void *sig_pvt;
+ struct ast_cc_config_params *cc_params;
+ /* DAHDI channel names may differ greatly from the
+ * string that was provided to an app such as Dial. We
+ * need to save the original string passed to dahdi_request
+ * for call completion purposes. This way, we can replicate
+ * the original dialed string later.
+ */
+ char dialstring[AST_CHANNEL_NAME];
};
static struct dahdi_pvt *iflist = NULL; /*!< Main interface list start */
@@ -1315,6 +1329,12 @@ static struct dahdi_chan_conf dahdi_chan_conf_default(void)
.nodetype = PRI_CPE,
.qsigchannelmapping = DAHDI_CHAN_MAPPING_PHYSICAL,
+#if defined(HAVE_PRI_CCSS)
+ .cc_ptmp_recall_mode = 1,/* specificRecall */
+ .cc_qsig_signaling_link_req = 1,/* retain */
+ .cc_qsig_signaling_link_rsp = 1,/* retain */
+#endif /* defined(HAVE_PRI_CCSS) */
+
.minunused = 2,
.idleext = "",
.idledial = "",
@@ -1398,6 +1418,7 @@ static struct dahdi_chan_conf dahdi_chan_conf_default(void)
.buf_policy = DAHDI_POLICY_IMMEDIATE,
.buf_no = numbufs,
.usefaxbuffers = 0,
+ .cc_params = ast_cc_config_params_init(),
},
.timing = {
.prewinktime = -1,
@@ -1433,6 +1454,8 @@ static int dahdi_setoption(struct ast_channel *chan, int option, void *data, int
static int dahdi_queryoption(struct ast_channel *chan, int option, void *data, int *datalen);
static int dahdi_func_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len);
static int dahdi_func_write(struct ast_channel *chan, const char *function, char *data, const char *value);
+static int dahdi_devicestate(void *data);
+static int dahdi_cc_callback(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback);
static const struct ast_channel_tech dahdi_tech = {
.type = "DAHDI",
@@ -1455,6 +1478,8 @@ static const struct ast_channel_tech dahdi_tech = {
.queryoption = dahdi_queryoption,
.func_channel_read = dahdi_func_read,
.func_channel_write = dahdi_func_write,
+ .devicestate = dahdi_devicestate,
+ .cc_callback = dahdi_cc_callback,
};
#define GET_CHANNEL(p) ((p)->channel)
@@ -2152,6 +2177,13 @@ static void my_set_pulsedial(void *pvt, int flag)
p->pulsedial = flag;
}
+static const char *my_get_orig_dialstring(void *pvt)
+{
+ struct dahdi_pvt *p = pvt;
+
+ return p->dialstring;
+}
+
static void my_increase_ss_count(void)
{
ast_mutex_lock(&ss_thread_lock);
@@ -2785,6 +2817,160 @@ static void my_pri_set_rdnis(void *pvt, const char *rdnis)
ast_copy_string(p->rdnis, rdnis, sizeof(p->rdnis));
}
+/*!
+ * \internal
+ * \brief Make a dialstring for native ISDN CC to recall properly.
+ * \since 1.8
+ *
+ * \param priv Channel private control structure.
+ * \param buf Where to put the modified dialstring.
+ * \param buf_size Size of modified dialstring buffer.
+ *
+ * \details
+ * original dialstring:
+ * DAHDI/[i<span>-]<channel#>[c|r<cadance#>|d][/extension[/options]]
+ * DAHDI/[i<span>-](g|G|r|R)<group#(0-63)>[c|r<cadance#>|d][/extension[/options]]
+ *
+ * The modified dialstring will have prefixed the channel-group section
+ * with the ISDN channel restriction.
+ *
+ * buf:
+ * DAHDI/i<span>-<channel#>[c|r<cadance#>|d][/extension[/options]]
+ * DAHDI/i<span>-(g|G|r|R)<group#(0-63)>[c|r<cadance#>|d][/extension[/options]]
+ *
+ * The routine will check to see if the ISDN channel restriction is already
+ * in the original dialstring.
+ *
+ * \return Nothing
+ */
+static void my_pri_make_cc_dialstring(void *priv, char *buf, size_t buf_size)
+{
+ char *dial;
+ struct dahdi_pvt *pvt;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(tech); /* channel technology token */
+ AST_APP_ARG(group); /* channel/group token */
+ //AST_APP_ARG(ext); /* extension token */
+ //AST_APP_ARG(opts); /* options token */
+ //AST_APP_ARG(other); /* Any remining unused arguments */
+ );
+
+ pvt = priv;
+ dial = ast_strdupa(pvt->dialstring);
+ AST_NONSTANDARD_APP_ARGS(args, dial, '/');
+ if (!args.tech) {
+ ast_copy_string(buf, pvt->dialstring, buf_size);
+ return;
+ }
+ if (!args.group) {
+ /* Append the ISDN span channel restriction to the dialstring. */
+ snprintf(buf, buf_size, "%s/i%d-", args.tech, pvt->pri->span);
+ return;
+ }
+ if (args.group[0] == 'i') {
+ /* The ISDN span channel restriction is already in the dialstring. */
+ ast_copy_string(buf, pvt->dialstring, buf_size);
+ return;
+ }
+ /* Insert the ISDN span channel restriction into the dialstring. */
+ snprintf(buf, buf_size, "%s/i%d-%s", args.tech, pvt->pri->span, args.group);
+}
+
+/*!
+ * \internal
+ * \brief Reevaluate the PRI span device state.
+ * \since 1.8
+ *
+ * \param pri Asterisk D channel control structure.
+ *
+ * \return Nothing
+ *
+ * \note Assumes the pri->lock is already obtained.
+ */
+static void dahdi_pri_update_span_devstate(struct sig_pri_pri *pri)
+{
+ unsigned idx;
+ unsigned num_b_chans; /* Number of B channels provisioned on the span. */
+ unsigned in_use; /* Number of B channels in use on the span. */
+ unsigned in_alarm; /* TRUE if the span is in alarm condition. */
+ enum ast_device_state new_state;
+
+ /* Count the number of B channels and the number of B channels in use. */
+ num_b_chans = 0;
+ in_use = 0;
+ in_alarm = 1;
+ for (idx = pri->numchans; idx--;) {
+ if (pri->pvts[idx] && !pri->pvts[idx]->no_b_channel) {
+ /* This is a B channel interface. */
+ ++num_b_chans;
+ if (pri->pvts[idx]->owner
+#if defined(HAVE_PRI_SERVICE_MESSAGES)
+ /* Out-of-service B channels are "in-use". */
+ && pri->pvts[idx]->service_status
+#endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */
+ ) {
+ ++in_use;
+ }
+ if (!pri->pvts[idx]->inalarm) {
+ /* There is a channel that is not in alarm. */
+ in_alarm = 0;
+ }
+ }
+ }
+
+ /* Update the span congestion device state and report any change. */
+ if (in_alarm) {
+ new_state = AST_DEVICE_UNAVAILABLE;
+ } else {
+ new_state = num_b_chans == in_use ? AST_DEVICE_BUSY : AST_DEVICE_NOT_INUSE;
+ }
+ if (pri->congestion_devstate != new_state) {
+ pri->congestion_devstate = new_state;
+ ast_devstate_changed(AST_DEVICE_UNKNOWN, "DAHDI/I%d/congestion", pri->span);
+ }
+#if defined(THRESHOLD_DEVSTATE_PLACEHOLDER)
+ /* Update the span threshold device state and report any change. */
+ if (in_alarm) {
+ new_state = AST_DEVICE_UNAVAILABLE;
+ } else if (!in_use) {
+ new_state = AST_DEVICE_NOT_INUSE;
+ } else if (!pri->user_busy_threshold) {
+ new_state = in_use < num_b_chans ? AST_DEVICE_INUSE : AST_DEVICE_BUSY;
+ } else {
+ new_state = in_use < pri->user_busy_threshold ? AST_DEVICE_INUSE
+ : AST_DEVICE_BUSY;
+ }
+ if (pri->threshold_devstate != new_state) {
+ pri->threshold_devstate = new_state;
+ ast_devstate_changed(AST_DEVICE_UNKNOWN, "DAHDI/I%d/threshold", pri->span);
+ }
+#endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */
+}
+
+/*!
+ * \internal
+ * \brief Reference this module.
+ * \since 1.8
+ *
+ * \return Nothing
+ */
+static void my_module_ref(void)
+{
+ ast_module_ref(ast_module_info->self);
+}
+
+/*!
+ * \internal
+ * \brief Unreference this module.
+ * \since 1.8
+ *
+ * \return Nothing
+ */
+static void my_module_unref(void)
+{
+ ast_module_unref(ast_module_info->self);
+}
+
static int dahdi_new_pri_nobch_channel(struct sig_pri_pri *pri);
static struct sig_pri_callback dahdi_pri_callbacks =
@@ -2803,6 +2989,11 @@ static struct sig_pri_callback dahdi_pri_callbacks =
.set_dnid = my_pri_set_dnid,
.set_rdnis = my_pri_set_rdnis,
.new_nobch_intf = dahdi_new_pri_nobch_channel,
+ .get_orig_dialstring = my_get_orig_dialstring,
+ .make_cc_dialstring = my_pri_make_cc_dialstring,
+ .update_span_devstate = dahdi_pri_update_span_devstate,
+ .module_ref = my_module_ref,
+ .module_unref = my_module_unref,
};
#endif /* defined(HAVE_PRI) */
@@ -2932,6 +3123,7 @@ static struct analog_callback dahdi_analog_callbacks =
.cancel_cidspill = my_cancel_cidspill,
.confmute = my_confmute,
.set_pulsedial = my_set_pulsedial,
+ .get_orig_dialstring = my_get_orig_dialstring,
};
static struct dahdi_pvt *round_robin[32];
@@ -5122,6 +5314,9 @@ static void destroy_dahdi_pvt(struct dahdi_pvt *pvt)
if (p->vars) {
ast_variables_destroy(p->vars);
}
+ if (p->cc_params) {
+ ast_cc_config_params_destroy(p->cc_params);
+ }
ast_mutex_destroy(&p->lock);
dahdi_close_sub(p, SUB_REAL);
if (p->owner)
@@ -5957,6 +6152,18 @@ static int dahdi_queryoption(struct ast_channel *chan, int option, void *data, i
*cp = (p->callprogress & CALLPROGRESS_FAX) ? 0 : 1;
ast_debug(1, "Reporting fax tone detection %sabled on %s\n", *cp ? "en" : "dis", chan->name);
break;
+ case AST_OPTION_CC_AGENT_TYPE:
+#if defined(HAVE_PRI)
+#if defined(HAVE_PRI_CCSS)
+ if (dahdi_sig_pri_lib_handles(p->sig)) {
+ ast_copy_string((char *) data, dahdi_pri_cc_type, *datalen);
+ break;
+ }
+#endif /* defined(HAVE_PRI_CCSS) */
+#endif /* defined(HAVE_PRI) */
+ return -1;
+ default:
+ return -1;
}
errno = 0;
@@ -8582,37 +8789,28 @@ static int dahdi_indicate(struct ast_channel *chan, int condition, const void *d
return res;
}
-static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpbx, int idx, int law, int transfercapability, const char *linkedid)
+#if defined(HAVE_PRI)
+static struct ast_str *create_channel_name(struct dahdi_pvt *i, int is_outgoing, char *address)
+#else
+static struct ast_str *create_channel_name(struct dahdi_pvt *i)
+#endif /* defined(HAVE_PRI) */
{
- struct ast_channel *tmp;
- format_t deflaw;
- int res;
- int x,y;
- int features;
struct ast_str *chan_name;
- struct ast_variable *v;
- struct dahdi_params ps;
+ int x, y;
- if (i->subs[idx].owner) {
- ast_log(LOG_WARNING, "Channel %d already has a %s call\n", i->channel,subnames[idx]);
+ /* Create the new channel name tail. */
+ if (!(chan_name = ast_str_create(32))) {
return NULL;
}
-
- /* Create the new channel name tail. */
- chan_name = ast_str_alloca(32);
if (i->channel == CHAN_PSEUDO) {
ast_str_set(&chan_name, 0, "pseudo-%ld", ast_random());
#if defined(HAVE_PRI)
} else if (i->pri) {
ast_mutex_lock(&i->pri->lock);
y = ++i->pri->new_chan_seq;
- if (i->outgoing) {
- /*
- * The dnid has been stuffed with the called-number[:subaddress]
- * by dahdi_request().
- */
- ast_str_set(&chan_name, 0, "i%d/%s-%x", i->pri->span, i->dnid, y);
- i->dnid[0] = '\0';
+ if (is_outgoing) {
+ ast_str_set(&chan_name, 0, "i%d/%s-%x", i->pri->span, address, y);
+ address[0] = '\0';
} else if (ast_strlen_zero(i->cid_subaddr)) {
/* Put in caller-id number only since there is no subaddress. */
ast_str_set(&chan_name, 0, "i%d/%s-%x", i->pri->span, i->cid_num, y);
@@ -8636,11 +8834,49 @@ static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpb
++y;
} while (x < 3);
}
+ return chan_name;
+}
+
+static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpbx, int idx, int law, int transfercapability, const char *linkedid)
+{
+ struct ast_channel *tmp;
+ format_t deflaw;
+ int res;
+ int x;
+ int features;
+ struct ast_str *chan_name;
+ struct ast_variable *v;
+ struct dahdi_params ps;
+
+ if (i->subs[idx].owner) {
+ ast_log(LOG_WARNING, "Channel %d already has a %s call\n", i->channel,subnames[idx]);
+ return NULL;
+ }
+
+#if defined(HAVE_PRI)
+ /*
+ * The dnid has been stuffed with the called-number[:subaddress]
+ * by dahdi_request() for outgoing calls.
+ */
+ chan_name = create_channel_name(i, i->outgoing, i->dnid);
+#else
+ chan_name = create_channel_name(i);
+#endif /* defined(HAVE_PRI) */
+ if (!chan_name) {
+ return NULL;
+ }
tmp = ast_channel_alloc(0, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, linkedid, i->amaflags, "DAHDI/%s", ast_str_buffer(chan_name));
+ ast_free(chan_name);
if (!tmp)
return NULL;
tmp->tech = &dahdi_tech;
+#if defined(HAVE_PRI)
+ if (i->pri) {
+ ast_cc_copy_config_params(i->cc_params, i->pri->cc_params);
+ }
+#endif /* defined(HAVE_PRI) */
+ ast_channel_cc_params_init(tmp, i->cc_params);
memset(&ps, 0, sizeof(ps));
res = ioctl(i->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &ps);
if (res) {
@@ -11169,6 +11405,11 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
if (!tmp) {
return NULL;
}
+ tmp->cc_params = ast_cc_config_params_init();
+ if (!tmp->cc_params) {
+ ast_free(tmp);
+ return NULL;
+ }
ast_mutex_init(&tmp->lock);
ifcount++;
for (x = 0; x < 3; x++)
@@ -11412,6 +11653,16 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
tmp->sig_pvt = pchan;
tmp->pri = &pris[span].pri;
+ if (!tmp->pri->cc_params) {
+ tmp->pri->cc_params = ast_cc_config_params_init();
+ if (!tmp->pri->cc_params) {
+ destroy_dahdi_pvt(tmp);
+ return NULL;
+ }
+ }
+ ast_cc_copy_config_params(tmp->pri->cc_params,
+ conf->chan.cc_params);
+
pris[span].pri.sig = chan_sig;
pris[span].pri.nodetype = conf->pri.pri.nodetype;
pris[span].pri.switchtype = myswitchtype;
@@ -11434,6 +11685,14 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
pris[span].pri.hold_disconnect_transfer =
conf->pri.pri.hold_disconnect_transfer;
#endif /* defined(HAVE_PRI_CALL_HOLD) */
+#if defined(HAVE_PRI_CCSS)
+ pris[span].pri.cc_ptmp_recall_mode =
+ conf->pri.pri.cc_ptmp_recall_mode;
+ pris[span].pri.cc_qsig_signaling_link_req =
+ conf->pri.pri.cc_qsig_signaling_link_req;
+ pris[span].pri.cc_qsig_signaling_link_rsp =
+ conf->pri.pri.cc_qsig_signaling_link_rsp;
+#endif /* defined(HAVE_PRI_CCSS) */
pris[span].pri.facilityenable = conf->pri.pri.facilityenable;
ast_copy_string(pris[span].pri.msn_list, conf->pri.pri.msn_list, sizeof(pris[span].pri.msn_list));
ast_copy_string(pris[span].pri.idledial, conf->pri.pri.idledial, sizeof(pris[span].pri.idledial));
@@ -11742,6 +12001,7 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
tmp->answeronpolarityswitch = conf->chan.answeronpolarityswitch;
tmp->hanguponpolarityswitch = conf->chan.hanguponpolarityswitch;
tmp->sendcalleridafter = conf->chan.sendcalleridafter;
+ ast_cc_copy_config_params(tmp->cc_params, conf->chan.cc_params);
if (!here) {
tmp->locallyblocked = tmp->remotelyblocked = 0;
@@ -11881,21 +12141,36 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
return tmp;
}
-static inline int available(struct dahdi_pvt *p, int channelmatch, ast_group_t groupmatch, int *channelmatched, int *groupmatched)
+static int is_group_or_channel_match(struct dahdi_pvt *p, int span, ast_group_t groupmatch, int *groupmatched, int channelmatch, int *channelmatched)
{
- /* First, check group matching */
+#if defined(HAVE_PRI)
+ if (0 < span) {
+ /* The channel must be on the specified PRI span. */
+ if (!p->pri || p->pri->span != span) {
+ return 0;
+ }
+ }
+#endif /* defined(HAVE_PRI) */
+ /* check group matching */
if (groupmatch) {
if ((p->group & groupmatch) != groupmatch)
+ /* Doesn't match the specified group, try the next one */
return 0;
*groupmatched = 1;
}
/* Check to see if we have a channel match */
if (channelmatch != -1) {
if (p->channel != channelmatch)
+ /* Doesn't match the specified channel, try the next one */
return 0;
*channelmatched = 1;
}
+ return 1;
+}
+
+static int available(struct dahdi_pvt *p)
+{
if (p->inalarm)
return 0;
@@ -11988,6 +12263,11 @@ static int dahdi_new_pri_nobch_channel(struct sig_pri_pri *pri)
if (!pvt) {
return -1;
}
+ pvt->cc_params = ast_cc_config_params_init();
+ if (!pvt->cc_params) {
+ ast_free(pvt);
+ return -1;
+ }
ast_mutex_init(&pvt->lock);
for (idx = 0; idx < ARRAY_LEN(pvt->subs); ++idx) {
pvt->subs[idx].dfd = -1;
@@ -12089,24 +12369,31 @@ static struct dahdi_pvt *duplicate_pseudo(struct dahdi_pvt *src)
return p;
}
-static struct ast_channel *dahdi_request(const char *type, format_t format, const struct ast_channel *requestor, void *data, int *cause)
+struct dahdi_starting_point {
+ /*! Group matching mask. Zero if not specified. */
+ ast_group_t groupmatch;
+ /*! DAHDI channel to match with. -1 if not specified. */
+ int channelmatch;
+ /*! Round robin saved search location index. (Valid if roundrobin TRUE) */
+ int rr_starting_point;
+ /*! ISDN span where channels can be picked (Zero if not specified) */
+ int span;
+ /*! Analog channel distinctive ring cadance index. */
+ int cadance;
+ /*! Dialing option. c/r/d if present and valid. */
+ char opt;
+ /*! TRUE if to search the channel list backwards. */
+ char backwards;
+ /*! TRUE if search is done with round robin sequence. */
+ char roundrobin;
+};
+static struct dahdi_pvt *determine_starting_point(const char *data, struct dahdi_starting_point *param)
{
- ast_group_t groupmatch = 0;
- int channelmatch = -1;
- int roundrobin = 0;
- int callwait = 0;
- struct dahdi_pvt *p;
- struct ast_channel *tmp = NULL;
char *dest;
- int x;
char *s;
- char opt=0;
- int res=0, y=0;
- int backwards = 0;
- struct dahdi_pvt *exitpvt;
- int channelmatched = 0;
- int groupmatched = 0;
- int transcapdigital = 0;
+ int x;
+ int res = 0;
+ struct dahdi_pvt *p;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(group); /* channel/group token */
//AST_APP_ARG(ext); /* extension token */
@@ -12117,8 +12404,11 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons
/*
* data is ---v
* Dial(DAHDI/pseudo[/extension[/options]])
- * Dial(DAHDI/<channel#>[c|r<cadance#>|d][/extension[/options]])
- * Dial(DAHDI/(g|G|r|R)<group#(0-63)>[c|r<cadance#>|d][/extension[/options]])
+ * Dial(DAHDI/[i<span>-]<channel#>[c|r<cadance#>|d][/extension[/options]])
+ * Dial(DAHDI/[i<span>-](g|G|r|R)<group#(0-63)>[c|r<cadance#>|d][/extension[/options]])
+ *
+ * i - ISDN span channel restriction.
+ * Used by CC to ensure that the CC recall goes out the same span.
*
* g - channel group allocation search forward
* G - channel group allocation search backward
@@ -12131,7 +12421,7 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons
*/
if (data) {
- dest = ast_strdupa((char *)data);
+ dest = ast_strdupa(data);
} else {
ast_log(LOG_WARNING, "Channel requested with no data\n");
return NULL;
@@ -12142,27 +12432,47 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons
return NULL;
}
+ /* Initialize the output parameters */
+ memset(param, 0, sizeof(*param));
+ param->channelmatch = -1;
+
+ if (args.group[0] == 'i') {
+ /* Extract the ISDN span channel restriction specifier. */
+ res = sscanf(args.group + 1, "%30d", &x);
+ if (res < 1) {
+ ast_log(LOG_WARNING, "Unable to determine ISDN span for data %s\n", data);
+ return NULL;
+ }
+ param->span = x;
+
+ /* Remove the ISDN span channel restriction specifier. */
+ s = strchr(args.group, '-');
+ if (!s) {
+ ast_log(LOG_WARNING, "Bad ISDN span format for data %s\n", data);
+ return NULL;
+ }
+ args.group = s + 1;
+ res = 0;
+ }
if (toupper(args.group[0]) == 'G' || toupper(args.group[0])=='R') {
/* Retrieve the group number */
s = args.group + 1;
- if ((res = sscanf(s, "%30d%1c%30d", &x, &opt, &y)) < 1) {
- ast_log(LOG_WARNING, "Unable to determine group for data %s\n", (char *)data);
+ res = sscanf(s, "%30d%1c%30d", &x, &param->opt, &param->cadance);
+ if (res < 1) {
+ ast_log(LOG_WARNING, "Unable to determine group for data %s\n", data);
return NULL;
}
- groupmatch = ((ast_group_t) 1 << x);
-
- /* Lock the interface list */
- ast_mutex_lock(&iflock);
+ param->groupmatch = ((ast_group_t) 1 << x);
if (toupper(args.group[0]) == 'G') {
if (args.group[0] == 'G') {
- backwards = 1;
+ param->backwards = 1;
p = ifend;
} else
p = iflist;
} else {
if (args.group[0] == 'R') {
- backwards = 1;
+ param->backwards = 1;
p = round_robin[x]?round_robin[x]->prev:ifend;
if (!p)
p = ifend;
@@ -12171,36 +12481,62 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons
if (!p)
p = iflist;
}
- roundrobin = 1;
+ param->roundrobin = 1;
+ param->rr_starting_point = x;
}
} else {
s = args.group;
if (!strcasecmp(s, "pseudo")) {
/* Special case for pseudo */
x = CHAN_PSEUDO;
- channelmatch = x;
- } else if ((res = sscanf(s, "%30d%1c%30d", &x, &opt, &y)) < 1) {
- ast_log(LOG_WARNING, "Unable to determine channel for data %s\n", (char *)data);
- return NULL;
+ param->channelmatch = x;
} else {
- channelmatch = x;
+ res = sscanf(s, "%30d%1c%30d", &x, &param->opt, &param->cadance);
+ if (res < 1) {
+ ast_log(LOG_WARNING, "Unable to determine channel for data %s\n", data);
+ return NULL;
+ } else {
+ param->channelmatch = x;
+ }
}
- /* Lock the interface list */
- ast_mutex_lock(&iflock);
-
p = iflist;
}
+
+ if (param->opt == 'r' && res < 3) {
+ ast_log(LOG_WARNING, "Distinctive ring missing identifier in '%s'\n", data);
+ param->opt = '\0';
+ }
+
+ return p;
+}
+
+static struct ast_channel *dahdi_request(const char *type, format_t format, const struct ast_channel *requestor, void *data, int *cause)
+{
+ int callwait = 0;
+ struct dahdi_pvt *p;
+ struct ast_channel *tmp = NULL;
+ struct dahdi_pvt *exitpvt;
+ int channelmatched = 0;
+ int groupmatched = 0;
+ int transcapdigital = 0;
+ struct dahdi_starting_point start;
+
+ p = determine_starting_point(data, &start);
+ if (!p) {
+ /* We couldn't determine a starting point, which likely means badly-formatted channel name. Abort! */
+ return NULL;
+ }
+
/* Search for an unowned channel */
exitpvt = p;
+ ast_mutex_lock(&iflock);
while (p && !tmp) {
- if (roundrobin)
- round_robin[x] = p;
-#if 0
- ast_verbose("name = %s, %d, %d, %llu\n",p->owner ? p->owner->name : "<none>", p->channel, channelmatch, groupmatch);
-#endif
+ if (start.roundrobin)
+ round_robin[start.rr_starting_point] = p;
- if (p && available(p, channelmatch, groupmatch, &channelmatched, &groupmatched)) {
+ if (is_group_or_channel_match(p, start.span, start.groupmatch, &groupmatched, start.channelmatch, &channelmatched)
+ && available(p)) {
ast_debug(1, "Using channel %d\n", p->channel);
callwait = (p->owner != NULL);
@@ -12224,22 +12560,25 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons
}
/* Make special notes */
- if (res > 1) {
- if (opt == 'c') {
- /* Confirm answer */
- p->confirmanswer = 1;
- } else if (opt == 'r') {
- /* Distinctive ring */
- if (res < 3)
- ast_log(LOG_WARNING, "Distinctive ring missing identifier in '%s'\n", (char *)data);
- else
- p->distinctivering = y;
- } else if (opt == 'd') {
- /* If this is an ISDN call, make it digital */
- transcapdigital = AST_TRANS_CAP_DIGITAL;
- } else {
- ast_log(LOG_WARNING, "Unknown option '%c' in '%s'\n", opt, (char *)data);
- }
+ switch (start.opt) {
+ case '\0':
+ /* No option present. */
+ break;
+ case 'c':
+ /* Confirm answer */
+ p->confirmanswer = 1;
+ break;
+ case 'r':
+ /* Distinctive ring */
+ p->distinctivering = start.cadance;
+ break;
+ case 'd':
+ /* If this is an ISDN call, make it digital */
+ transcapdigital = AST_TRANS_CAP_DIGITAL;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Unknown option '%c' in '%s'\n", start.opt, (char *)data);
+ break;
}
p->outgoing = 1;
@@ -12256,13 +12595,15 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons
}
if (!tmp) {
p->outgoing = 0;
+ } else {
+ snprintf(p->dialstring, sizeof(p->dialstring), "DAHDI/%s", (char *) data);
}
break;
}
#ifdef HAVE_OPENR2
next:
#endif
- if (backwards) {
+ if (start.backwards) {
p = p->prev;
if (!p)
p = ifend;
@@ -12293,6 +12634,167 @@ next:
return tmp;
}
+/*!
+ * \internal
+ * \brief Determine the device state for a given DAHDI device if we can.
+ * \since 1.8
+ *
+ * \param data DAHDI device name after "DAHDI/".
+ *
+ * \retval device_state enum ast_device_state value.
+ * \retval AST_DEVICE_UNKNOWN if we could not determine the device's state.
+ */
+static int dahdi_devicestate(void *data)
+{
+#if defined(HAVE_PRI)
+ char *device;
+ unsigned span;
+ int res;
+
+ device = data;
+
+ if (*device != 'I') {
+ /* The request is not for an ISDN span device. */
+ return AST_DEVICE_UNKNOWN;
+ }
+ res = sscanf(device, "I%30u", &span);
+ if (res != 1 || !span || NUM_SPANS < span) {
+ /* Bad format for ISDN span device name. */
+ return AST_DEVICE_UNKNOWN;
+ }
+ device = strchr(device, '/');
+ if (!device) {
+ /* Bad format for ISDN span device name. */
+ return AST_DEVICE_UNKNOWN;
+ }
+
+ /*
+ * Since there are currently no other span devstate's defined,
+ * it must be congestion.
+ */
+#if defined(THRESHOLD_DEVSTATE_PLACEHOLDER)
+ ++device;
+ if (!strcmp(device, "congestion"))
+#endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */
+ {
+ return pris[span - 1].pri.congestion_devstate;
+ }
+#if defined(THRESHOLD_DEVSTATE_PLACEHOLDER)
+ else if (!strcmp(device, "threshold")) {
+ return pris[span - 1].pri.threshold_devstate;
+ }
+ return AST_DEVICE_UNKNOWN;
+#endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */
+#else
+ return AST_DEVICE_UNKNOWN;
+#endif /* defined(HAVE_PRI) */
+}
+
+/*!
+ * \brief Callback made when dial failed to get a channel out of dahdi_request().
+ * \since 1.8
+ *
+ * \param inbound Incoming asterisk channel.
+ * \param dest Same dial string passed to dahdi_request().
+ * \param callback Callback into CC core to announce a busy channel available for CC.
+ *
+ * \details
+ * This callback acts like a forked dial with all prongs of the fork busy.
+ * Essentially, for each channel that could have taken the call, indicate that
+ * it is busy.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int dahdi_cc_callback(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback)
+{
+ struct dahdi_pvt *p;
+ struct dahdi_pvt *exitpvt;
+ struct dahdi_starting_point start;
+ int groupmatched = 0;
+ int channelmatched = 0;
+
+ p = determine_starting_point(dest, &start);
+ if (!p) {
+ return -1;
+ }
+ ast_mutex_lock(&iflock);
+ exitpvt = p;
+ for (;;) {
+ if (is_group_or_channel_match(p, start.span, start.groupmatch, &groupmatched, start.channelmatch, &channelmatched)) {
+ /* We found a potential match. call the callback */
+ struct ast_str *device_name;
+ char *dash;
+ const char *monitor_type;
+ char dialstring[AST_CHANNEL_NAME];
+ char full_device_name[AST_CHANNEL_NAME];
+
+ switch (ast_get_cc_monitor_policy(p->cc_params)) {
+ case AST_CC_MONITOR_NEVER:
+ break;
+ case AST_CC_MONITOR_NATIVE:
+ case AST_CC_MONITOR_ALWAYS:
+ case AST_CC_MONITOR_GENERIC:
+#if defined(HAVE_PRI)
+ if (dahdi_sig_pri_lib_handles(p->sig)) {
+ /*
+ * ISDN is in a trunk busy condition so we need to monitor
+ * the span congestion device state.
+ */
+ snprintf(full_device_name, sizeof(full_device_name),
+ "DAHDI/I%d/congestion", p->pri->span);
+ } else
+#endif /* defined(HAVE_PRI) */
+ {
+#if defined(HAVE_PRI)
+ device_name = create_channel_name(p, 1, "");
+#else
+ device_name = create_channel_name(p);
+#endif /* defined(HAVE_PRI) */
+ snprintf(full_device_name, sizeof(full_device_name), "DAHDI/%s",
+ device_name ? ast_str_buffer(device_name) : "");
+ ast_free(device_name);
+ /*
+ * The portion after the '-' in the channel name is either a random
+ * number, a sequence number, or a subchannel number. None are
+ * necessary so strip them off.
+ */
+ dash = strrchr(full_device_name, '-');
+ if (dash) {
+ *dash = '\0';
+ }
+ }
+ snprintf(dialstring, sizeof(dialstring), "DAHDI/%s", dest);
+
+ /*
+ * Analog can only do generic monitoring.
+ * ISDN is in a trunk busy condition and any "device" is going
+ * to be busy until a B channel becomes available. The generic
+ * monitor can do this task.
+ */
+ monitor_type = AST_CC_GENERIC_MONITOR_TYPE;
+ callback(inbound,
+#if defined(HAVE_PRI)
+ p->pri ? p->pri->cc_params : p->cc_params,
+#else
+ p->cc_params,
+#endif /* defined(HAVE_PRI) */
+ monitor_type, full_device_name, dialstring, NULL);
+ break;
+ }
+ }
+ p = start.backwards ? p->prev : p->next;
+ if (!p) {
+ p = start.backwards ? ifend : iflist;
+ }
+ if (p == exitpvt) {
+ break;
+ }
+ }
+ ast_mutex_unlock(&iflock);
+ return 0;
+}
+
#if defined(HAVE_SS7)
static int ss7_find_cic(struct dahdi_ss7 *linkset, int cic, unsigned int dpc)
{
@@ -13480,9 +13982,7 @@ static char *handle_pri_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
for (x = 0; x < NUM_DCHANS; x++) {
if (pris[span-1].pri.dchans[x]) {
if (level == 1) {
- pri_set_debug(pris[span-1].pri.dchans[x], PRI_DEBUG_APDU |
- PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE |
- PRI_DEBUG_Q921_STATE);
+ pri_set_debug(pris[span-1].pri.dchans[x], SIG_PRI_DEBUG_NORMAL);
ast_cli(a->fd, "Enabled debugging on span %d\n", span);
} else if (level == 0) {
pri_set_debug(pris[span-1].pri.dchans[x], 0);
@@ -13493,9 +13993,7 @@ static char *handle_pri_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
ast_cli(a->fd, "PRI debug output to file disabled\n");
ast_mutex_unlock(&pridebugfdlock);
} else {
- pri_set_debug(pris[span-1].pri.dchans[x], PRI_DEBUG_APDU |
- PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE |
- PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_DUMP | PRI_DEBUG_Q921_STATE);
+ pri_set_debug(pris[span-1].pri.dchans[x], SIG_PRI_DEBUG_INTENSE);
ast_cli(a->fd, "Enabled debugging on span %d\n", span);
}
}
@@ -13583,6 +14081,8 @@ static char *handle_pri_service_generic(struct ast_cli_entry *e, int cmd, struct
if (*why) {
snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why);
ast_db_put(db_chan_name, SRVST_DBKEY, db_answer);
+ } else {
+ dahdi_pri_update_span_devstate(tmp->pri);
}
break;
/* case 1: -- loop */
@@ -13592,6 +14092,7 @@ static char *handle_pri_service_generic(struct ast_cli_entry *e, int cmd, struct
*why |= SRVST_NEAREND;
snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why);
ast_db_put(db_chan_name, SRVST_DBKEY, db_answer);
+ dahdi_pri_update_span_devstate(tmp->pri);
break;
/* case 3: -- continuity */
/* case 4: -- shutdown */
@@ -15612,6 +16113,110 @@ static struct ast_cli_entry dahdi_ss7_cli[] = {
};
#endif /* defined(HAVE_SS7) */
+#if defined(HAVE_PRI)
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief CC agent initialization.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ * \param chan Original channel the agent will attempt to recall.
+ *
+ * \details
+ * This callback is called when the CC core is initialized. Agents should allocate
+ * any private data necessary for the call and assign it to the private_data
+ * on the agent. Additionally, if any ast_cc_agent_flags are pertinent to the
+ * specific agent type, they should be set in this function as well.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int dahdi_pri_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan)
+{
+ struct dahdi_pvt *pvt;
+ struct sig_pri_chan *pvt_chan;
+ int res;
+
+ ast_assert(!strcmp(chan->tech->type, "DAHDI"));
+
+ pvt = chan->tech_pvt;
+ if (dahdi_sig_pri_lib_handles(pvt->sig)) {
+ pvt_chan = pvt->sig_pvt;
+ } else {
+ pvt_chan = NULL;
+ }
+ if (!pvt_chan) {
+ return -1;
+ }
+
+ ast_module_ref(ast_module_info->self);
+
+ res = sig_pri_cc_agent_init(agent, pvt_chan);
+ if (res) {
+ ast_module_unref(ast_module_info->self);
+ }
+ return res;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+#endif /* defined(HAVE_PRI) */
+
+#if defined(HAVE_PRI)
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Destroy private data on the agent.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * The core will call this function upon completion
+ * or failure of CC.
+ *
+ * \return Nothing
+ */
+static void dahdi_pri_cc_agent_destructor(struct ast_cc_agent *agent)
+{
+ sig_pri_cc_agent_destructor(agent);
+
+ ast_module_unref(ast_module_info->self);
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+#endif /* defined(HAVE_PRI) */
+
+#if defined(HAVE_PRI)
+#if defined(HAVE_PRI_CCSS)
+static struct ast_cc_agent_callbacks dahdi_pri_cc_agent_callbacks = {
+ .type = dahdi_pri_cc_type,
+ .init = dahdi_pri_cc_agent_init,
+ .start_offer_timer = sig_pri_cc_agent_start_offer_timer,
+ .stop_offer_timer = sig_pri_cc_agent_stop_offer_timer,
+ .ack = sig_pri_cc_agent_req_ack,
+ .status_request = sig_pri_cc_agent_status_req,
+ .stop_ringing = sig_pri_cc_agent_stop_ringing,
+ .party_b_free = sig_pri_cc_agent_party_b_free,
+ .start_monitoring = sig_pri_cc_agent_start_monitoring,
+ .callee_available = sig_pri_cc_agent_callee_available,
+ .destructor = dahdi_pri_cc_agent_destructor,
+};
+#endif /* defined(HAVE_PRI_CCSS) */
+#endif /* defined(HAVE_PRI) */
+
+#if defined(HAVE_PRI)
+#if defined(HAVE_PRI_CCSS)
+static struct ast_cc_monitor_callbacks dahdi_pri_cc_monitor_callbacks = {
+ .type = dahdi_pri_cc_type,
+ .request_cc = sig_pri_cc_monitor_req_cc,
+ .suspend = sig_pri_cc_monitor_suspend,
+ .unsuspend = sig_pri_cc_monitor_unsuspend,
+ .status_response = sig_pri_cc_monitor_status_rsp,
+ .cancel_available_timer = sig_pri_cc_monitor_cancel_available_timer,
+ .destructor = sig_pri_cc_monitor_destructor,
+};
+#endif /* defined(HAVE_PRI_CCSS) */
+#endif /* defined(HAVE_PRI) */
+
static int __unload_module(void)
{
struct dahdi_pvt *p;
@@ -15680,6 +16285,11 @@ static int __unload_module(void)
dahdi_close_pri_fd(&(pris[i]), j);
}
}
+#if defined(HAVE_PRI_CCSS)
+ ast_cc_agent_unregister(&dahdi_pri_cc_agent_callbacks);
+ ast_cc_monitor_unregister(&dahdi_pri_cc_monitor_callbacks);
+#endif /* defined(HAVE_PRI_CCSS) */
+ sig_pri_unload();
#endif
#if defined(HAVE_SS7)
@@ -16100,6 +16710,8 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
confp->chan.sendcalleridafter = atoi(v->value);
} else if (!strcasecmp(v->name, "mwimonitornotify")) {
ast_copy_string(mwimonitornotify, v->value, sizeof(mwimonitornotify));
+ } else if (ast_cc_is_config_param(v->name)) {
+ ast_cc_set_param(confp->chan.cc_params, v->name, v->value);
} else if (!strcasecmp(v->name, "mwisendtype")) {
#ifndef HAVE_DAHDI_LINEREVERSE_VMWI /* backward compatibility for older dahdi VMWI implementation */
if (!strcasecmp(v->value, "rpas")) { /* Ring Pulse Alert Signal */
@@ -16478,6 +17090,34 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
} else if (!strcasecmp(v->name, "hold_disconnect_transfer")) {
confp->pri.pri.hold_disconnect_transfer = ast_true(v->value);
#endif /* defined(HAVE_PRI_CALL_HOLD) */
+#if defined(HAVE_PRI_CCSS)
+ } else if (!strcasecmp(v->name, "cc_ptmp_recall_mode")) {
+ if (!strcasecmp(v->value, "global")) {
+ confp->pri.pri.cc_ptmp_recall_mode = 0;/* globalRecall */
+ } else if (!strcasecmp(v->value, "specific")) {
+ confp->pri.pri.cc_ptmp_recall_mode = 1;/* specificRecall */
+ } else {
+ confp->pri.pri.cc_ptmp_recall_mode = 1;/* specificRecall */
+ }
+ } else if (!strcasecmp(v->name, "cc_qsig_signaling_link_req")) {
+ if (!strcasecmp(v->value, "release")) {
+ confp->pri.pri.cc_qsig_signaling_link_req = 0;/* release */
+ } else if (!strcasecmp(v->value, "retain")) {
+ confp->pri.pri.cc_qsig_signaling_link_req = 1;/* retain */
+ } else if (!strcasecmp(v->value, "do_not_care")) {
+ confp->pri.pri.cc_qsig_signaling_link_req = 2;/* do-not-care */
+ } else {
+ confp->pri.pri.cc_qsig_signaling_link_req = 1;/* retain */
+ }
+ } else if (!strcasecmp(v->name, "cc_qsig_signaling_link_rsp")) {
+ if (!strcasecmp(v->value, "release")) {
+ confp->pri.pri.cc_qsig_signaling_link_rsp = 0;/* release */
+ } else if (!strcasecmp(v->value, "retain")) {
+ confp->pri.pri.cc_qsig_signaling_link_rsp = 1;/* retain */
+ } else {
+ confp->pri.pri.cc_qsig_signaling_link_rsp = 1;/* retain */
+ }
+#endif /* defined(HAVE_PRI_CCSS) */
#endif /* HAVE_PRI */
#ifdef HAVE_SS7
} else if (!strcasecmp(v->name, "ss7type")) {
@@ -16800,23 +17440,57 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
*/
struct dahdi_chan_conf conf = dahdi_chan_conf_default();
- tmp = mkintf(CHAN_PSEUDO, &conf, reload);
-
+ if (conf.chan.cc_params) {
+ tmp = mkintf(CHAN_PSEUDO, &conf, reload);
+ } else {
+ tmp = NULL;
+ }
if (tmp) {
ast_verb(3, "Automatically generated pseudo channel\n");
} else {
ast_log(LOG_WARNING, "Unable to register pseudo channel!\n");
}
+ ast_cc_config_params_destroy(conf.chan.cc_params);
}
return 0;
}
-static int setup_dahdi(int reload)
+/*!
+ * \internal
+ * \brief Deep copy struct dahdi_chan_conf.
+ * \since 1.8
+ *
+ * \param dest Destination.
+ * \param src Source.
+ *
+ * \return Nothing
+ */
+static void deep_copy_dahdi_chan_conf(struct dahdi_chan_conf *dest, const struct dahdi_chan_conf *src)
+{
+ struct ast_cc_config_params *cc_params;
+
+ cc_params = dest->chan.cc_params;
+ memcpy(dest, src, sizeof(dest));
+ dest->chan.cc_params = cc_params;
+ ast_cc_copy_config_params(dest->chan.cc_params, src->chan.cc_params);
+}
+
+/*!
+ * \internal
+ * \brief Setup DAHDI channel driver.
+ *
+ * \param reload enum: load_module(0), reload(1), restart(2).
+ * \param base_conf Default config parameters. So cc_params can be properly destroyed.
+ * \param conf Local config parameters. So cc_params can be properly destroyed.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_dahdi_int(int reload, struct dahdi_chan_conf *base_conf, struct dahdi_chan_conf *conf)
{
- struct ast_config *cfg, *ucfg;
+ struct ast_config *cfg;
+ struct ast_config *ucfg;
struct ast_variable *v;
- struct dahdi_chan_conf base_conf = dahdi_chan_conf_default();
- struct dahdi_chan_conf conf;
struct ast_flags config_flags = { reload == 1 ? CONFIG_FLAG_FILEUNCHANGED : 0 };
const char *cat;
int res;
@@ -16931,7 +17605,7 @@ static int setup_dahdi(int reload)
mwimonitornotify[0] = '\0';
v = ast_variable_browse(cfg, "channels");
- if ((res = process_dahdi(&base_conf, "", v, reload, 0))) {
+ if ((res = process_dahdi(base_conf, "", v, reload, 0))) {
ast_mutex_unlock(&iflock);
ast_config_destroy(cfg);
if (ucfg) {
@@ -16952,9 +17626,10 @@ static int setup_dahdi(int reload)
continue;
}
- memcpy(&conf, &base_conf, sizeof(conf));
+ /* Copy base_conf to conf. */
+ deep_copy_dahdi_chan_conf(conf, base_conf);
- if ((res = process_dahdi(&conf, cat, ast_variable_browse(cfg, cat), reload, PROC_DAHDI_OPT_NOCHAN))) {
+ if ((res = process_dahdi(conf, cat, ast_variable_browse(cfg, cat), reload, PROC_DAHDI_OPT_NOCHAN))) {
ast_mutex_unlock(&iflock);
ast_config_destroy(cfg);
if (ucfg) {
@@ -16969,7 +17644,7 @@ static int setup_dahdi(int reload)
if (ucfg) {
const char *chans;
- process_dahdi(&base_conf, "", ast_variable_browse(ucfg, "general"), 1, 0);
+ process_dahdi(base_conf, "", ast_variable_browse(ucfg, "general"), 1, 0);
for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
if (!strcasecmp(cat, "general")) {
@@ -16982,9 +17657,10 @@ static int setup_dahdi(int reload)
continue;
}
- memcpy(&conf, &base_conf, sizeof(conf));
+ /* Copy base_conf to conf. */
+ deep_copy_dahdi_chan_conf(conf, base_conf);
- if ((res = process_dahdi(&conf, cat, ast_variable_browse(ucfg, cat), reload, PROC_DAHDI_OPT_NOCHAN | PROC_DAHDI_OPT_NOWARN))) {
+ if ((res = process_dahdi(conf, cat, ast_variable_browse(ucfg, cat), reload, PROC_DAHDI_OPT_NOCHAN | PROC_DAHDI_OPT_NOWARN))) {
ast_config_destroy(ucfg);
ast_mutex_unlock(&iflock);
return res;
@@ -17041,6 +17717,32 @@ static int setup_dahdi(int reload)
return 0;
}
+/*!
+ * \internal
+ * \brief Setup DAHDI channel driver.
+ *
+ * \param reload enum: load_module(0), reload(1), restart(2).
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_dahdi(int reload)
+{
+ int res;
+ struct dahdi_chan_conf base_conf = dahdi_chan_conf_default();
+ struct dahdi_chan_conf conf = dahdi_chan_conf_default();
+
+ if (base_conf.chan.cc_params && conf.chan.cc_params) {
+ res = setup_dahdi_int(reload, &base_conf, &conf);
+ } else {
+ res = -1;
+ }
+ ast_cc_config_params_destroy(base_conf.chan.cc_params);
+ ast_cc_config_params_destroy(conf.chan.cc_params);
+
+ return res;
+}
+
static int load_module(void)
{
int res;
@@ -17061,6 +17763,23 @@ static int load_module(void)
#ifdef HAVE_PRI_PROG_W_CAUSE
ast_register_application_xml(dahdi_send_callrerouting_facility_app, dahdi_send_callrerouting_facility_exec);
#endif
+#if defined(HAVE_PRI_CCSS)
+ if (ast_cc_agent_register(&dahdi_pri_cc_agent_callbacks)
+ || ast_cc_monitor_register(&dahdi_pri_cc_monitor_callbacks)) {
+ __unload_module();
+ return AST_MODULE_LOAD_FAILURE;
+ }
+#endif /* defined(HAVE_PRI_CCSS) */
+ if (sig_pri_load(
+#if defined(HAVE_PRI_CCSS)
+ dahdi_pri_cc_type
+#else
+ NULL
+#endif /* defined(HAVE_PRI_CCSS) */
+ )) {
+ __unload_module();
+ return AST_MODULE_LOAD_FAILURE;
+ }
#endif
#ifdef HAVE_SS7
memset(linksets, 0, sizeof(linksets));
diff --git a/channels/chan_local.c b/channels/chan_local.c
index 5e522e797..b8052b0fd 100644
--- a/channels/chan_local.c
+++ b/channels/chan_local.c
@@ -545,6 +545,8 @@ static int local_call(struct ast_channel *ast, char *dest, int timeout)
int res;
struct ast_var_t *varptr = NULL, *new;
size_t len, namelen;
+ char *reduced_dest = ast_strdupa(dest);
+ char *slash;
if (!p)
return -1;
@@ -594,6 +596,8 @@ start_over:
ast_string_field_set(p->chan, musicclass, p->owner->musicclass);
ast_cdr_update(p->chan);
+ ast_channel_cc_params_init(p->chan, ast_channel_get_cc_config_params(p->owner));
+
if (!ast_exists_extension(NULL, p->chan->context, p->chan->exten, 1, p->owner->cid.cid_num)) {
ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n", p->chan->exten, p->chan->context);
ast_mutex_unlock(&p->lock);
@@ -618,6 +622,14 @@ start_over:
}
}
ast_channel_datastore_inherit(p->owner, p->chan);
+ /* If the local channel has /n or /b on the end of it,
+ * we need to lop that off for our argument to setting
+ * up the CC_INTERFACES variable
+ */
+ if ((slash = strrchr(reduced_dest, '/'))) {
+ *slash = '\0';
+ }
+ ast_set_cc_interfaces_chanvar(p->chan, reduced_dest);
/* Start switch on sub channel */
if (!(res = ast_pbx_start(p->chan)))
@@ -857,6 +869,10 @@ static struct ast_channel *local_request(const char *type, format_t format, cons
AST_LIST_UNLOCK(&locals);
p = local_pvt_destroy(p);
}
+ if (ast_channel_cc_params_init(chan, ast_channel_get_cc_config_params((struct ast_channel *)requestor))) {
+ chan = ast_channel_release(chan);
+ p = local_pvt_destroy(p);
+ }
}
return chan;
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index bd6cb1889..91773b05c 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -266,6 +266,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "sip/include/config_parser.h"
#include "sip/include/reqresp_parser.h"
#include "sip/include/sip_utils.h"
+#include "asterisk/ccss.h"
+#include "asterisk/xml.h"
#include "sip/include/dialog.h"
#include "sip/include/dialplan_functions.h"
@@ -625,7 +627,7 @@ static const struct cfsip_methods {
{ SIP_UPDATE, NO_RTP, "UPDATE", CAN_NOT_CREATE_DIALOG },
{ SIP_INFO, NO_RTP, "INFO", CAN_NOT_CREATE_DIALOG },
{ SIP_CANCEL, NO_RTP, "CANCEL", CAN_NOT_CREATE_DIALOG },
- { SIP_PUBLISH, NO_RTP, "PUBLISH", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD },
+ { SIP_PUBLISH, NO_RTP, "PUBLISH", CAN_CREATE_DIALOG },
{ SIP_PING, NO_RTP, "PING", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD }
};
@@ -784,6 +786,14 @@ static int global_max_se; /*!< Highest threshold for session
static int global_dynamic_exclude_static = 0; /*!< Exclude static peers from contact registrations */
/*@}*/
+/*!
+ * We use libxml2 in order to parse XML that may appear in the body of a SIP message. Currently,
+ * the only usage is for parsing PIDF bodies of incoming PUBLISH requests in the call-completion
+ * event package. This variable is set at module load time and may be checked at runtime to determine
+ * if XML parsing support was found.
+ */
+static int can_parse_xml;
+
/*! \name Object counters @{
* \bug These counters are not handled in a thread-safe way ast_atomic_fetchadd_int()
* should be used to modify these values. */
@@ -851,6 +861,251 @@ static const int HASH_PEER_SIZE = 563; /*!< Size of peer hash table, prime numbe
static const int HASH_DIALOG_SIZE = 563;
#endif
+static const struct {
+ enum ast_cc_service_type service;
+ const char *service_string;
+} sip_cc_service_map [] = {
+ [AST_CC_NONE] = { AST_CC_NONE, "" },
+ [AST_CC_CCBS] = { AST_CC_CCBS, "BS" },
+ [AST_CC_CCNR] = { AST_CC_CCNR, "NR" },
+ [AST_CC_CCNL] = { AST_CC_CCNL, "NL" },
+};
+
+static enum ast_cc_service_type service_string_to_service_type(const char * const service_string)
+{
+ enum ast_cc_service_type service;
+ for (service = AST_CC_CCBS; service <= AST_CC_CCNL; ++service) {
+ if (!strcasecmp(service_string, sip_cc_service_map[service].service_string)) {
+ return service;
+ }
+ }
+ return AST_CC_NONE;
+}
+
+static const struct {
+ enum sip_cc_notify_state state;
+ const char *state_string;
+} sip_cc_notify_state_map [] = {
+ [CC_QUEUED] = {CC_QUEUED, "cc-state: queued"},
+ [CC_READY] = {CC_READY, "cc-state: ready"},
+};
+
+AST_LIST_HEAD_STATIC(epa_static_data_list, epa_backend);
+
+static int sip_epa_register(const struct epa_static_data *static_data)
+{
+ struct epa_backend *backend = ast_calloc(1, sizeof(*backend));
+
+ if (!backend) {
+ return -1;
+ }
+
+ backend->static_data = static_data;
+
+ AST_LIST_LOCK(&epa_static_data_list);
+ AST_LIST_INSERT_TAIL(&epa_static_data_list, backend, next);
+ AST_LIST_UNLOCK(&epa_static_data_list);
+ return 0;
+}
+
+static void cc_handle_publish_error(struct sip_pvt *pvt, const int resp, struct sip_request *req, struct sip_epa_entry *epa_entry);
+
+static void cc_epa_destructor(void *data)
+{
+ struct sip_epa_entry *epa_entry = data;
+ struct cc_epa_entry *cc_entry = epa_entry->instance_data;
+ ast_free(cc_entry);
+}
+
+static const struct epa_static_data cc_epa_static_data = {
+ .event = CALL_COMPLETION,
+ .name = "call-completion",
+ .handle_error = cc_handle_publish_error,
+ .destructor = cc_epa_destructor,
+};
+
+static const struct epa_static_data *find_static_data(const char * const event_package)
+{
+ const struct epa_backend *backend = NULL;
+
+ AST_LIST_LOCK(&epa_static_data_list);
+ AST_LIST_TRAVERSE(&epa_static_data_list, backend, next) {
+ if (!strcmp(backend->static_data->name, event_package)) {
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&epa_static_data_list);
+ return backend ? backend->static_data : NULL;
+}
+
+static struct sip_epa_entry *create_epa_entry (const char * const event_package, const char * const destination)
+{
+ struct sip_epa_entry *epa_entry;
+ const struct epa_static_data *static_data;
+
+ if (!(static_data = find_static_data(event_package))) {
+ return NULL;
+ }
+
+ if (!(epa_entry = ao2_t_alloc(sizeof(*epa_entry), static_data->destructor, "Allocate new EPA entry"))) {
+ return NULL;
+ }
+
+ epa_entry->static_data = static_data;
+ ast_copy_string(epa_entry->destination, destination, sizeof(epa_entry->destination));
+ return epa_entry;
+}
+
+/*!
+ * Used to create new entity IDs by ESCs.
+ */
+static int esc_etag_counter;
+static const int DEFAULT_PUBLISH_EXPIRES = 3600;
+
+#ifdef HAVE_LIBXML2
+static int cc_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry);
+
+static const struct sip_esc_publish_callbacks cc_esc_publish_callbacks = {
+ .initial_handler = cc_esc_publish_handler,
+ .modify_handler = cc_esc_publish_handler,
+};
+#endif
+
+/*!
+ * \brief The Event State Compositors
+ *
+ * An Event State Compositor is an entity which
+ * accepts PUBLISH requests and acts appropriately
+ * based on these requests.
+ *
+ * The actual event_state_compositor structure is simply
+ * an ao2_container of sip_esc_entrys. When an incoming
+ * PUBLISH is received, we can match the appropriate sip_esc_entry
+ * using the entity ID of the incoming PUBLISH.
+ */
+static struct event_state_compositor {
+ enum subscriptiontype event;
+ const char * name;
+ const struct sip_esc_publish_callbacks *callbacks;
+ struct ao2_container *compositor;
+} event_state_compositors [] = {
+#ifdef HAVE_LIBXML2
+ {CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks},
+#endif
+};
+
+static const int ESC_MAX_BUCKETS = 37;
+
+static void esc_entry_destructor(void *obj)
+{
+ struct sip_esc_entry *esc_entry = obj;
+ if (esc_entry->sched_id > -1) {
+ AST_SCHED_DEL(sched, esc_entry->sched_id);
+ }
+}
+
+static int esc_hash_fn(const void *obj, const int flags)
+{
+ const struct sip_esc_entry *entry = obj;
+ return ast_str_hash(entry->entity_tag);
+}
+
+static int esc_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct sip_esc_entry *entry1 = obj;
+ struct sip_esc_entry *entry2 = arg;
+
+ return (!strcmp(entry1->entity_tag, entry2->entity_tag)) ? (CMP_MATCH | CMP_STOP) : 0;
+}
+
+static struct event_state_compositor *get_esc(const char * const event_package) {
+ int i;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ if (!strcasecmp(event_package, event_state_compositors[i].name)) {
+ return &event_state_compositors[i];
+ }
+ }
+ return NULL;
+}
+
+static struct sip_esc_entry *get_esc_entry(const char * entity_tag, struct event_state_compositor *esc) {
+ struct sip_esc_entry *entry;
+ struct sip_esc_entry finder;
+
+ ast_copy_string(finder.entity_tag, entity_tag, sizeof(finder.entity_tag));
+
+ entry = ao2_find(esc->compositor, &finder, OBJ_POINTER);
+
+ return entry;
+}
+
+static int publish_expire(const void *data)
+{
+ struct sip_esc_entry *esc_entry = (struct sip_esc_entry *) data;
+ struct event_state_compositor *esc = get_esc(esc_entry->event);
+
+ ast_assert(esc != NULL);
+
+ ao2_unlink(esc->compositor, esc_entry);
+ ao2_ref(esc_entry, -1);
+ return 0;
+}
+
+static void create_new_sip_etag(struct sip_esc_entry *esc_entry, int is_linked)
+{
+ int new_etag = ast_atomic_fetchadd_int(&esc_etag_counter, +1);
+ struct event_state_compositor *esc = get_esc(esc_entry->event);
+
+ ast_assert(esc != NULL);
+ if (is_linked) {
+ ao2_unlink(esc->compositor, esc_entry);
+ }
+ snprintf(esc_entry->entity_tag, sizeof(esc_entry->entity_tag), "%d", new_etag);
+ ao2_link(esc->compositor, esc_entry);
+}
+
+static struct sip_esc_entry *create_esc_entry(struct event_state_compositor *esc, struct sip_request *req, const int expires)
+{
+ struct sip_esc_entry *esc_entry;
+ int expires_ms;
+
+ if (!(esc_entry = ao2_alloc(sizeof(*esc_entry), esc_entry_destructor))) {
+ return NULL;
+ }
+
+ esc_entry->event = esc->name;
+
+ expires_ms = expires * 1000;
+ /* Bump refcount for scheduler */
+ ao2_ref(esc_entry, +1);
+ esc_entry->sched_id = ast_sched_add(sched, expires_ms, publish_expire, esc_entry);
+
+ /* Note: This links the esc_entry into the ESC properly */
+ create_new_sip_etag(esc_entry, 0);
+
+ return esc_entry;
+}
+
+static int initialize_escs(void)
+{
+ int i, res = 0;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ if (!((event_state_compositors[i].compositor) =
+ ao2_container_alloc(ESC_MAX_BUCKETS, esc_hash_fn, esc_cmp_fn))) {
+ res = -1;
+ }
+ }
+ return res;
+}
+
+static void destroy_escs(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ ao2_ref(event_state_compositors[i].compositor, -1);
+ }
+}
+
/*! \brief
* Here we implement the container for dialogs (sip_pvt), defining
* generic wrapper functions to ease the transition from the current
@@ -1001,6 +1256,7 @@ static int sip_prepare_socket(struct sip_pvt *p);
static int sipsock_read(int *id, int fd, short events, void *ignore);
static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len);
static int __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod);
+static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp);
static int __transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
static int retrans_pkt(const void *data);
static int transmit_response_using_temp(ast_string_field callid, struct sockaddr_in *sin, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg);
@@ -1015,7 +1271,8 @@ static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, cons
static void transmit_fake_auth_response(struct sip_pvt *p, int sipmethod, struct sip_request *req, enum xmittype reliable);
static int transmit_request(struct sip_pvt *p, int sipmethod, int inc, enum xmittype reliable, int newbranch);
static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch);
-static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init);
+static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri);
+static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri);
static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp);
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
static int transmit_info_with_vidupdate(struct sip_pvt *p);
@@ -1023,6 +1280,7 @@ static int transmit_message_with_text(struct sip_pvt *p, const char *text);
static int transmit_refer(struct sip_pvt *p, const char *dest);
static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, const char *vmexten);
static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate);
+static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscription, enum sip_cc_notify_state state);
static int transmit_register(struct sip_registry *r, int sipmethod, const char *auth, const char *authheader);
static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
@@ -1236,7 +1494,7 @@ static int set_address_from_contact(struct sip_pvt *pvt);
static void check_via(struct sip_pvt *p, struct sip_request *req);
static int get_rpid(struct sip_pvt *p, struct sip_request *oreq);
static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason);
-static int get_destination(struct sip_pvt *p, struct sip_request *oreq);
+static int get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id);
static int get_msg_text(char *buf, int len, struct sip_request *req, int addnewline);
static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout);
static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen);
@@ -1253,7 +1511,7 @@ static void *sip_tcp_worker_fn(void *);
static void initialize_initreq(struct sip_pvt *p, struct sip_request *req);
static int init_req(struct sip_request *req, int sipmethod, const char *recip);
static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, int seqno, int newbranch);
-static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod);
+static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, const char * const explicit_uri);
static int init_resp(struct sip_request *resp, const char *msg);
static inline int resp_needs_contact(const char *msg, enum sipmethod method);
static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg, const struct sip_request *req);
@@ -1297,6 +1555,7 @@ static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, str
static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, int seqno, int *nounlock);
/*------Response handling functions */
+static void handle_response_publish(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_refer(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
@@ -1372,6 +1631,556 @@ const struct ast_channel_tech sip_tech = {
*/
struct ast_channel_tech sip_tech_info;
+static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan);
+static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent);
+static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent);
+static void sip_cc_agent_ack(struct ast_cc_agent *agent);
+static int sip_cc_agent_status_request(struct ast_cc_agent *agent);
+static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent);
+static int sip_cc_agent_recall(struct ast_cc_agent *agent);
+static void sip_cc_agent_destructor(struct ast_cc_agent *agent);
+
+static struct ast_cc_agent_callbacks sip_cc_agent_callbacks = {
+ .type = "SIP",
+ .init = sip_cc_agent_init,
+ .start_offer_timer = sip_cc_agent_start_offer_timer,
+ .stop_offer_timer = sip_cc_agent_stop_offer_timer,
+ .ack = sip_cc_agent_ack,
+ .status_request = sip_cc_agent_status_request,
+ .start_monitoring = sip_cc_agent_start_monitoring,
+ .callee_available = sip_cc_agent_recall,
+ .destructor = sip_cc_agent_destructor,
+};
+
+static int find_by_notify_uri_helper(void *obj, void *arg, int flags)
+{
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ const char *uri = arg;
+
+ return !strcmp(agent_pvt->notify_uri, uri) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static struct ast_cc_agent *find_sip_cc_agent_by_notify_uri(const char * const uri)
+{
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_notify_uri_helper, (char *)uri, "SIP");
+ return agent;
+}
+
+static int find_by_subscribe_uri_helper(void *obj, void *arg, int flags)
+{
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ const char *uri = arg;
+
+ return !strcmp(agent_pvt->subscribe_uri, uri) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static struct ast_cc_agent *find_sip_cc_agent_by_subscribe_uri(const char * const uri)
+{
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_subscribe_uri_helper, (char *)uri, "SIP");
+ return agent;
+}
+
+static int find_by_callid_helper(void *obj, void *arg, int flags)
+{
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ struct sip_pvt *call_pvt = arg;
+
+ return !strcmp(agent_pvt->original_callid, call_pvt->callid) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static struct ast_cc_agent *find_sip_cc_agent_by_original_callid(struct sip_pvt *pvt)
+{
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_callid_helper, pvt, "SIP");
+ return agent;
+}
+
+static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan)
+{
+ struct sip_cc_agent_pvt *agent_pvt = ast_calloc(1, sizeof(*agent_pvt));
+ struct sip_pvt *call_pvt = chan->tech_pvt;
+
+ if (!agent_pvt) {
+ return -1;
+ }
+
+ ast_assert(!strcmp(chan->tech->type, "SIP"));
+
+ ast_copy_string(agent_pvt->original_callid, call_pvt->callid, sizeof(agent_pvt->original_callid));
+ ast_copy_string(agent_pvt->original_exten, call_pvt->exten, sizeof(agent_pvt->original_exten));
+ agent_pvt->offer_timer_id = -1;
+ agent->private_data = agent_pvt;
+ sip_pvt_lock(call_pvt);
+ ast_set_flag(&call_pvt->flags[0], SIP_OFFER_CC);
+ sip_pvt_unlock(call_pvt);
+ return 0;
+}
+
+static int sip_offer_timer_expire(const void *data)
+{
+ struct ast_cc_agent *agent = (struct ast_cc_agent *) data;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+
+ agent_pvt->offer_timer_id = -1;
+
+ return ast_cc_failed(agent->core_id, "SIP agent %s's offer timer expired", agent->device_name);
+}
+
+static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ int when;
+
+ when = ast_get_cc_offer_timer(agent->cc_params) * 1000;
+ agent_pvt->offer_timer_id = ast_sched_add(sched, when, sip_offer_timer_expire, agent);
+ return 0;
+}
+
+static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+
+ AST_SCHED_DEL(sched, agent_pvt->offer_timer_id);
+ return 0;
+}
+
+static void sip_cc_agent_ack(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ ast_set_flag(&agent_pvt->subscribe_pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response(agent_pvt->subscribe_pvt, "200 OK", &agent_pvt->subscribe_pvt->initreq);
+ transmit_cc_notify(agent, agent_pvt->subscribe_pvt, CC_QUEUED);
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ agent_pvt->is_available = TRUE;
+}
+
+static int sip_cc_agent_status_request(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ enum ast_device_state state = agent_pvt->is_available ? AST_DEVICE_NOT_INUSE : AST_DEVICE_INUSE;
+ return ast_cc_agent_status_response(agent->core_id, state);
+}
+
+static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent)
+{
+ /* To start monitoring just means to wait for an incoming PUBLISH
+ * to tell us that the caller has become available again. No special
+ * action is needed
+ */
+ return 0;
+}
+
+static int sip_cc_agent_recall(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ /* If we have received a PUBLISH beforehand stating that the caller in question
+ * is not available, we can save ourself a bit of effort here and just report
+ * the caller as busy
+ */
+ if (!agent_pvt->is_available) {
+ return ast_cc_agent_caller_busy(agent->core_id, "Caller %s is busy, reporting to the core",
+ agent->device_name);
+ }
+ /* Otherwise, we transmit a NOTIFY to the caller and await either
+ * a PUBLISH or an INVITE
+ */
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ transmit_cc_notify(agent, agent_pvt->subscribe_pvt, CC_READY);
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ return 0;
+}
+
+static void sip_cc_agent_destructor(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+
+ if (!agent_pvt) {
+ /* The agent constructor probably failed. */
+ return;
+ }
+
+ sip_cc_agent_stop_offer_timer(agent);
+ if (agent_pvt->subscribe_pvt) {
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ if (!ast_test_flag(&agent_pvt->subscribe_pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) {
+ /* If we haven't sent a 200 OK for the SUBSCRIBE dialog yet, then we need to send a response letting
+ * the subscriber know something went wrong
+ */
+ transmit_response(agent_pvt->subscribe_pvt, "500 Internal Server Error", &agent_pvt->subscribe_pvt->initreq);
+ }
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ agent_pvt->subscribe_pvt = dialog_unref(agent_pvt->subscribe_pvt, "SIP CC agent destructor: Remove ref to subscription");
+ }
+ ast_free(agent_pvt);
+}
+
+struct ao2_container *sip_monitor_instances;
+
+static int sip_monitor_instance_hash_fn(const void *obj, const int flags)
+{
+ const struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->core_id;
+}
+
+static int sip_monitor_instance_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct sip_monitor_instance *monitor_instance1 = obj;
+ struct sip_monitor_instance *monitor_instance2 = arg;
+
+ return monitor_instance1->core_id == monitor_instance2->core_id ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static void sip_monitor_instance_destructor(void *data)
+{
+ struct sip_monitor_instance *monitor_instance = data;
+ if (monitor_instance->subscription_pvt) {
+ sip_pvt_lock(monitor_instance->subscription_pvt);
+ monitor_instance->subscription_pvt->expiry = 0;
+ transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 0, monitor_instance->subscribe_uri);
+ sip_pvt_unlock(monitor_instance->subscription_pvt);
+ dialog_unref(monitor_instance->subscription_pvt, "Unref monitor instance ref of subscription pvt");
+ }
+ if (monitor_instance->suspension_entry) {
+ monitor_instance->suspension_entry->body[0] = '\0';
+ transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_REMOVE ,monitor_instance->notify_uri);
+ ao2_t_ref(monitor_instance->suspension_entry, -1, "Decrementing suspension entry refcount in sip_monitor_instance_destructor");
+ }
+ ast_string_field_free_memory(monitor_instance);
+}
+
+static struct sip_monitor_instance *sip_monitor_instance_init(int core_id, const char * const subscribe_uri, const char * const peername, const char * const device_name)
+{
+ struct sip_monitor_instance *monitor_instance = ao2_alloc(sizeof(*monitor_instance), sip_monitor_instance_destructor);
+
+ if (!monitor_instance) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(monitor_instance, 256)) {
+ ao2_ref(monitor_instance, -1);
+ return NULL;
+ }
+
+ ast_string_field_set(monitor_instance, subscribe_uri, subscribe_uri);
+ ast_string_field_set(monitor_instance, peername, peername);
+ ast_string_field_set(monitor_instance, device_name, device_name);
+ monitor_instance->core_id = core_id;
+ ao2_link(sip_monitor_instances, monitor_instance);
+ return monitor_instance;
+}
+
+static int find_sip_monitor_instance_by_subscription_pvt(void *obj, void *arg, int flags)
+{
+ struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->subscription_pvt == arg ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static int find_sip_monitor_instance_by_suspension_entry(void *obj, void *arg, int flags)
+{
+ struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->suspension_entry == arg ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id);
+static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor);
+static int sip_cc_monitor_status_response(struct ast_cc_monitor *monitor, enum ast_device_state devstate);
+static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor);
+static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id);
+static void sip_cc_monitor_destructor(void *private_data);
+
+static struct ast_cc_monitor_callbacks sip_cc_monitor_callbacks = {
+ .type = "SIP",
+ .request_cc = sip_cc_monitor_request_cc,
+ .suspend = sip_cc_monitor_suspend,
+ .status_response = sip_cc_monitor_status_response,
+ .unsuspend = sip_cc_monitor_unsuspend,
+ .cancel_available_timer = sip_cc_monitor_cancel_available_timer,
+ .destructor = sip_cc_monitor_destructor,
+};
+
+static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id)
+{
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ enum ast_cc_service_type service = monitor->service_offered;
+ int when;
+
+ if (!monitor_instance) {
+ return -1;
+ }
+
+ if (!(monitor_instance->subscription_pvt = sip_alloc(NULL, NULL, 0, SIP_SUBSCRIBE, NULL))) {
+ return -1;
+ }
+
+ when = service == AST_CC_CCBS ? ast_get_ccbs_available_timer(monitor->interface->config_params) :
+ ast_get_ccnr_available_timer(monitor->interface->config_params);
+
+ sip_pvt_lock(monitor_instance->subscription_pvt);
+ create_addr(monitor_instance->subscription_pvt, monitor_instance->peername, 0, 1);
+ ast_sip_ouraddrfor(&monitor_instance->subscription_pvt->sa.sin_addr, &monitor_instance->subscription_pvt->ourip, monitor_instance->subscription_pvt);
+ monitor_instance->subscription_pvt->subscribed = CALL_COMPLETION;
+ monitor_instance->subscription_pvt->expiry = when;
+
+ transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 2, monitor_instance->subscribe_uri);
+ sip_pvt_unlock(monitor_instance->subscription_pvt);
+
+ ao2_t_ref(monitor, +1, "Adding a ref to the monitor for the scheduler");
+ *available_timer_id = ast_sched_add(sched, when * 1000, ast_cc_available_timer_expire, monitor);
+ return 0;
+}
+
+static int construct_pidf_body(enum sip_cc_publish_state state, char *pidf_body, size_t size, const char *presentity)
+{
+ struct ast_str *body = ast_str_alloca(size);
+ char tuple_id[32];
+
+ generate_random_string(tuple_id, sizeof(tuple_id));
+
+ /* We'll make this a bare-bones pidf body. In state_notify_build_xml, the PIDF
+ * body gets a lot more extra junk that isn't necessary, so we'll leave it out here.
+ */
+ ast_str_append(&body, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ /* XXX The entity attribute is currently set to the peer name associated with the
+ * dialog. This is because we currently only call this function for call-completion
+ * PUBLISH bodies. In such cases, the entity is completely disregarded. For other
+ * event packages, it may be crucial to have a proper URI as the presentity so this
+ * should be revisited as support is expanded.
+ */
+ ast_str_append(&body, 0, "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"%s\">\n", presentity);
+ ast_str_append(&body, 0, "<tuple id=\"%s\">\n", tuple_id);
+ ast_str_append(&body, 0, "<status><basic>%s</basic></status>\n", state == CC_OPEN ? "open" : "closed");
+ ast_str_append(&body, 0, "</tuple>\n");
+ ast_str_append(&body, 0, "</presence>\n");
+ ast_copy_string(pidf_body, ast_str_buffer(body), size);
+ return 0;
+}
+
+static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor)
+{
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ enum sip_publish_type publish_type;
+ struct cc_epa_entry *cc_entry;
+
+ if (!monitor_instance) {
+ return -1;
+ }
+
+ if (!monitor_instance->suspension_entry) {
+ /* We haven't yet allocated the suspension entry, so let's give it a shot */
+ if (!(monitor_instance->suspension_entry = create_epa_entry("call-completion", monitor_instance->peername))) {
+ ast_log(LOG_WARNING, "Unable to allocate sip EPA entry for call-completion\n");
+ ao2_ref(monitor_instance, -1);
+ return -1;
+ }
+ if (!(cc_entry = ast_calloc(1, sizeof(*cc_entry)))) {
+ ast_log(LOG_WARNING, "Unable to allocate space for instance data of EPA entry for call-completion\n");
+ ao2_ref(monitor_instance, -1);
+ return -1;
+ }
+ cc_entry->core_id = monitor->core_id;
+ monitor_instance->suspension_entry->instance_data = cc_entry;
+ publish_type = SIP_PUBLISH_INITIAL;
+ } else {
+ publish_type = SIP_PUBLISH_MODIFY;
+ cc_entry = monitor_instance->suspension_entry->instance_data;
+ }
+
+ cc_entry->current_state = CC_CLOSED;
+
+ if (ast_strlen_zero(monitor_instance->notify_uri)) {
+ /* If we have no set notify_uri, then what this means is that we have
+ * not received a NOTIFY from this destination stating that he is
+ * currently available.
+ *
+ * This situation can arise when the core calls the suspend callbacks
+ * of multiple destinations. If one of the other destinations aside
+ * from this one notified Asterisk that he is available, then there
+ * is no reason to take any suspension action on this device. Rather,
+ * we should return now and if we receive a NOTIFY while monitoring
+ * is still "suspended" then we can immediately respond with the
+ * proper PUBLISH to let this endpoint know what is going on.
+ */
+ return 0;
+ }
+ construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
+ return transmit_publish(monitor_instance->suspension_entry, publish_type, monitor_instance->notify_uri);
+}
+
+static int sip_cc_monitor_status_response(struct ast_cc_monitor *monitor, enum ast_device_state devstate)
+{
+ /* This will never be called because the SIP monitor will never make a status request to
+ * begin with
+ */
+ ast_log(LOG_WARNING, "sip_cc_monitor_status_response called. Something dreadfully wrong must have happened.\n");
+ return 0;
+}
+
+static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor)
+{
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ struct cc_epa_entry *cc_entry;
+
+ if (!monitor_instance) {
+ return -1;
+ }
+
+ ast_assert(monitor_instance->suspension_entry != NULL);
+
+ cc_entry = monitor_instance->suspension_entry->instance_data;
+ cc_entry->current_state = CC_OPEN;
+ if (ast_strlen_zero(monitor_instance->notify_uri)) {
+ /* This means we are being asked to unsuspend a call leg we never
+ * sent a PUBLISH on. As such, there is no reason to send another
+ * PUBLISH at this point either. We can just return instead.
+ */
+ return 0;
+ }
+ construct_pidf_body(CC_OPEN, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
+ return transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_MODIFY, monitor_instance->notify_uri);
+}
+
+static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id)
+{
+ if (*sched_id != -1) {
+ AST_SCHED_DEL(sched, *sched_id);
+ ao2_t_ref(monitor, -1, "Removing scheduler's reference to the monitor");
+ }
+ return 0;
+}
+
+static void sip_cc_monitor_destructor(void *private_data)
+{
+ struct sip_monitor_instance *monitor_instance = private_data;
+ ao2_unlink(sip_monitor_instances, monitor_instance);
+ ast_module_unref(ast_module_info->self);
+}
+
+static int sip_get_cc_information(struct sip_request *req, char *subscribe_uri, size_t size, enum ast_cc_service_type *service)
+{
+ char *call_info = ast_strdupa(get_header(req, "Call-Info"));
+ char *uri;
+ char *purpose;
+ char *service_str;
+ static const char cc_purpose[] = "purpose=call-completion";
+ static const int cc_purpose_len = sizeof(cc_purpose) - 1;
+
+ if (ast_strlen_zero(call_info)) {
+ /* No Call-Info present. Definitely no CC offer */
+ return -1;
+ }
+
+ uri = strsep(&call_info, ";");
+
+ while ((purpose = strsep(&call_info, ";"))) {
+ if (!strncmp(purpose, cc_purpose, cc_purpose_len)) {
+ break;
+ }
+ }
+ if (!purpose) {
+ /* We didn't find the appropriate purpose= parameter. Oh well */
+ return -1;
+ }
+
+ /* Okay, call-completion has been offered. Let's figure out what type of service this is */
+ while ((service_str = strsep(&call_info, ";"))) {
+ if (!strncmp(service_str, "m=", 2)) {
+ break;
+ }
+ }
+ if (!service_str) {
+ /* So they didn't offer a particular service, We'll just go with CCBS since it really
+ * doesn't matter anyway
+ */
+ service_str = "BS";
+ } else {
+ /* We already determined that there is an "m=" so no need to check
+ * the result of this strsep
+ */
+ strsep(&service_str, "=");
+ }
+
+ if ((*service = service_string_to_service_type(service_str)) == AST_CC_NONE) {
+ /* Invalid service offered */
+ return -1;
+ }
+
+ ast_copy_string(subscribe_uri, get_in_brackets(uri), size);
+
+ return 0;
+}
+
+/*
+ * \brief Determine what, if any, CC has been offered and queue a CC frame if possible
+ *
+ * After taking care of some formalities to be sure that this call is eligible for CC,
+ * we first try to see if we can make use of native CC. We grab the information from
+ * the passed-in sip_request (which is always a response to an INVITE). If we can
+ * use native CC monitoring for the call, then so be it.
+ *
+ * If native cc monitoring is not possible or not supported, then we will instead attempt
+ * to use generic monitoring. Falling back to generic from a failed attempt at using native
+ * monitoring will only work if the monitor policy of the endpoint is "always"
+ *
+ * \param pvt The current dialog. Contains CC parameters for the endpoint
+ * \param req The response to the INVITE we want to inspect
+ * \param service The service to use if generic monitoring is to be used. For native
+ * monitoring, we get the service from the SIP response itself
+ */
+static void sip_handle_cc(struct sip_pvt *pvt, struct sip_request *req, enum ast_cc_service_type service)
+{
+ enum ast_cc_monitor_policies monitor_policy = ast_get_cc_monitor_policy(pvt->cc_params);
+ int core_id;
+ char interface_name[AST_CHANNEL_NAME];
+
+ if (monitor_policy == AST_CC_MONITOR_NEVER) {
+ /* Don't bother, just return */
+ return;
+ }
+
+ if ((core_id = ast_cc_get_current_core_id(pvt->owner)) == -1) {
+ /* For some reason, CC is invalid, so don't try it! */
+ return;
+ }
+
+ ast_channel_get_device_name(pvt->owner, interface_name, sizeof(interface_name));
+
+ if (monitor_policy == AST_CC_MONITOR_ALWAYS || monitor_policy == AST_CC_MONITOR_NATIVE) {
+ char subscribe_uri[SIPBUFSIZE];
+ char device_name[AST_CHANNEL_NAME];
+ enum ast_cc_service_type offered_service;
+ struct sip_monitor_instance *monitor_instance;
+ if (sip_get_cc_information(req, subscribe_uri, sizeof(subscribe_uri), &offered_service)) {
+ /* If CC isn't being offered to us, or for some reason the CC offer is
+ * not formatted correctly, then it may still be possible to use generic
+ * call completion since the monitor policy may be "always"
+ */
+ goto generic;
+ }
+ ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name));
+ if (!(monitor_instance = sip_monitor_instance_init(core_id, subscribe_uri, pvt->peername, device_name))) {
+ /* Same deal. We can try using generic still */
+ goto generic;
+ }
+ /* We bump the refcount of chan_sip because once we queue this frame, the CC core
+ * will have a reference to callbacks in this module. We decrement the module
+ * refcount once the monitor destructor is called
+ */
+ ast_module_ref(ast_module_info->self);
+ ast_queue_cc_frame(pvt->owner, "SIP", pvt->dialstring, offered_service, monitor_instance);
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
+
+generic:
+ if (monitor_policy == AST_CC_MONITOR_GENERIC || monitor_policy == AST_CC_MONITOR_ALWAYS) {
+ ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE, interface_name, service, NULL);
+ }
+}
+
/*! \brief Working TLS connection configuration */
static struct ast_tls_config sip_tls_cfg;
@@ -2700,7 +3509,7 @@ static int __sip_autodestruct(const void *data)
struct sip_pvt *p = (struct sip_pvt *)data;
/* If this is a subscription, tell the phone that we got a timeout */
- if (p->subscribed) {
+ if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) {
transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
p->subscribed = NONE;
append_history(p, "Subscribestatus", "timeout");
@@ -3159,6 +3968,16 @@ static int sip_queryoption(struct ast_channel *chan, int option, void *data, int
*cp = p->dsp ? 1 : 0;
ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", chan->name);
break;
+ case AST_OPTION_DEVICE_NAME:
+ if (p && p->outgoing_call) {
+ cp = (char *) data;
+ ast_copy_string(cp, p->dialstring, *datalen);
+ res = 0;
+ }
+ /* We purposely break with a return of -1 in the
+ * implied else case here
+ */
+ break;
default:
break;
}
@@ -3401,6 +4220,8 @@ static void sip_destroy_peer(struct sip_peer *peer)
peer->socket.tcptls_session = NULL;
}
+ ast_cc_config_params_destroy(peer->cc_params);
+
ast_string_field_free_memory(peer);
}
@@ -3943,6 +4764,7 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
dialog->peerauth = peer->auth;
dialog->maxcallbitrate = peer->maxcallbitrate;
dialog->disallowed_methods = peer->disallowed_methods;
+ ast_cc_copy_config_params(dialog->cc_params, peer->cc_params);
if (ast_strlen_zero(dialog->tohost))
ast_string_field_set(dialog, tohost, ast_inet_ntoa(dialog->sa.sin_addr));
if (!ast_strlen_zero(peer->fromdomain)) {
@@ -4131,12 +4953,26 @@ static int sip_call(struct ast_channel *ast, char *dest, int timeout)
struct varshead *headp;
struct ast_var_t *current;
const char *referer = NULL; /* SIP referrer */
+ int cc_core_id;
+ char uri[SIPBUFSIZE] = "";
if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
ast_log(LOG_WARNING, "sip_call called on %s, neither down nor reserved\n", ast->name);
return -1;
}
+ if (ast_cc_is_recall(ast, &cc_core_id, "SIP")) {
+ char device_name[AST_CHANNEL_NAME];
+ struct ast_cc_monitor *recall_monitor;
+ struct sip_monitor_instance *monitor_instance;
+ ast_channel_get_device_name(ast, device_name, sizeof(device_name));
+ if ((recall_monitor = ast_cc_get_monitor_by_recall_core_id(cc_core_id, device_name))) {
+ monitor_instance = recall_monitor->private_data;
+ ast_copy_string(uri, monitor_instance->notify_uri, sizeof(uri));
+ ao2_t_ref(recall_monitor, -1, "Got the URI we need so unreffing monitor");
+ }
+ }
+
/* Check whether there is vxml_url, distinctive ring variables */
headp=&ast->varshead;
AST_LIST_TRAVERSE(headp, current, entries) {
@@ -4201,7 +5037,7 @@ static int sip_call(struct ast_channel *ast, char *dest, int timeout)
int xmitres;
sip_pvt_lock(p);
- xmitres = transmit_invite(p, SIP_INVITE, 1, 2);
+ xmitres = transmit_invite(p, SIP_INVITE, 1, 2, uri);
sip_pvt_unlock(p);
if (xmitres == XMIT_ERROR)
return -1;
@@ -4371,6 +5207,13 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
ast_string_field_free_memory(p);
+ ast_cc_config_params_destroy(p->cc_params);
+
+ if (p->epa_entry) {
+ ao2_ref(p->epa_entry, -1);
+ p->epa_entry = NULL;
+ }
+
if (p->socket.tcptls_session) {
ao2_ref(p->socket.tcptls_session, -1);
p->socket.tcptls_session = NULL;
@@ -5446,7 +6289,10 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *tit
sip_pvt_lock(i);
return NULL;
}
+ ast_channel_lock(tmp);
sip_pvt_lock(i);
+ ast_channel_cc_params_init(tmp, i->cc_params);
+ ast_channel_unlock(tmp);
tmp->tech = ( ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INFO || ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_SHORTINFO) ? &sip_tech_info : &sip_tech;
@@ -5911,6 +6757,23 @@ static char *generate_random_string(char *buf, size_t size)
return buf;
}
+static char *generate_uri(struct sip_pvt *pvt, char *buf, size_t size)
+{
+ struct ast_str *uri = ast_str_alloca(size);
+ int ourport = ntohs(pvt->ourip.sin_port);
+ ast_str_set(&uri, 0, "%s", pvt->socket.type == SIP_TRANSPORT_TLS ? "sips:" : "sip:");
+ /* Here would be a great place to generate a UUID, but for now we'll
+ * use the handy random string generation function we already have
+ */
+ ast_str_append(&uri, 0, "%s", generate_random_string(buf, size));
+ ast_str_append(&uri, 0, "@%s", ast_inet_ntoa(pvt->ourip.sin_addr));
+ if (!sip_standard_port(pvt->socket.type, ourport)) {
+ ast_str_append(&uri, 0, ":%d", ourport);
+ }
+ ast_copy_string(buf, ast_str_buffer(uri), size);
+ return buf;
+}
+
/*! \brief Build SIP Call-ID value for a non-REGISTER transaction */
static void build_callid_pvt(struct sip_pvt *pvt)
{
@@ -5975,6 +6838,11 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *sin,
return NULL;
}
+ if (!(p->cc_params = ast_cc_config_params_init())) {
+ ao2_t_ref(p, -1, "Yuck, couldn't allocate cc_params struct. Get rid o' p");
+ return NULL;
+ }
+
if (req) {
set_socket_transport(&p->socket, req->socket.type); /* Later in ast_sip_ouraddrfor we need this to choose the right ip and port for the specific transport */
} else {
@@ -8234,6 +9102,9 @@ static int __transmit_response(struct sip_pvt *p, const char *msg, const struct
ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND);
add_rpid(&resp, p);
}
+ if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) {
+ add_cc_call_info_to_response(p, &resp);
+ }
add_header_contentLength(&resp, 0);
/* If we are cancelling an incoming invite for some reason, add information
@@ -8269,6 +9140,19 @@ static int __transmit_response(struct sip_pvt *p, const char *msg, const struct
return send_response(p, &resp, reliable, seqno);
}
+static int transmit_response_with_sip_etag(struct sip_pvt *p, const char *msg, const struct sip_request *req, struct sip_esc_entry *esc_entry, int need_new_etag)
+{
+ struct sip_request resp;
+
+ if (need_new_etag) {
+ create_new_sip_etag(esc_entry, 1);
+ }
+ respprep(&resp, p, msg, req);
+ add_header(&resp, "SIP-ETag", esc_entry->entity_tag);
+
+ return send_response(p, &resp, 0, 0);
+}
+
static int temp_pvt_init(void *data)
{
struct sip_pvt *p = data;
@@ -9305,6 +10189,38 @@ static void copy_request(struct sip_request *dst, const struct sip_request *src)
dst->data->used = src->data->used;
}
+static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp)
+{
+ char uri[SIPBUFSIZE];
+ struct ast_str *header = ast_str_alloca(SIPBUFSIZE);
+ struct ast_cc_agent *agent = find_sip_cc_agent_by_original_callid(p);
+ struct sip_cc_agent_pvt *agent_pvt;
+
+ if (!agent) {
+ /* Um, what? How could the SIP_OFFER_CC flag be set but there not be an
+ * agent? Oh well, we'll just warn and return without adding the header.
+ */
+ ast_log(LOG_WARNING, "Can't find SIP CC agent for call '%s' even though OFFER_CC flag was set?\n", p->callid);
+ return;
+ }
+
+ agent_pvt = agent->private_data;
+
+ if (!ast_strlen_zero(agent_pvt->subscribe_uri)) {
+ ast_copy_string(uri, agent_pvt->subscribe_uri, sizeof(uri));
+ } else {
+ generate_uri(p, uri, sizeof(uri));
+ ast_copy_string(agent_pvt->subscribe_uri, uri, sizeof(agent_pvt->subscribe_uri));
+ }
+ /* XXX Hardcode "NR" as the m reason for now. This should perhaps be changed
+ * to be more accurate. This parameter has no bearing on the actual operation
+ * of the feature; it's just there for informational purposes.
+ */
+ ast_str_set(&header, 0, "<%s>;purpose=call-completion;m=%s", uri, "NR");
+ add_header(resp, "Call-Info", ast_str_buffer(header));
+ ao2_ref(agent, -1);
+}
+
/*! \brief Used for 200 OK and 183 early media
\return Will return XMIT_ERROR for network errors.
*/
@@ -9320,6 +10236,9 @@ static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const
if (rpid == TRUE) {
add_rpid(&resp, p);
}
+ if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) {
+ add_cc_call_info_to_response(p, &resp);
+ }
if (p->rtp) {
if (!p->autoframing && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
ast_debug(1, "Setting framing from config on incoming call\n");
@@ -9479,7 +10398,7 @@ static void build_contact(struct sip_pvt *p)
}
/*! \brief Initiate new SIP request to peer/user */
-static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod)
+static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, const char * const explicit_uri)
{
struct ast_str *invite = ast_str_alloca(256);
char from[256];
@@ -9564,25 +10483,30 @@ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmetho
else
snprintf(from, sizeof(from), "\"%s\" <sip:%s@%s>;tag=%s", n, l, d, p->tag);
- /* If we're calling a registered SIP peer, use the fullcontact to dial to the peer */
- if (!ast_strlen_zero(p->fullcontact)) {
- /* If we have full contact, trust it */
- ast_str_append(&invite, 0, "%s", p->fullcontact);
+
+ if (!ast_strlen_zero(explicit_uri)) {
+ ast_str_set(&invite, 0, "%s", explicit_uri);
} else {
- /* Otherwise, use the username while waiting for registration */
- ast_str_append(&invite, 0, "sip:");
- if (!ast_strlen_zero(p->username)) {
- n = p->username;
- if (sip_cfg.pedanticsipchecking) {
- ast_uri_encode(n, tmp_n, sizeof(tmp_n), 0);
- n = tmp_n;
+ /* If we're calling a registered SIP peer, use the fullcontact to dial to the peer */
+ if (!ast_strlen_zero(p->fullcontact)) {
+ /* If we have full contact, trust it */
+ ast_str_append(&invite, 0, "%s", p->fullcontact);
+ } else {
+ /* Otherwise, use the username while waiting for registration */
+ ast_str_append(&invite, 0, "sip:");
+ if (!ast_strlen_zero(p->username)) {
+ n = p->username;
+ if (sip_cfg.pedanticsipchecking) {
+ ast_uri_encode(n, tmp_n, sizeof(tmp_n), 0);
+ n = tmp_n;
+ }
+ ast_str_append(&invite, 0, "%s@", n);
}
- ast_str_append(&invite, 0, "%s@", n);
+ ast_str_append(&invite, 0, "%s", p->tohost);
+ if (p->portinuri)
+ ast_str_append(&invite, 0, ":%d", ntohs(p->sa.sin_port));
+ ast_str_append(&invite, 0, "%s", urioptions);
}
- ast_str_append(&invite, 0, "%s", p->tohost);
- if (p->portinuri)
- ast_str_append(&invite, 0, ":%d", ntohs(p->sa.sin_port));
- ast_str_append(&invite, 0, "%s", urioptions);
}
/* If custom URI options have been provided, append them */
@@ -9674,6 +10598,39 @@ static void add_diversion_header(struct sip_request *req, struct sip_pvt *pvt)
add_header(req, "Diversion", header_text);
}
+static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri)
+{
+ struct sip_pvt *pvt;
+ int expires;
+
+ epa_entry->publish_type = publish_type;
+
+ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_PUBLISH, NULL))) {
+ return -1;
+ }
+
+ sip_pvt_lock(pvt);
+
+ if (create_addr(pvt, epa_entry->destination, NULL, TRUE)) {
+ dialog_unlink_all(pvt, TRUE, TRUE);
+ dialog_unref(pvt, "create_addr failed in transmit_publish. Unref dialog");
+ }
+ ast_sip_ouraddrfor(&pvt->sa.sin_addr, &pvt->ourip, pvt);
+ ast_set_flag(&pvt->flags[0], SIP_OUTGOING);
+ expires = (publish_type == SIP_PUBLISH_REMOVE) ? 0 : DEFAULT_PUBLISH_EXPIRES;
+ pvt->expiry = expires;
+
+ /* Bump refcount for sip_pvt's reference */
+ ao2_ref(epa_entry, +1);
+ pvt->epa_entry = epa_entry;
+
+ transmit_invite(pvt, SIP_PUBLISH, FALSE, 2, explicit_uri);
+ sip_pvt_unlock(pvt);
+ sip_scheddestroy(pvt, DEFAULT_TRANS_TIMEOUT);
+ dialog_unref(pvt, "Done with the sip_pvt allocated for transmitting PUBLISH");
+ return 0;
+}
+
/*! \brief Build REFER/INVITE/OPTIONS/SUBSCRIBE message and transmit it
\param init 0 = Prepare request within dialog, 1= prepare request, new branch, 2= prepare new request and new dialog. do_proxy_auth calls this with init!=2
\param p sip_pvt structure
@@ -9681,7 +10638,7 @@ static void add_diversion_header(struct sip_request *req, struct sip_pvt *pvt)
\param sipmethod unknown
*/
-static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init)
+static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri)
{
struct sip_request req;
struct ast_variable *var;
@@ -9693,7 +10650,7 @@ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init)
build_via(p);
}
if (init > 1)
- initreqprep(&req, p, sipmethod);
+ initreqprep(&req, p, sipmethod, explicit_uri);
else
/* If init=1, we should not generate a new branch. If it's 0, we need a new branch. */
reqprep(&req, p, sipmethod, 0, init ? 0 : 1);
@@ -9711,12 +10668,16 @@ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init)
add_header(&req, "Referred-By", buf);
}
}
- } else if (sipmethod == SIP_SUBSCRIBE) { /* We only support sending MWI subscriptions right now */
+ } else if (sipmethod == SIP_SUBSCRIBE) {
char buf[SIPBUFSIZE];
-
- add_header(&req, "Event", "message-summary");
- add_header(&req, "Accept", "application/simple-message-summary");
- snprintf(buf, sizeof(buf), "%d", mwi_expiry);
+ if (p->subscribed == MWI_NOTIFICATION) {
+ add_header(&req, "Event", "message-summary");
+ add_header(&req, "Accept", "application/simple-message-summary");
+ } else if (p->subscribed == CALL_COMPLETION) {
+ add_header(&req, "Event", "call-completion");
+ add_header(&req, "Accept", "application/call-completion");
+ }
+ snprintf(buf, sizeof(buf), "%d", p->expiry);
add_header(&req, "Expires", buf);
}
@@ -9805,13 +10766,33 @@ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init)
add_header_contentLength(&req, ast_str_strlen(p->notify->content));
if (ast_str_strlen(p->notify->content))
add_line(&req, ast_str_buffer(p->notify->content));
+ } else if (sipmethod == SIP_PUBLISH) {
+ char expires[SIPBUFSIZE];
+ switch (p->epa_entry->static_data->event) {
+ case CALL_COMPLETION:
+ snprintf(expires, sizeof(expires), "%d", p->expiry);
+ add_header(&req, "Event", "call-completion");
+ add_header(&req, "Expires", expires);
+ if (p->epa_entry->publish_type != SIP_PUBLISH_INITIAL) {
+ add_header(&req, "SIP-If-Match", p->epa_entry->entity_tag);
+ }
+ if (!ast_strlen_zero(p->epa_entry->body)) {
+ add_header(&req, "Content-Type", "application/pidf+xml");
+ add_header_contentLength(&req, strlen(p->epa_entry->body));
+ add_line(&req, p->epa_entry->body);
+ } else {
+ add_header_contentLength(&req, 0);
+ }
+ default:
+ break;
+ }
} else {
add_header_contentLength(&req, 0);
}
if (!p->initreq.headers || init > 2)
initialize_initreq(p, &req);
- if (sipmethod == SIP_INVITE) {
+ if (sipmethod == SIP_INVITE || sipmethod == SIP_SUBSCRIBE) {
p->lastinvite = p->ocseq;
}
return send_request(p, &req, init ? XMIT_CRITICAL : XMIT_RELIABLE, p->ocseq);
@@ -9845,7 +10826,7 @@ static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi)
/* If we already have a subscription up simply send a resubscription */
if (mwi->call) {
- transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 0);
+ transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 0, NULL);
return 0;
}
@@ -9866,6 +10847,8 @@ static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi)
mwi->call = dialog_unref(mwi->call, "unref dialog after unlink_all");
return 0;
}
+
+ mwi->call->expiry = mwi_expiry;
if (!mwi->dnsmgr && mwi->portno) {
mwi->call->sa.sin_port = htons(mwi->portno);
@@ -9900,7 +10883,7 @@ static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi)
mwi->call->mwi = ASTOBJ_REF(mwi);
/* Actually send the packet */
- transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 2);
+ transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 2, NULL);
return 0;
}
@@ -10084,6 +11067,34 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
}
}
+static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscription, enum sip_cc_notify_state state)
+{
+ struct sip_request req;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ char uri[SIPBUFSIZE];
+ char state_str[64];
+
+ if (state < CC_QUEUED || state > CC_READY) {
+ ast_log(LOG_WARNING, "Invalid state provided for transmit_cc_notify (%d)\n", state);
+ return -1;
+ }
+
+ reqprep(&req, subscription, SIP_NOTIFY, 0, TRUE);
+ snprintf(state_str, sizeof(state_str), "%s\r\n", sip_cc_notify_state_map[state].state_string);
+ add_header(&req, "Event", "call-completion");
+ add_header(&req, "Content-Type", "application/call-completion");
+ if (state == CC_READY) {
+ generate_uri(subscription, agent_pvt->notify_uri, sizeof(agent_pvt->notify_uri));
+ snprintf(uri, sizeof(uri) - 1, "cc-URI: %s\r\n", agent_pvt->notify_uri);
+ }
+ add_header_contentLength(&req, strlen(state_str) +
+ (state == CC_READY ? strlen(uri) : 0));
+ add_line(&req, state_str);
+ if (state == CC_READY) {
+ add_line(&req, uri);
+ }
+ return send_request(subscription, &req, XMIT_RELIABLE, subscription->ocseq);
+}
/*! \brief Used in the SUBSCRIBE notification subsystem (RFC3265) */
static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout)
@@ -10183,7 +11194,7 @@ static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs,
int ourport = ntohs(p->ourip.sin_port);
const char *exten = S_OR(vmexten, default_vmexten);
- initreqprep(&req, p, SIP_NOTIFY);
+ initreqprep(&req, p, SIP_NOTIFY, NULL);
add_header(&req, "Event", "message-summary");
add_header(&req, "Content-Type", default_notifymime);
ast_str_append(&out, 0, "Messages-Waiting: %s\r\n", newmsgs ? "yes" : "no");
@@ -10297,7 +11308,7 @@ static int manager_sipnotify(struct mansession *s, const struct message *m)
dialog_ref(p, "bump the count of p, which transmit_sip_request will decrement.");
sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
- transmit_invite(p, SIP_NOTIFY, 0, 2);
+ transmit_invite(p, SIP_NOTIFY, 0, 2, NULL);
astman_send_ack(s, m, "Notify Sent");
ast_variables_destroy(vars);
@@ -12438,13 +13449,13 @@ static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, c
the dialplan, so that the outbound call also is a sips: call or encrypted
IAX2 call. If that's not available, the call should FAIL.
*/
-static int get_destination(struct sip_pvt *p, struct sip_request *oreq)
+static int get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id)
{
char tmp[256] = "", *uri, *domain, *dummy = NULL;
char tmpf[256] = "", *from = NULL;
struct sip_request *req;
char *decoded_uri;
-
+
req = oreq;
if (!req)
req = &p->initreq;
@@ -12453,7 +13464,7 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq)
if (req->rlPart2)
ast_copy_string(tmp, REQ_OFFSET_TO_STR(req, rlPart2), sizeof(tmp));
- uri = get_in_brackets(tmp);
+ uri = ast_strdupa(get_in_brackets(tmp));
if (parse_uri(uri, "sip:,sips:", &uri, &dummy, &domain, &dummy, NULL)) {
ast_log(LOG_WARNING, "Not a SIP header (%s)?\n", uri);
@@ -12510,6 +13521,7 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq)
char hint[AST_MAX_EXTENSION];
return (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, p->exten) ? 0 : -1);
} else {
+ struct ast_cc_agent *agent;
decoded_uri = ast_strdupa(uri);
ast_uri_decode(decoded_uri);
/* Check the dialplan for the username part of the request URI,
@@ -12522,6 +13534,22 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq)
if (!oreq)
ast_string_field_set(p, exten, decoded_uri);
return 0;
+ } else if ((agent = find_sip_cc_agent_by_notify_uri(tmp))) {
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ /* This is a CC recall. We can set p's extension to the exten from
+ * the original INVITE
+ */
+ ast_string_field_set(p, exten, agent_pvt->original_exten);
+ /* And we need to let the CC core know that the caller is attempting
+ * his recall
+ */
+ ast_cc_agent_recalling(agent->core_id, "SIP caller %s is attempting recall",
+ agent->device_name);
+ if (cc_recall_core_id) {
+ *cc_recall_core_id = agent->core_id;
+ }
+ ao2_ref(agent, -1);
+ return 0;
}
}
@@ -13033,6 +14061,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
ast_string_field_set(p, engine, peer->engine);
p->disallowed_methods = peer->disallowed_methods;
set_pvt_allowed_methods(p, req);
+ ast_cc_copy_config_params(p->cc_params, peer->cc_params);
if (peer->callingpres) /* Peer calling pres setting will override RPID */
p->callingpres = peer->callingpres;
if (peer->maxms && peer->lastms)
@@ -15981,7 +17010,7 @@ static char *sip_cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n", a->argv[2], a->argv[i]);
dialog_ref(p, "bump the count of p, which transmit_sip_request will decrement.");
sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
- transmit_invite(p, SIP_NOTIFY, 0, 2);
+ transmit_invite(p, SIP_NOTIFY, 0, 2, NULL);
}
return CLI_SUCCESS;
@@ -16061,7 +17090,7 @@ static int do_proxy_auth(struct sip_pvt *p, struct sip_request *req, enum sip_au
/* Now we have a reply digest */
p->options->auth = digest;
p->options->authheader = respheader;
- return transmit_invite(p, sipmethod, sipmethod == SIP_INVITE, init);
+ return transmit_invite(p, sipmethod, sipmethod == SIP_INVITE, init, NULL);
}
/*! \brief reply to authentication for outbound registrations
@@ -16698,6 +17727,106 @@ static void handle_response_update(struct sip_pvt *p, int resp, const char *rest
}
}
+static void cc_handle_publish_error(struct sip_pvt *pvt, const int resp, struct sip_request *req, struct sip_epa_entry *epa_entry)
+{
+ struct cc_epa_entry *cc_entry = epa_entry->instance_data;
+ struct sip_monitor_instance *monitor_instance = ao2_callback(sip_monitor_instances, 0,
+ find_sip_monitor_instance_by_suspension_entry, epa_entry);
+ const char *min_expires;
+
+ if (!monitor_instance) {
+ ast_log(LOG_WARNING, "Can't find monitor_instance corresponding to epa_entry %p.\n", epa_entry);
+ return;
+ }
+
+ if (resp != 423) {
+ ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name,
+ "Received error response to our PUBLISH");
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
+
+ /* Allrighty, the other end doesn't like our Expires value. They think it's
+ * too small, so let's see if they've provided a more sensible value. If they
+ * haven't, then we'll just double our Expires value and see if they like that
+ * instead.
+ *
+ * XXX Ideally this logic could be placed into its own function so that SUBSCRIBE,
+ * PUBLISH, and REGISTER could all benefit from the same shared code.
+ */
+ min_expires = get_header(req, "Min-Expires");
+ if (ast_strlen_zero(min_expires)) {
+ pvt->expiry *= 2;
+ if (pvt->expiry < 0) {
+ /* You dork! You overflowed! */
+ ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name,
+ "PUBLISH expiry overflowed");
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
+ } else if (sscanf(min_expires, "%d", &pvt->expiry) != 1) {
+ ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name,
+ "Min-Expires has non-numeric value");
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
+ /* At this point, we have most certainly changed pvt->expiry, so try transmitting
+ * again
+ */
+ transmit_invite(pvt, SIP_PUBLISH, FALSE, 0, NULL);
+ ao2_ref(monitor_instance, -1);
+}
+
+static void handle_response_publish(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno)
+{
+ struct sip_epa_entry *epa_entry = p->epa_entry;
+ const char *etag = get_header(req, "Sip-ETag");
+
+ ast_assert(epa_entry != NULL);
+
+ if (resp == 401 || resp == 407) {
+ ast_string_field_set(p, theirtag, NULL);
+ if (p->options) {
+ p->options->auth_type = (resp == 401 ? WWW_AUTH : PROXY_AUTH);
+ }
+ if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, resp, SIP_PUBLISH, 0)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on PUBLISH to '%s'\n", get_header(&p->initreq, "From"));
+ pvt_set_needdestroy(p, "Failed to authenticate on PUBLISH");
+ sip_alreadygone(p);
+ }
+ return;
+ }
+
+ if (resp == 501 || resp == 405) {
+ mark_method_unallowed(&p->allowed_methods, SIP_PUBLISH);
+ }
+
+ if (resp == 200) {
+ p->authtries = 0;
+ /* If I've read section 6, item 6 of RFC 3903 correctly,
+ * an ESC will only generate a new etag when it sends a 200 OK
+ */
+ if (!ast_strlen_zero(etag)) {
+ ast_copy_string(epa_entry->entity_tag, etag, sizeof(epa_entry->entity_tag));
+ }
+ /* The nominal case. Everything went well. Everybody is happy.
+ * Each EPA will have a specific action to take as a result of this
+ * development, so ... callbacks!
+ */
+ if (epa_entry->static_data->handle_ok) {
+ epa_entry->static_data->handle_ok(p, req, epa_entry);
+ }
+ } else {
+ /* Rather than try to make individual callbacks for each error
+ * type, there is just a single error callback. The callback
+ * can distinguish between error messages and do what it needs to
+ */
+ if (epa_entry->static_data->handle_error) {
+ epa_entry->static_data->handle_error(p, resp, req, epa_entry);
+ }
+ }
+}
+
/*! \brief Handle SIP response to INVITE dialogue */
static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno)
{
@@ -16769,6 +17898,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
ast_channel_queue_connected_line_update(p->owner, &connected);
}
+ sip_handle_cc(p, req, AST_CC_CCNR);
ast_queue_control(p->owner, AST_CONTROL_RINGING);
if (p->owner->_state != AST_STATE_UP) {
ast_setstate(p->owner, AST_STATE_RINGING);
@@ -16794,6 +17924,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
struct ast_party_redirecting redirecting = {{0,},};
change_redirecting_information(p, req, &redirecting, FALSE);
ast_channel_queue_redirecting_update(p->owner, &redirecting);
+ sip_handle_cc(p, req, AST_CC_CCNR);
}
check_pendings(p);
break;
@@ -16811,6 +17942,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
ast_channel_queue_connected_line_update(p->owner, &connected);
}
+ sip_handle_cc(p, req, AST_CC_CCNR);
}
if (find_sdp(req)) {
if (p->invitestate != INV_CANCELLED)
@@ -17569,6 +18701,11 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc
need to hang around for something more "definitive" */
if (resp != 100)
handle_response_peerpoke(p, resp, req);
+ } else if (sipmethod == SIP_PUBLISH) {
+ /* SIP PUBLISH transcends this morass of doodoo and instead
+ * we just always call the response handler. Good gravy!
+ */
+ handle_response_publish(p, resp, rest, req, seqno);
} else if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
switch(resp) {
case 100: /* 100 Trying */
@@ -17763,8 +18900,10 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc
case 486: /* Busy here */
case 600: /* Busy everywhere */
case 603: /* Decline */
- if (p->owner)
+ if (p->owner) {
+ sip_handle_cc(p, req, AST_CC_CCBS);
ast_queue_control(p->owner, AST_CONTROL_BUSY);
+ }
break;
case 482: /*!
\note SIP is incapable of performing a hairpin call, which
@@ -18214,6 +19353,66 @@ static const char *gettag(const struct sip_request *req, const char *header, cha
return NULL;
}
+static int handle_cc_notify(struct sip_pvt *pvt, struct sip_request *req)
+{
+ struct sip_monitor_instance *monitor_instance = ao2_callback(sip_monitor_instances, 0,
+ find_sip_monitor_instance_by_subscription_pvt, pvt);
+ const char *status = get_body(req, "cc-state", ':');
+ struct cc_epa_entry *cc_entry;
+ char *uri;
+
+ if (!monitor_instance) {
+ transmit_response(pvt, "400 Bad Request", req);
+ return -1;
+ }
+
+ if (ast_strlen_zero(status)) {
+ ao2_ref(monitor_instance, -1);
+ transmit_response(pvt, "400 Bad Request", req);
+ return -1;
+ }
+
+ if (!strcmp(status, "queued")) {
+ /* We've been told that we're queued. This is the endpoint's way of telling
+ * us that it has accepted our CC request. We need to alert the core of this
+ * development
+ */
+ ast_cc_monitor_request_acked(monitor_instance->core_id, "SIP endpoint %s accepted request", monitor_instance->device_name);
+ transmit_response(pvt, "200 OK", req);
+ ao2_ref(monitor_instance, -1);
+ return 0;
+ }
+
+ /* It's open! Yay! */
+ uri = get_body(req, "cc-URI", ':');
+ if (ast_strlen_zero(uri)) {
+ uri = get_in_brackets((char *)get_header(req, "From"));
+ }
+
+ ast_string_field_set(monitor_instance, notify_uri, uri);
+ if (monitor_instance->suspension_entry) {
+ cc_entry = monitor_instance->suspension_entry->instance_data;
+ if (cc_entry->current_state == CC_CLOSED) {
+ /* If we've created a suspension entry and the current state is closed, then that means
+ * we got a notice from the CC core earlier to suspend monitoring, but because this particular
+ * call leg had not yet notified us that it was ready for recall, it meant that we
+ * could not yet send a PUBLISH. Now, however, we can.
+ */
+ construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body,
+ sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
+ transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_INITIAL, monitor_instance->notify_uri);
+ } else {
+ ast_cc_monitor_callee_available(monitor_instance->core_id, "SIP monitored callee has become available");
+ }
+ } else {
+ ast_cc_monitor_callee_available(monitor_instance->core_id, "SIP monitored callee has become available");
+ }
+ ao2_ref(monitor_instance, -1);
+ transmit_response(pvt, "200 OK", req);
+
+ return 0;
+}
+
/*! \brief Handle incoming notifications */
static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, const char *e)
{
@@ -18376,6 +19575,8 @@ static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, str
/* Used by Sipura/Linksys for NAT pinhole,
* just confirm that we recieved the packet. */
transmit_response(p, "200 OK", req);
+ } else if (!strcmp(event, "call-completion")) {
+ res = handle_cc_notify(p, req);
} else {
/* We don't understand this event. */
transmit_response(p, "489 Bad event", req);
@@ -18411,7 +19612,7 @@ static int handle_request_options(struct sip_pvt *p, struct sip_request *req)
return 0;
}
- res = get_destination(p, req);
+ res = get_destination(p, req, NULL);
build_contact(p);
if (ast_strlen_zero(p->context))
@@ -19271,6 +20472,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int
/* This is a new invite */
/* Handle authentication if this is our first invite */
struct ast_party_redirecting redirecting = {{0,},};
+ int cc_recall_core_id = -1;
set_pvt_allowed_methods(p, req);
res = check_user(p, req, SIP_INVITE, e, XMIT_RELIABLE, sin);
if (res == AUTH_CHALLENGE_SENT) {
@@ -19338,7 +20540,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int
res = 0;
goto request_invite_cleanup;
}
- gotdest = get_destination(p, NULL); /* Get destination right away */
+ gotdest = get_destination(p, NULL, &cc_recall_core_id); /* Get destination right away */
change_redirecting_information(p, req, &redirecting, FALSE); /*Will return immediately if no Diversion header is present */
extract_uri(p, req); /* Get the Contact URI */
build_contact(p); /* Build our contact header */
@@ -19376,6 +20578,10 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int
make_our_tag(p->tag, sizeof(p->tag));
/* First invitation - create the channel */
c = sip_new(p, AST_STATE_DOWN, S_OR(p->peername, NULL), NULL);
+ if (cc_recall_core_id != -1) {
+ ast_setup_cc_recall_datastore(c, cc_recall_core_id);
+ ast_cc_agent_set_interfaces_chanvar(c);
+ }
*recount = 1;
/* Save Record-Route for any later requests we make on this dialogue */
@@ -20454,6 +21660,496 @@ static int handle_request_message(struct sip_pvt *p, struct sip_request *req)
return 1;
}
+static enum sip_publish_type determine_sip_publish_type(struct sip_request *req, const char * const event, const char * const etag, const char * const expires, int *expires_int)
+{
+ int etag_present = !ast_strlen_zero(etag);
+ int body_present = req->lines > 0;
+
+ ast_assert(expires_int != NULL);
+
+ if (ast_strlen_zero(expires)) {
+ /* Section 6, item 4, second bullet point of RFC 3903 says to
+ * use a locally-configured default expiration if none is provided
+ * in the request
+ */
+ *expires_int = DEFAULT_PUBLISH_EXPIRES;
+ } else if (sscanf(expires, "%30d", expires_int) != 1) {
+ return SIP_PUBLISH_UNKNOWN;
+ }
+
+ if (*expires_int == 0) {
+ return SIP_PUBLISH_REMOVE;
+ } else if (!etag_present && body_present) {
+ return SIP_PUBLISH_INITIAL;
+ } else if (etag_present && !body_present) {
+ return SIP_PUBLISH_REFRESH;
+ } else if (etag_present && body_present) {
+ return SIP_PUBLISH_MODIFY;
+ }
+
+ return SIP_PUBLISH_UNKNOWN;
+}
+
+#ifdef HAVE_LIBXML2
+static void get_pidf_body(struct sip_request *req, char *pidf_body, size_t size)
+{
+ int i;
+ struct ast_str *str = ast_str_alloca(size);
+ for (i = 0; i < req->lines; ++i) {
+ ast_str_append(&str, 0, "%s", REQ_OFFSET_TO_STR(req, line[i]));
+ }
+ ast_copy_string(pidf_body, ast_str_buffer(str), size);
+}
+
+static int pidf_validate_tuple(struct ast_xml_node *tuple_node)
+{
+ const char *id;
+ int status_found = FALSE;
+ struct ast_xml_node *tuple_children;
+ struct ast_xml_node *tuple_children_iterator;
+ /* Tuples have to have an id attribute or they're invalid */
+ if (!(id = ast_xml_get_attribute(tuple_node, "id"))) {
+ ast_log(LOG_WARNING, "Tuple XML element has no attribute 'id'\n");
+ return FALSE;
+ }
+ /* We don't care what it actually is, just that it's there */
+ ast_xml_free_attr(id);
+ /* This is a tuple. It must have a status element */
+ if (!(tuple_children = ast_xml_node_get_children(tuple_node))) {
+ /* The tuple has no children. It sucks */
+ ast_log(LOG_WARNING, "Tuple XML element has no child elements\n");
+ return FALSE;
+ }
+ for (tuple_children_iterator = tuple_children; tuple_children_iterator;
+ tuple_children_iterator = ast_xml_node_get_next(tuple_children_iterator)) {
+ /* Similar to the wording used regarding tuples, the status element should appear
+ * first. However, we will once again relax things and accept the status at any
+ * position. We will enforce that only a single status element can be present.
+ */
+ if (strcmp(ast_xml_node_get_name(tuple_children_iterator), "status")) {
+ /* Not the status, we don't care */
+ continue;
+ }
+ if (status_found == TRUE) {
+ /* THERE CAN BE ONLY ONE!!! */
+ ast_log(LOG_WARNING, "Multiple status elements found in tuple. Only one allowed\n");
+ return FALSE;
+ }
+ status_found = TRUE;
+ }
+ return status_found;
+}
+
+
+static int pidf_validate_presence(struct ast_xml_doc *doc)
+{
+ struct ast_xml_node *presence_node = ast_xml_get_root(doc);
+ struct ast_xml_node *child_nodes;
+ struct ast_xml_node *node_iterator;
+ struct ast_xml_ns *ns;
+ const char *entity;
+ const char *namespace;
+ const char presence_namespace[] = "urn:ietf:params:xml:ns:pidf";
+
+ if (!presence_node) {
+ ast_log(LOG_WARNING, "Unable to retrieve root node of the XML document\n");
+ return FALSE;
+ }
+ /* Okay, we managed to open the document! YAY! Now, let's start making sure it's all PIDF-ified
+ * correctly.
+ */
+ if (strcmp(ast_xml_node_get_name(presence_node), "presence")) {
+ ast_log(LOG_WARNING, "Root node of PIDF document is not 'presence'. Invalid\n");
+ return FALSE;
+ }
+
+ /* The presence element must have an entity attribute and an xmlns attribute. Furthermore
+ * the xmlns attribute must be "urn:ietf:params:xml:ns:pidf"
+ */
+ if (!(entity = ast_xml_get_attribute(presence_node, "entity"))) {
+ ast_log(LOG_WARNING, "Presence element of PIDF document has no 'entity' attribute\n");
+ return FALSE;
+ }
+ /* We're not interested in what the entity is, just that it exists */
+ ast_xml_free_attr(entity);
+
+ if (!(ns = ast_xml_find_namespace(doc, presence_node, NULL))) {
+ ast_log(LOG_WARNING, "Couldn't find default namespace...\n");
+ return FALSE;
+ }
+
+ namespace = ast_xml_get_ns_href(ns);
+ if (ast_strlen_zero(namespace) || strcmp(namespace, presence_namespace)) {
+ ast_log(LOG_WARNING, "PIDF document has invalid namespace value %s\n", namespace);
+ return FALSE;
+ }
+
+ if (!(child_nodes = ast_xml_node_get_children(presence_node))) {
+ ast_log(LOG_WARNING, "PIDF document has no elements as children of 'presence'. Invalid\n");
+ return FALSE;
+ }
+
+ /* Check for tuple elements. RFC 3863 says that PIDF documents can have any number of
+ * tuples, including 0. The big thing here is that if there are tuple elements present,
+ * they have to have a single status element within.
+ *
+ * The RFC is worded such that tuples should appear as the first elements as children of
+ * the presence element. However, we'll be accepting of documents which may place other elements
+ * before the tuple(s).
+ */
+ for (node_iterator = child_nodes; node_iterator;
+ node_iterator = ast_xml_node_get_next(node_iterator)) {
+ if (strcmp(ast_xml_node_get_name(node_iterator), "tuple")) {
+ /* Not a tuple. We don't give a rat's hind quarters */
+ continue;
+ }
+ if (pidf_validate_tuple(node_iterator) == FALSE) {
+ ast_log(LOG_WARNING, "Unable to validate tuple\n");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/*!
+ * \brief Makes sure that body is properly formatted PIDF
+ *
+ * Specifically, we check that the document has a "presence" element
+ * at the root and that within that, there is at least one "tuple" element
+ * that contains a "status" element.
+ *
+ * XXX This function currently assumes a default namespace is used. Of course
+ * if you're not using a default namespace, you're probably a stupid jerk anyway.
+ *
+ * \param req The SIP request to check
+ * \param[out] pidf_doc The validated PIDF doc.
+ * \retval FALSE The XML was malformed or the basic PIDF structure was marred
+ * \retval TRUE The PIDF document is of a valid format
+ */
+static int sip_pidf_validate(struct sip_request *req, struct ast_xml_doc **pidf_doc)
+{
+ struct ast_xml_doc *doc;
+ int content_length;
+ const char *content_length_str = get_header(req, "Content-Length");
+ const char *content_type = get_header(req, "Content-Type");
+ char pidf_body[SIPBUFSIZE];
+ int res;
+
+ if (ast_strlen_zero(content_type) || strcmp(content_type, "application/pidf+xml")) {
+ ast_log(LOG_WARNING, "Content type is not PIDF\n");
+ return FALSE;
+ }
+
+ if (ast_strlen_zero(content_length_str)) {
+ ast_log(LOG_WARNING, "No content length. Can't determine bounds of PIDF document\n");
+ return FALSE;
+ }
+
+ if (sscanf(content_length_str, "%30d", &content_length) != 1) {
+ ast_log(LOG_WARNING, "Invalid content length provided\n");
+ return FALSE;
+ }
+
+ if (content_length > sizeof(pidf_body)) {
+ ast_log(LOG_WARNING, "Content length of PIDF document truncated to %d bytes\n", (int) sizeof(pidf_body));
+ content_length = sizeof(pidf_body);
+ }
+
+ get_pidf_body(req, pidf_body, content_length);
+
+ if (!(doc = ast_xml_read_memory(pidf_body, content_length))) {
+ ast_log(LOG_WARNING, "Unable to open XML PIDF document. Is it malformed?\n");
+ return FALSE;
+ }
+
+ res = pidf_validate_presence(doc);
+ if (res == TRUE) {
+ *pidf_doc = doc;
+ } else {
+ ast_xml_close(doc);
+ }
+ return res;
+}
+
+static int cc_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry)
+{
+ const char *uri = REQ_OFFSET_TO_STR(req, rlPart2);
+ struct ast_cc_agent *agent = find_sip_cc_agent_by_notify_uri(uri);
+ struct sip_cc_agent_pvt *agent_pvt;
+ struct ast_xml_doc *pidf_doc = NULL;
+ const char *basic_status = NULL;
+ struct ast_xml_node *presence_node;
+ struct ast_xml_node *presence_children;
+ struct ast_xml_node *tuple_node;
+ struct ast_xml_node *tuple_children;
+ struct ast_xml_node *status_node;
+ struct ast_xml_node *status_children;
+ struct ast_xml_node *basic_node;
+ int res = 0;
+
+ if (!agent) {
+ ast_log(LOG_WARNING, "Could not find agent using uri '%s'\n", uri);
+ transmit_response(pvt, "412 Conditional Request Failed", req);
+ return -1;
+ }
+
+ agent_pvt = agent->private_data;
+
+ if (sip_pidf_validate(req, &pidf_doc) == FALSE) {
+ res = -1;
+ goto cc_publish_cleanup;
+ }
+
+ /* It's important to note that the PIDF validation routine has no knowledge
+ * of what we specifically want in this instance. A valid PIDF document could
+ * have no tuples, or it could have tuples whose status element has no basic
+ * element contained within. While not violating the PIDF spec, these are
+ * insufficient for our needs in this situation
+ */
+ presence_node = ast_xml_get_root(pidf_doc);
+ if (!(presence_children = ast_xml_node_get_children(presence_node))) {
+ ast_log(LOG_WARNING, "No tuples within presence element.\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
+
+ if (!(tuple_node = ast_xml_find_element(presence_children, "tuple", NULL, NULL))) {
+ ast_log(LOG_NOTICE, "Couldn't find tuple node?\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
+
+ /* We already made sure that the tuple has a status node when we validated the PIDF
+ * document earlier. So there's no need to enclose this operation in an if statement.
+ */
+ tuple_children = ast_xml_node_get_children(tuple_node);
+ status_node = ast_xml_find_element(tuple_children, "status", NULL, NULL);
+
+ if (!(status_children = ast_xml_node_get_children(status_node))) {
+ ast_log(LOG_WARNING, "No basic elements within status element.\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
+
+ if (!(basic_node = ast_xml_find_element(status_children, "basic", NULL, NULL))) {
+ ast_log(LOG_WARNING, "Couldn't find basic node?\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
+
+ basic_status = ast_xml_get_text(basic_node);
+
+ if (ast_strlen_zero(basic_status)) {
+ ast_log(LOG_NOTICE, "NOthing in basic node?\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
+
+ if (!strcmp(basic_status, "open")) {
+ agent_pvt->is_available = TRUE;
+ ast_cc_agent_caller_available(agent->core_id, "Received PUBLISH stating SIP caller %s is available",
+ agent->device_name);
+ } else if (!strcmp(basic_status, "closed")) {
+ agent_pvt->is_available = FALSE;
+ ast_cc_agent_caller_busy(agent->core_id, "Received PUBLISH stating SIP caller %s is busy",
+ agent->device_name);
+ } else {
+ ast_log(LOG_NOTICE, "Invalid content in basic element: %s\n", basic_status);
+ }
+
+cc_publish_cleanup:
+ if (basic_status) {
+ ast_xml_free_text(basic_status);
+ }
+ if (pidf_doc) {
+ ast_xml_close(pidf_doc);
+ }
+ ao2_ref(agent, -1);
+ if (res) {
+ transmit_response(pvt, "400 Bad Request", req);
+ }
+ return res;
+}
+
+#endif /* HAVE_LIBXML2 */
+
+static int handle_sip_publish_initial(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const int expires)
+{
+ struct sip_esc_entry *esc_entry = create_esc_entry(esc, req, expires);
+ int res = 0;
+
+ if (!esc_entry) {
+ transmit_response(p, "503 Internal Server Failure", req);
+ return -1;
+ }
+
+ if (esc->callbacks->initial_handler) {
+ res = esc->callbacks->initial_handler(p, req, esc, esc_entry);
+ }
+
+ if (!res) {
+ transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 0);
+ }
+
+ ao2_ref(esc_entry, -1);
+ return res;
+}
+
+static int handle_sip_publish_refresh(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag, const int expires)
+{
+ struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc);
+ int expires_ms = expires * 1000;
+ int res = 0;
+
+ if (!esc_entry) {
+ transmit_response(p, "412 Conditional Request Failed", req);
+ return -1;
+ }
+
+ AST_SCHED_REPLACE_UNREF(esc_entry->sched_id, sched, expires_ms, publish_expire, esc_entry,
+ ao2_ref(_data, -1),
+ ao2_ref(esc_entry, -1),
+ ao2_ref(esc_entry, +1));
+
+ if (esc->callbacks->refresh_handler) {
+ res = esc->callbacks->refresh_handler(p, req, esc, esc_entry);
+ }
+
+ if (!res) {
+ transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1);
+ }
+
+ ao2_ref(esc_entry, -1);
+ return res;
+}
+
+static int handle_sip_publish_modify(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag, const int expires)
+{
+ struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc);
+ int expires_ms = expires * 1000;
+ int res = 0;
+
+ if (!esc_entry) {
+ transmit_response(p, "412 Conditional Request Failed", req);
+ return -1;
+ }
+
+ AST_SCHED_REPLACE_UNREF(esc_entry->sched_id, sched, expires_ms, publish_expire, esc_entry,
+ ao2_ref(_data, -1),
+ ao2_ref(esc_entry, -1),
+ ao2_ref(esc_entry, +1));
+
+ if (esc->callbacks->modify_handler) {
+ res = esc->callbacks->modify_handler(p, req, esc, esc_entry);
+ }
+
+ if (!res) {
+ transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1);
+ }
+
+ ao2_ref(esc_entry, -1);
+ return res;
+}
+
+static int handle_sip_publish_remove(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag)
+{
+ struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc);
+ int res = 0;
+
+ if (!esc_entry) {
+ transmit_response(p, "412 Conditional Request Failed", req);
+ return -1;
+ }
+
+ AST_SCHED_DEL(sched, esc_entry->sched_id);
+ /* Scheduler's ref of the esc_entry */
+ ao2_ref(esc_entry, -1);
+
+ if (esc->callbacks->remove_handler) {
+ res = esc->callbacks->remove_handler(p, req, esc, esc_entry);
+ }
+
+ if (!res) {
+ transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1);
+ }
+
+ /* Ref from finding the esc_entry earlier in function */
+ ao2_unlink(esc->compositor, esc_entry);
+ ao2_ref(esc_entry, -1);
+ return res;
+}
+
+static int handle_request_publish(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, const int seqno, const char *uri)
+{
+ const char *etag = get_header(req, "SIP-If-Match");
+ const char *event = get_header(req, "Event");
+ struct event_state_compositor *esc;
+ enum sip_publish_type publish_type;
+ const char *expires_str = get_header(req, "Expires");
+ int expires_int;
+ int auth_result;
+ int handler_result = -1;
+
+ if (ast_strlen_zero(event)) {
+ transmit_response(p, "489 Bad Event", req);
+ return -1;
+ }
+
+ if (!(esc = get_esc(event))) {
+ transmit_response(p, "489 Bad Event", req);
+ return -1;
+ }
+
+ auth_result = check_user(p, req, SIP_PUBLISH, uri, XMIT_RELIABLE, sin);
+ if (auth_result == AUTH_CHALLENGE_SENT) {
+ p->lastinvite = seqno;
+ return 0;
+ } else if (auth_result < 0) {
+ if (auth_result == AUTH_FAKE_AUTH) {
+ ast_log(LOG_NOTICE, "Sending fake auth rejection for device %s\n", get_header(req, "From"));
+ transmit_fake_auth_response(p, SIP_INVITE, req, XMIT_RELIABLE);
+ } else {
+ ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", get_header(req, "From"));
+ transmit_response_reliable(p, "403 Forbidden", req);
+ }
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ ast_string_field_set(p, theirtag, NULL);
+ return 0;
+ } else if (auth_result == AUTH_SUCCESSFUL && p->lastinvite) {
+ /* We need to stop retransmitting the 401 */
+ __sip_ack(p, p->lastinvite, 1, 0);
+ }
+
+ publish_type = determine_sip_publish_type(req, event, etag, expires_str, &expires_int);
+
+ /* It is the responsibility of these handlers to formulate any response
+ * sent for a PUBLISH
+ */
+ switch (publish_type) {
+ case SIP_PUBLISH_UNKNOWN:
+ transmit_response(p, "400 Bad Request", req);
+ break;
+ case SIP_PUBLISH_INITIAL:
+ handler_result = handle_sip_publish_initial(p, req, esc, expires_int);
+ break;
+ case SIP_PUBLISH_REFRESH:
+ handler_result = handle_sip_publish_refresh(p, req, esc, etag, expires_int);
+ break;
+ case SIP_PUBLISH_MODIFY:
+ handler_result = handle_sip_publish_modify(p, req, esc, etag, expires_int);
+ break;
+ case SIP_PUBLISH_REMOVE:
+ handler_result = handle_sip_publish_remove(p, req, esc, etag);
+ break;
+ default:
+ transmit_response(p, "400 Impossible Condition", req);
+ break;
+ }
+
+ return handler_result;
+}
+
static void add_peer_mwi_subs(struct sip_peer *peer)
{
struct sip_mailbox *mailbox;
@@ -20466,6 +22162,63 @@ static void add_peer_mwi_subs(struct sip_peer *peer)
}
}
+static int handle_cc_subscribe(struct sip_pvt *p, struct sip_request *req)
+{
+ const char *uri = REQ_OFFSET_TO_STR(req, rlPart2);
+ char *param_separator;
+ struct ast_cc_agent *agent;
+ struct sip_cc_agent_pvt *agent_pvt;
+ const char *expires_str = get_header(req, "Expires");
+ int expires = -1; /* Just need it to be non-zero */
+
+ if (!ast_strlen_zero(expires_str)) {
+ sscanf(expires_str, "%d", &expires);
+ }
+
+ if ((param_separator = strchr(uri, ';'))) {
+ *param_separator = '\0';
+ }
+
+ if (!(agent = find_sip_cc_agent_by_subscribe_uri(uri))) {
+ if (!expires) {
+ /* Typically, if a 0 Expires reaches us and we can't find
+ * the corresponding agent, it means that the CC transaction
+ * has completed and so the calling side is just trying to
+ * clean up its subscription. We'll just respond with a
+ * 200 OK and be done with it
+ */
+ transmit_response(p, "200 OK", req);
+ return 0;
+ }
+ ast_log(LOG_WARNING, "Invalid URI '%s' in CC subscribe\n", uri);
+ transmit_response(p, "404 Not Found", req);
+ return -1;
+ }
+
+ agent_pvt = agent->private_data;
+
+ if (!expires) {
+ /* We got sent a SUBSCRIBE and found an agent. This means that CC
+ * is being canceled.
+ */
+ ast_cc_failed(agent->core_id, "CC is being canceled by %s", agent->device_name);
+ transmit_response(p, "200 OK", req);
+ ao2_ref(agent, -1);
+ return 0;
+ }
+
+ agent_pvt->subscribe_pvt = dialog_ref(p, "SIP CC agent gains reference to subscription dialog");
+ ast_cc_agent_accept_request(agent->core_id, "SIP caller %s has requested CC via SUBSCRIBE",
+ agent->device_name);
+ p->subscribed = CALL_COMPLETION;
+
+ /* We don't send a response here. That is done in the agent's ack callback or in the
+ * agent destructor, should a failure occur before we have responded
+ */
+ ao2_ref(agent, -1);
+ return 0;
+}
+
/*! \brief Handle incoming SUBSCRIBE request */
static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, const char *e)
{
@@ -20578,9 +22331,9 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
return 0;
}
- if (strcmp(event, "message-summary")) {
+ if (strcmp(event, "message-summary") && strcmp(event, "call-completion")) {
/* Get destination right away */
- gotdest = get_destination(p, NULL);
+ gotdest = get_destination(p, NULL, NULL);
}
/* Get full contact header - this needs to be used as a request URI in NOTIFY's */
@@ -20685,6 +22438,8 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
unref_peer(p->relatedpeer, "Unref previously stored relatedpeer ptr");
p->relatedpeer = ref_peer(authpeer, "setting dialog's relatedpeer pointer"); /* already refcounted...Link from pvt to peer UH- should this be dialog_ref()? */
/* Do not release authpeer here */
+ } else if (!strcmp(event, "call-completion")) {
+ handle_cc_subscribe(p, req);
} else { /* At this point, Asterisk does not understand the specified event */
transmit_response(p, "489 Bad Event", req);
ast_debug(2, "Received SIP subscribe for unknown event package: %s\n", event);
@@ -20695,7 +22450,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
}
/* Add subscription for extension state from the PBX core */
- if (p->subscribed != MWI_NOTIFICATION && !resubscribe) {
+ if (p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION && !resubscribe) {
if (p->stateid > -1) {
ast_extension_state_del(p->stateid, cb_extensionstate);
/* we need to dec the refcount, now that the extensionstate is removed */
@@ -20716,10 +22471,13 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
p->expiry = min_expiry;
if (sipdebug) {
- if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer)
+ if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer) {
ast_debug(2, "Adding subscription for mailbox notification - peer %s\n", p->relatedpeer->name);
- else
+ } else if (p->subscribed == CALL_COMPLETION) {
+ ast_debug(2, "Adding CC subscription for peer %s\n", p->username);
+ } else {
ast_debug(2, "Adding subscription for extension %s context %s for peer %s\n", p->exten, p->context, p->username);
+ }
}
if (p->autokillid > -1 && sip_cancel_destroy(p)) /* Remove subscription expiry for renewals */
ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
@@ -20734,7 +22492,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
sip_send_mwi_to_peer(p->relatedpeer, NULL, 0);
ao2_unlock(p->relatedpeer);
}
- } else {
+ } else if (p->subscribed != CALL_COMPLETION) {
struct sip_pvt *p_old;
if ((firststate = ast_extension_state(NULL, p->context, p->exten)) < 0) {
@@ -21005,7 +22763,7 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct so
}
}
- if (!e && (p->method == SIP_INVITE || p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER || p->method == SIP_NOTIFY)) {
+ if (!e && (p->method == SIP_INVITE || p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER || p->method == SIP_NOTIFY || p->method == SIP_PUBLISH)) {
transmit_response(p, "400 Bad request", req);
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
return -1;
@@ -21031,6 +22789,9 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct so
case SIP_MESSAGE:
res = handle_request_message(p, req);
break;
+ case SIP_PUBLISH:
+ res = handle_request_publish(p, req, sin, seqno, e);
+ break;
case SIP_SUBSCRIBE:
res = handle_request_subscribe(p, req, sin, seqno, e);
break;
@@ -22001,7 +23762,7 @@ static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp)
return;
}
p->stimer->st_interval = minse;
- transmit_invite(p, SIP_INVITE, 1, 2);
+ transmit_invite(p, SIP_INVITE, 1, 2, NULL);
}
@@ -22195,9 +23956,9 @@ static int sip_poke_peer(struct sip_peer *peer, int force)
ast_set_flag(&p->flags[0], SIP_OUTGOING);
#ifdef VOCAL_DATA_HACK
ast_copy_string(p->username, "__VOCAL_DATA_SHOULD_READ_THE_SIP_SPEC__", sizeof(p->username));
- xmitres = transmit_invite(p, SIP_INVITE, 0, 2); /* sinks the p refcount */
+ xmitres = transmit_invite(p, SIP_INVITE, 0, 2, NULL); /* sinks the p refcount */
#else
- xmitres = transmit_invite(p, SIP_OPTIONS, 0, 2); /* sinks the p refcount */
+ xmitres = transmit_invite(p, SIP_OPTIONS, 0, 2, NULL); /* sinks the p refcount */
#endif
peer->ps = ast_tvnow();
if (xmitres == XMIT_ERROR) {
@@ -22336,6 +24097,7 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c
char *md5secret = NULL;
char *authname = NULL;
char *trans = NULL;
+ char dialstring[256];
char *remote_address;
enum sip_transport transport = 0;
struct sockaddr_in remote_address_sin = { .sin_family = AF_INET };
@@ -22369,6 +24131,9 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c
p->outgoing_call = TRUE;
+ snprintf(dialstring, sizeof(dialstring), "%s/%s", type, dest);
+ ast_string_field_set(p, dialstring, dialstring);
+
if (!(p->options = ast_calloc(1, sizeof(*p->options)))) {
dialog_unlink_all(p, TRUE, TRUE);
dialog_unref(p, "unref dialog p from mem fail");
@@ -23059,6 +24824,11 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
return NULL;
}
+ if (!(peer->cc_params = ast_cc_config_params_init())) {
+ ao2_t_ref(peer, -1, "failed to allocate cc_params for peer");
+ return NULL;
+ }
+
if (realtime && !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
ast_atomic_fetchadd_int(&rpeerobjs, 1);
ast_debug(3, "-REALTIME- peer built. Name: %s. Peer objects: %d\n", name, rpeerobjs);
@@ -23442,9 +25212,16 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
if (peer->busy_level < 0) {
peer->busy_level = 0;
}
+ } else if (ast_cc_is_config_param(v->name)) {
+ ast_cc_set_param(peer->cc_params, v->name, v->value);
}
}
+ if (!can_parse_xml && (ast_get_cc_agent_policy(peer->cc_params) == AST_CC_AGENT_NATIVE)) {
+ ast_log(LOG_WARNING, "Peer %s has a cc_agent_policy of 'native' but required libxml2 dependency is not installed. Changing policy to 'never'\n", peer->name);
+ ast_set_cc_agent_policy(peer->cc_params, AST_CC_AGENT_NEVER);
+ }
+
/* Note that Timer B is dependent upon T1 and MUST NOT be lower
* than T1 * 64, according to RFC 3261, Section 17.1.1.2 */
if (peer->timer_b < peer->timer_t1 * 64) {
@@ -24961,6 +26738,15 @@ static int sip_sipredirect(struct sip_pvt *p, const char *dest)
return 0;
}
+static int sip_is_xml_parsable(void)
+{
+#ifdef HAVE_LIBXML2
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
/*! \brief Send a poke to all known peers */
static void sip_poke_all_peers(void)
{
@@ -25276,6 +27062,7 @@ static int load_module(void)
sip_reloadreason = CHANNEL_MODULE_LOAD;
+ can_parse_xml = sip_is_xml_parsable();
if(reload_config(sip_reloadreason)) /* Load the configuration from sip.conf */
return AST_MODULE_LOAD_DECLINE;
@@ -25323,6 +27110,24 @@ static int load_module(void)
sip_poke_all_peers();
sip_send_all_registers();
sip_send_all_mwi_subscriptions();
+ initialize_escs();
+ if (sip_epa_register(&cc_epa_static_data)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (can_parse_xml) {
+ /* SIP CC agents require the ability to parse XML PIDF bodies
+ * in incoming PUBLISH requests
+ */
+ if (ast_cc_agent_register(&sip_cc_agent_callbacks)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ }
+ if (ast_cc_monitor_register(&sip_cc_monitor_callbacks)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (!(sip_monitor_instances = ao2_container_alloc(37, sip_monitor_instance_hash_fn, sip_monitor_instance_cmp_fn))) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
/* And start the monitor for the first time */
restart_monitor();
@@ -25433,6 +27238,7 @@ static int unload_module(void)
clear_realm_authentication(authl);
+ destroy_escs();
if (default_tls_cfg.certfile)
ast_free(default_tls_cfg.certfile);
@@ -25464,6 +27270,8 @@ static int unload_module(void)
ast_context_destroy(con, "SIP");
ast_unload_realtime("sipregs");
ast_unload_realtime("sippeers");
+ ast_cc_monitor_unregister(&sip_cc_monitor_callbacks);
+ ast_cc_agent_unregister(&sip_cc_agent_callbacks);
sip_unregister_tests();
diff --git a/channels/sig_analog.c b/channels/sig_analog.c
index 8e4aa3391..f8b1ede45 100644
--- a/channels/sig_analog.c
+++ b/channels/sig_analog.c
@@ -169,6 +169,14 @@ static int analog_get_callerid(struct analog_pvt *p, char *name, char *number, e
return -1;
}
+static const char *analog_get_orig_dialstring(struct analog_pvt *p)
+{
+ if (p->calls->get_orig_dialstring) {
+ return p->calls->get_orig_dialstring(p->chan_pvt);
+ }
+ return "";
+}
+
static int analog_get_event(struct analog_pvt *p)
{
if (p->calls->get_event) {
@@ -934,6 +942,24 @@ int analog_call(struct analog_pvt *p, struct ast_channel *ast, char *rdest, int
ast_setstate(ast, AST_STATE_RINGING);
index = analog_get_index(ast, p, 0);
if (index > -1) {
+ struct ast_cc_config_params *cc_params;
+
+ /* This is where the initial ringing frame is queued for an analog call.
+ * As such, this is a great time to offer CCNR to the caller if it's available.
+ */
+ cc_params = ast_channel_get_cc_config_params(p->subs[index].owner);
+ if (cc_params) {
+ switch (ast_get_cc_monitor_policy(cc_params)) {
+ case AST_CC_MONITOR_NEVER:
+ break;
+ case AST_CC_MONITOR_NATIVE:
+ case AST_CC_MONITOR_ALWAYS:
+ case AST_CC_MONITOR_GENERIC:
+ ast_queue_cc_frame(p->subs[index].owner, AST_CC_GENERIC_MONITOR_TYPE,
+ analog_get_orig_dialstring(p), AST_CC_CCNR, NULL);
+ break;
+ }
+ }
ast_queue_control(p->subs[index].owner, AST_CONTROL_RINGING);
}
break;
diff --git a/channels/sig_analog.h b/channels/sig_analog.h
index 33b642289..57fc5c1f2 100644
--- a/channels/sig_analog.h
+++ b/channels/sig_analog.h
@@ -213,6 +213,8 @@ struct analog_callback {
void (* const cancel_cidspill)(void *pvt);
int (* const confmute)(void *pvt, int mute);
void (* const set_pulsedial)(void *pvt, int flag);
+
+ const char *(* const get_orig_dialstring)(void *pvt);
};
diff --git a/channels/sig_pri.c b/channels/sig_pri.c
index fd3b4111e..5749e89b3 100644
--- a/channels/sig_pri.c
+++ b/channels/sig_pri.c
@@ -55,11 +55,32 @@
/* define this to send PRI user-user information elements */
#undef SUPPORT_USERUSER
-#if 0
-#define DEFAULT_PRI_DEBUG (PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q921_DUMP | PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_STATE)
-#else
-#define DEFAULT_PRI_DEBUG 0
-#endif
+#if defined(HAVE_PRI_CCSS)
+struct sig_pri_cc_agent_prv {
+ /*! Asterisk span D channel control structure. */
+ struct sig_pri_pri *pri;
+ /*! CC id value to use with libpri. -1 if invalid. */
+ long cc_id;
+ /*! TRUE if CC has been requested and we are waiting for the response. */
+ unsigned char cc_request_response_pending;
+};
+
+struct sig_pri_cc_monitor_instance {
+ /*! \brief Asterisk span D channel control structure. */
+ struct sig_pri_pri *pri;
+ /*! CC id value to use with libpri. (-1 if already canceled). */
+ long cc_id;
+ /*! CC core id value. */
+ int core_id;
+ /*! Device name(Channel name less sequence number) */
+ char name[1];
+};
+
+/*! Upper level agent/monitor type name. */
+static const char *sig_pri_cc_type_name;
+/*! Container of sig_pri monitor instances. */
+static struct ao2_container *sig_pri_cc_monitors;
+#endif /* defined(HAVE_PRI_CCSS) */
static int pri_matchdigittimeout = 3000;
@@ -120,6 +141,45 @@ static void sig_pri_set_digital(struct sig_pri_chan *p, int flag)
p->calls->set_digital(p->chan_pvt, flag);
}
+static const char *sig_pri_get_orig_dialstring(struct sig_pri_chan *p)
+{
+ if (p->calls->get_orig_dialstring) {
+ return p->calls->get_orig_dialstring(p->chan_pvt);
+ }
+ ast_log(LOG_ERROR, "get_orig_dialstring callback not defined\n");
+ return "";
+}
+
+#if defined(HAVE_PRI_CCSS)
+static void sig_pri_make_cc_dialstring(struct sig_pri_chan *p, char *buf, size_t buf_size)
+{
+ if (p->calls->make_cc_dialstring) {
+ p->calls->make_cc_dialstring(p->chan_pvt, buf, buf_size);
+ } else {
+ ast_log(LOG_ERROR, "make_cc_dialstring callback not defined\n");
+ buf[0] = '\0';
+ }
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+/*!
+ * \internal
+ * \brief Reevaluate the PRI span device state.
+ * \since 1.8
+ *
+ * \param pri Asterisk D channel control structure.
+ *
+ * \return Nothing
+ *
+ * \note Assumes the pri->lock is already obtained.
+ */
+static void sig_pri_span_devstate_changed(struct sig_pri_pri *pri)
+{
+ if (pri->calls->update_span_devstate) {
+ pri->calls->update_span_devstate(pri);
+ }
+}
+
/*!
* \internal
* \brief Set the caller id information in the parent module.
@@ -733,6 +793,12 @@ static struct ast_channel *sig_pri_new_ast_channel(struct sig_pri_chan *p, int s
pbx_builtin_setvar_helper(c, "TRANSFERCAPABILITY", ast_transfercapability2str(transfercapability));
sig_pri_set_digital(p, 1);
}
+ if (p->pri && !pri_grab(p, p->pri)) {
+ sig_pri_span_devstate_changed(p->pri);
+ pri_rel(p->pri);
+ } else {
+ ast_log(LOG_WARNING, "Failed to grab PRI!\n");
+ }
return c;
}
@@ -1476,6 +1542,615 @@ static void sig_pri_lock_owner(struct sig_pri_pri *pri, int chanpos)
}
}
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Compare the CC agent private data by libpri cc_id.
+ * \since 1.8
+ *
+ * \param obj pointer to the (user-defined part) of an object.
+ * \param arg callback argument from ao2_callback()
+ * \param flags flags from ao2_callback()
+ *
+ * \return values are a combination of enum _cb_results.
+ */
+static int sig_pri_cc_agent_cmp_cc_id(void *obj, void *arg, int flags)
+{
+ struct ast_cc_agent *agent_1 = obj;
+ struct sig_pri_cc_agent_prv *agent_prv_1 = agent_1->private_data;
+ struct sig_pri_cc_agent_prv *agent_prv_2 = arg;
+
+ return (agent_prv_1 && agent_prv_1->pri == agent_prv_2->pri
+ && agent_prv_1->cc_id == agent_prv_2->cc_id) ? CMP_MATCH | CMP_STOP : 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Find the CC agent by libpri cc_id.
+ * \since 1.8
+ *
+ * \param pri sig_pri PRI control structure.
+ * \param cc_id CC record ID to find.
+ *
+ * \note
+ * Since agents are refcounted, and this function returns
+ * a reference to the agent, it is imperative that you decrement
+ * the refcount of the agent once you have finished using it.
+ *
+ * \retval agent on success.
+ * \retval NULL not found.
+ */
+static struct ast_cc_agent *sig_pri_find_cc_agent_by_cc_id(struct sig_pri_pri *pri, long cc_id)
+{
+ struct sig_pri_cc_agent_prv finder = {
+ .pri = pri,
+ .cc_id = cc_id,
+ };
+
+ return ast_cc_agent_callback(0, sig_pri_cc_agent_cmp_cc_id, &finder,
+ sig_pri_cc_type_name);
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Compare the CC monitor instance by libpri cc_id.
+ * \since 1.8
+ *
+ * \param obj pointer to the (user-defined part) of an object.
+ * \param arg callback argument from ao2_callback()
+ * \param flags flags from ao2_callback()
+ *
+ * \return values are a combination of enum _cb_results.
+ */
+static int sig_pri_cc_monitor_cmp_cc_id(void *obj, void *arg, int flags)
+{
+ struct sig_pri_cc_monitor_instance *monitor_1 = obj;
+ struct sig_pri_cc_monitor_instance *monitor_2 = arg;
+
+ return (monitor_1->pri == monitor_2->pri
+ && monitor_1->cc_id == monitor_2->cc_id) ? CMP_MATCH | CMP_STOP : 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Find the CC monitor instance by libpri cc_id.
+ * \since 1.8
+ *
+ * \param pri sig_pri PRI control structure.
+ * \param cc_id CC record ID to find.
+ *
+ * \note
+ * Since monitor_instances are refcounted, and this function returns
+ * a reference to the instance, it is imperative that you decrement
+ * the refcount of the instance once you have finished using it.
+ *
+ * \retval monitor_instance on success.
+ * \retval NULL not found.
+ */
+static struct sig_pri_cc_monitor_instance *sig_pri_find_cc_monitor_by_cc_id(struct sig_pri_pri *pri, long cc_id)
+{
+ struct sig_pri_cc_monitor_instance finder = {
+ .pri = pri,
+ .cc_id = cc_id,
+ };
+
+ return ao2_callback(sig_pri_cc_monitors, 0, sig_pri_cc_monitor_cmp_cc_id, &finder);
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Destroy the given monitor instance.
+ * \since 1.8
+ *
+ * \param data Monitor instance to destroy.
+ *
+ * \return Nothing
+ */
+static void sig_pri_cc_monitor_instance_destroy(void *data)
+{
+ struct sig_pri_cc_monitor_instance *monitor_instance = data;
+
+ if (monitor_instance->cc_id != -1) {
+ ast_mutex_lock(&monitor_instance->pri->lock);
+ pri_cc_cancel(monitor_instance->pri->pri, monitor_instance->cc_id);
+ ast_mutex_unlock(&monitor_instance->pri->lock);
+ }
+ monitor_instance->pri->calls->module_unref();
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Construct a new monitor instance.
+ * \since 1.8
+ *
+ * \param core_id CC core ID.
+ * \param pri sig_pri PRI control structure.
+ * \param cc_id CC record ID.
+ * \param device_name Name of device (Asterisk channel name less sequence number).
+ *
+ * \note
+ * Since monitor_instances are refcounted, and this function returns
+ * a reference to the instance, it is imperative that you decrement
+ * the refcount of the instance once you have finished using it.
+ *
+ * \retval monitor_instance on success.
+ * \retval NULL on error.
+ */
+static struct sig_pri_cc_monitor_instance *sig_pri_cc_monitor_instance_init(int core_id, struct sig_pri_pri *pri, long cc_id, const char *device_name)
+{
+ struct sig_pri_cc_monitor_instance *monitor_instance;
+
+ if (!pri->calls->module_ref || !pri->calls->module_unref) {
+ return NULL;
+ }
+
+ monitor_instance = ao2_alloc(sizeof(*monitor_instance) + strlen(device_name),
+ sig_pri_cc_monitor_instance_destroy);
+ if (!monitor_instance) {
+ return NULL;
+ }
+
+ monitor_instance->cc_id = cc_id;
+ monitor_instance->pri = pri;
+ monitor_instance->core_id = core_id;
+ strcpy(monitor_instance->name, device_name);
+
+ pri->calls->module_ref();
+
+ ao2_link(sig_pri_cc_monitors, monitor_instance);
+ return monitor_instance;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Announce to the CC core that protocol CC monitor is available for this call.
+ * \since 1.8
+ *
+ * \param pri sig_pri PRI control structure.
+ * \param chanpos Channel position in the span.
+ * \param cc_id CC record ID.
+ * \param service CCBS/CCNR indication.
+ *
+ * \note Assumes the pri->lock is already obtained.
+ * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained.
+ * \note Assumes the sig_pri_lock_owner(pri, chanpos) is already obtained.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int sig_pri_cc_available(struct sig_pri_pri *pri, int chanpos, long cc_id, enum ast_cc_service_type service)
+{
+ struct sig_pri_chan *pvt;
+ struct ast_cc_config_params *cc_params;
+ struct sig_pri_cc_monitor_instance *monitor;
+ enum ast_cc_monitor_policies monitor_policy;
+ int core_id;
+ int res;
+ char device_name[AST_CHANNEL_NAME];
+ char dialstring[AST_CHANNEL_NAME];
+
+ pvt = pri->pvts[chanpos];
+
+ core_id = ast_cc_get_current_core_id(pvt->owner);
+ if (core_id == -1) {
+ return -1;
+ }
+
+ cc_params = ast_channel_get_cc_config_params(pvt->owner);
+ if (!cc_params) {
+ return -1;
+ }
+
+ res = -1;
+ monitor_policy = ast_get_cc_monitor_policy(cc_params);
+ switch (monitor_policy) {
+ case AST_CC_MONITOR_NEVER:
+ /* CCSS is not enabled. */
+ break;
+ case AST_CC_MONITOR_NATIVE:
+ case AST_CC_MONITOR_ALWAYS:
+ /*
+ * If it is AST_CC_MONITOR_ALWAYS and native fails we will attempt the fallback
+ * later in the call to sig_pri_cc_generic_check().
+ */
+ ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name));
+ sig_pri_make_cc_dialstring(pvt, dialstring, sizeof(dialstring));
+ monitor = sig_pri_cc_monitor_instance_init(core_id, pri, cc_id, device_name);
+ if (!monitor) {
+ break;
+ }
+ res = ast_queue_cc_frame(pvt->owner, sig_pri_cc_type_name, dialstring, service,
+ monitor);
+ if (res) {
+ monitor->cc_id = -1;
+ ao2_unlink(sig_pri_cc_monitors, monitor);
+ ao2_ref(monitor, -1);
+ }
+ break;
+ case AST_CC_MONITOR_GENERIC:
+ ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE,
+ sig_pri_get_orig_dialstring(pvt), service, NULL);
+ /* Say it failed to force caller to cancel native CC. */
+ break;
+ }
+ return res;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+/*!
+ * \internal
+ * \brief Check if generic CC monitor is needed and request it.
+ * \since 1.8
+ *
+ * \param pri sig_pri PRI control structure.
+ * \param chanpos Channel position in the span.
+ * \param service CCBS/CCNR indication.
+ *
+ * \note Assumes the pri->lock is already obtained.
+ * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained.
+ *
+ * \return Nothing
+ */
+static void sig_pri_cc_generic_check(struct sig_pri_pri *pri, int chanpos, enum ast_cc_service_type service)
+{
+ struct ast_channel *owner;
+ struct ast_cc_config_params *cc_params;
+#if defined(HAVE_PRI_CCSS)
+ struct ast_cc_monitor *monitor;
+ char device_name[AST_CHANNEL_NAME];
+#endif /* defined(HAVE_PRI_CCSS) */
+ enum ast_cc_monitor_policies monitor_policy;
+ int core_id;
+
+ if (!pri->pvts[chanpos]->outgoing) {
+ /* This is not an outgoing call so it cannot be CC monitor. */
+ return;
+ }
+
+ sig_pri_lock_owner(pri, chanpos);
+ owner = pri->pvts[chanpos]->owner;
+ if (!owner) {
+ return;
+ }
+ core_id = ast_cc_get_current_core_id(owner);
+ if (core_id == -1) {
+ /* No CC core setup */
+ goto done;
+ }
+
+ cc_params = ast_channel_get_cc_config_params(owner);
+ if (!cc_params) {
+ /* Could not get CC config parameters. */
+ goto done;
+ }
+
+#if defined(HAVE_PRI_CCSS)
+ ast_channel_get_device_name(owner, device_name, sizeof(device_name));
+ monitor = ast_cc_get_monitor_by_recall_core_id(core_id, device_name);
+ if (monitor) {
+ /* CC monitor is already present so no need for generic CC. */
+ ao2_ref(monitor, -1);
+ goto done;
+ }
+#endif /* defined(HAVE_PRI_CCSS) */
+
+ monitor_policy = ast_get_cc_monitor_policy(cc_params);
+ switch (monitor_policy) {
+ case AST_CC_MONITOR_NEVER:
+ /* CCSS is not enabled. */
+ break;
+ case AST_CC_MONITOR_NATIVE:
+ if (pri->sig == SIG_BRI_PTMP && pri->nodetype == PRI_NETWORK) {
+ /* Request generic CC monitor. */
+ ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE,
+ sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL);
+ }
+ break;
+ case AST_CC_MONITOR_ALWAYS:
+ if (pri->sig == SIG_BRI_PTMP && pri->nodetype != PRI_NETWORK) {
+ /*
+ * Cannot monitor PTMP TE side since this is not defined.
+ * We are playing the roll of a phone in this case and
+ * a phone cannot monitor a party over the network without
+ * protocol help.
+ */
+ break;
+ }
+ /*
+ * We are either falling back or this is a PTMP NT span.
+ * Request generic CC monitor.
+ */
+ ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE,
+ sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL);
+ break;
+ case AST_CC_MONITOR_GENERIC:
+ if (pri->sig == SIG_BRI_PTMP && pri->nodetype == PRI_NETWORK) {
+ /* Request generic CC monitor. */
+ ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE,
+ sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL);
+ }
+ break;
+ }
+
+done:
+ ast_channel_unlock(owner);
+}
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief The CC link canceled the CC instance.
+ * \since 1.8
+ *
+ * \param pri sig_pri PRI control structure.
+ * \param cc_id CC record ID.
+ * \param is_agent TRUE if the cc_id is for an agent.
+ *
+ * \return Nothing
+ */
+static void sig_pri_cc_link_canceled(struct sig_pri_pri *pri, long cc_id, int is_agent)
+{
+ if (is_agent) {
+ struct ast_cc_agent *agent;
+
+ agent = sig_pri_find_cc_agent_by_cc_id(pri, cc_id);
+ if (!agent) {
+ return;
+ }
+ ast_cc_failed(agent->core_id, "%s agent got canceled by link",
+ sig_pri_cc_type_name);
+ ao2_ref(agent, -1);
+ } else {
+ struct sig_pri_cc_monitor_instance *monitor;
+
+ monitor = sig_pri_find_cc_monitor_by_cc_id(pri, cc_id);
+ if (!monitor) {
+ return;
+ }
+ monitor->cc_id = -1;
+ ast_cc_monitor_failed(monitor->core_id, monitor->name,
+ "%s monitor got canceled by link", sig_pri_cc_type_name);
+ ao2_ref(monitor, -1);
+ }
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+/*!
+ * \internal
+ * \brief TRUE if PRI event came in on a CIS call.
+ * \since 1.8
+ *
+ * \param channel PRI encoded span/channel
+ *
+ * \retval non-zero if CIS call.
+ */
+static int sig_pri_is_cis_call(int channel)
+{
+ return channel != -1 && (channel & PRI_CIS_CALL);
+}
+
+/*!
+ * \internal
+ * \brief Handle the CIS associated PRI subcommand events.
+ * \since 1.8
+ *
+ * \param pri sig_pri PRI control structure.
+ * \param event_id PRI event id
+ * \param subcmds Subcommands to process if any. (Could be NULL).
+ * \param call_rsp libpri opaque call structure to send any responses toward.
+ * Could be NULL either because it is not available or the call is for the
+ * dummy call reference. However, this should not be NULL in the cases that
+ * need to use the pointer to send a response message back.
+ *
+ * \note Assumes the pri->lock is already obtained.
+ *
+ * \return Nothing
+ */
+static void sig_pri_handle_cis_subcmds(struct sig_pri_pri *pri, int event_id,
+ const struct pri_subcommands *subcmds, q931_call *call_rsp)
+{
+ int index;
+#if defined(HAVE_PRI_CCSS)
+ struct ast_cc_agent *agent;
+ struct sig_pri_cc_agent_prv *agent_prv;
+ struct sig_pri_cc_monitor_instance *monitor;
+#endif /* defined(HAVE_PRI_CCSS) */
+
+ if (!subcmds) {
+ return;
+ }
+ for (index = 0; index < subcmds->counter_subcmd; ++index) {
+ const struct pri_subcommand *subcmd = &subcmds->subcmd[index];
+
+ switch (subcmd->cmd) {
+#if defined(STATUS_REQUEST_PLACE_HOLDER)
+ case PRI_SUBCMD_STATUS_REQ:
+ case PRI_SUBCMD_STATUS_REQ_RSP:
+ /* Ignore for now. */
+ break;
+#endif /* defined(STATUS_REQUEST_PLACE_HOLDER) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_REQ:
+ agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_request.cc_id);
+ if (!agent) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id);
+ break;
+ }
+ if (!ast_cc_request_is_within_limits()) {
+ if (pri_cc_req_rsp(pri->pri, subcmd->u.cc_request.cc_id,
+ 5/* queue_full */)) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id);
+ }
+ ast_cc_failed(agent->core_id, "%s agent system CC queue full",
+ sig_pri_cc_type_name);
+ ao2_ref(agent, -1);
+ break;
+ }
+ agent_prv = agent->private_data;
+ agent_prv->cc_request_response_pending = 1;
+ if (ast_cc_agent_accept_request(agent->core_id,
+ "%s caller accepted CC offer.", sig_pri_cc_type_name)) {
+ agent_prv->cc_request_response_pending = 0;
+ if (pri_cc_req_rsp(pri->pri, subcmd->u.cc_request.cc_id,
+ 2/* short_term_denial */)) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id);
+ }
+ ast_cc_failed(agent->core_id, "%s agent CC core request accept failed",
+ sig_pri_cc_type_name);
+ }
+ ao2_ref(agent, -1);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_REQ_RSP:
+ monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
+ subcmd->u.cc_request_rsp.cc_id);
+ if (!monitor) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_request_rsp.cc_id);
+ break;
+ }
+ switch (subcmd->u.cc_request_rsp.status) {
+ case 0:/* success */
+ ast_cc_monitor_request_acked(monitor->core_id,
+ "%s far end accepted CC request", sig_pri_cc_type_name);
+ break;
+ case 1:/* timeout */
+ ast_verb(2, "core_id:%d %s CC request timeout\n", monitor->core_id,
+ sig_pri_cc_type_name);
+ ast_cc_monitor_failed(monitor->core_id, monitor->name,
+ "%s CC request timeout", sig_pri_cc_type_name);
+ break;
+ case 2:/* error */
+ ast_verb(2, "core_id:%d %s CC request error: %s\n", monitor->core_id,
+ sig_pri_cc_type_name,
+ pri_facility_error2str(subcmd->u.cc_request_rsp.fail_code));
+ ast_cc_monitor_failed(monitor->core_id, monitor->name,
+ "%s CC request error", sig_pri_cc_type_name);
+ break;
+ case 3:/* reject */
+ ast_verb(2, "core_id:%d %s CC request reject: %s\n", monitor->core_id,
+ sig_pri_cc_type_name,
+ pri_facility_reject2str(subcmd->u.cc_request_rsp.fail_code));
+ ast_cc_monitor_failed(monitor->core_id, monitor->name,
+ "%s CC request reject", sig_pri_cc_type_name);
+ break;
+ default:
+ ast_verb(2, "core_id:%d %s CC request unknown status %d\n",
+ monitor->core_id, sig_pri_cc_type_name,
+ subcmd->u.cc_request_rsp.status);
+ ast_cc_monitor_failed(monitor->core_id, monitor->name,
+ "%s CC request unknown status", sig_pri_cc_type_name);
+ break;
+ }
+ ao2_ref(monitor, -1);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_REMOTE_USER_FREE:
+ monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
+ subcmd->u.cc_remote_user_free.cc_id);
+ if (!monitor) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_remote_user_free.cc_id);
+ break;
+ }
+ ast_cc_monitor_callee_available(monitor->core_id,
+ "%s callee has become available", sig_pri_cc_type_name);
+ ao2_ref(monitor, -1);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_B_FREE:
+ monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
+ subcmd->u.cc_b_free.cc_id);
+ if (!monitor) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_b_free.cc_id);
+ break;
+ }
+ ast_cc_monitor_party_b_free(monitor->core_id);
+ ao2_ref(monitor, -1);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_STATUS_REQ:
+ monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
+ subcmd->u.cc_status_req.cc_id);
+ if (!monitor) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_status_req.cc_id);
+ break;
+ }
+ ast_cc_monitor_status_request(monitor->core_id);
+ ao2_ref(monitor, -1);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_STATUS_REQ_RSP:
+ agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_status_req_rsp.cc_id);
+ if (!agent) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_status_req_rsp.cc_id);
+ break;
+ }
+ ast_cc_agent_status_response(agent->core_id,
+ subcmd->u.cc_status_req_rsp.status ? AST_DEVICE_INUSE
+ : AST_DEVICE_NOT_INUSE);
+ ao2_ref(agent, -1);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_STATUS:
+ agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_status.cc_id);
+ if (!agent) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_status.cc_id);
+ break;
+ }
+ if (subcmd->u.cc_status.status) {
+ ast_cc_agent_caller_busy(agent->core_id, "%s agent caller is busy",
+ sig_pri_cc_type_name);
+ } else {
+ ast_cc_agent_caller_available(agent->core_id,
+ "%s agent caller is available", sig_pri_cc_type_name);
+ }
+ ao2_ref(agent, -1);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_CANCEL:
+ sig_pri_cc_link_canceled(pri, subcmd->u.cc_cancel.cc_id,
+ subcmd->u.cc_cancel.is_agent);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_STOP_ALERTING:
+ monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
+ subcmd->u.cc_stop_alerting.cc_id);
+ if (!monitor) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_stop_alerting.cc_id);
+ break;
+ }
+ ast_cc_monitor_stop_ringing(monitor->core_id);
+ ao2_ref(monitor, -1);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+ default:
+ ast_debug(2,
+ "Unknown CIS subcommand(%d) in %s event on span %d.\n",
+ subcmd->cmd, pri_event2str(event_id), pri->span);
+ break;
+ }
+ }
+}
+
/*!
* \internal
* \brief Handle the call associated PRI subcommand events.
@@ -1647,6 +2322,63 @@ static void sig_pri_handle_subcmds(struct sig_pri_pri *pri, int chanpos, int eve
}
break;
#endif /* defined(HAVE_PRI_CALL_REROUTING) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_AVAILABLE:
+ sig_pri_lock_owner(pri, chanpos);
+ owner = pri->pvts[chanpos]->owner;
+ if (owner) {
+ enum ast_cc_service_type service;
+
+ switch (event_id) {
+ case PRI_EVENT_RINGING:
+ service = AST_CC_CCNR;
+ break;
+ case PRI_EVENT_HANGUP_REQ:
+ /* We will assume that the cause was busy/congestion. */
+ service = AST_CC_CCBS;
+ break;
+ default:
+ service = AST_CC_NONE;
+ break;
+ }
+ if (service == AST_CC_NONE
+ || sig_pri_cc_available(pri, chanpos, subcmd->u.cc_available.cc_id,
+ service)) {
+ pri_cc_cancel(pri->pri, subcmd->u.cc_available.cc_id);
+ }
+ ast_channel_unlock(owner);
+ } else {
+ /* No asterisk channel. */
+ pri_cc_cancel(pri->pri, subcmd->u.cc_available.cc_id);
+ }
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_CALL:
+ sig_pri_lock_owner(pri, chanpos);
+ owner = pri->pvts[chanpos]->owner;
+ if (owner) {
+ struct ast_cc_agent *agent;
+
+ agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_call.cc_id);
+ if (agent) {
+ ast_setup_cc_recall_datastore(owner, agent->core_id);
+ ast_cc_agent_set_interfaces_chanvar(owner);
+ ast_cc_agent_recalling(agent->core_id,
+ "%s caller is attempting recall", sig_pri_cc_type_name);
+ ao2_ref(agent, -1);
+ }
+
+ ast_channel_unlock(owner);
+ }
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
+#if defined(HAVE_PRI_CCSS)
+ case PRI_SUBCMD_CC_CANCEL:
+ sig_pri_cc_link_canceled(pri, subcmd->u.cc_cancel.cc_id,
+ subcmd->u.cc_cancel.is_agent);
+ break;
+#endif /* defined(HAVE_PRI_CCSS) */
default:
ast_debug(2,
"Unknown call subcommand(%d) in %s event on channel %d/%d on span %d.\n",
@@ -1793,6 +2525,7 @@ static int sig_pri_handle_hold(struct sig_pri_pri *pri, pri_event *ev)
f.subclass.integer = AST_CONTROL_HOLD;
ast_queue_frame(owner, &f);
+ sig_pri_span_devstate_changed(pri);
retval = 0;
}
@@ -1866,6 +2599,7 @@ static void sig_pri_handle_retrieve(struct sig_pri_pri *pri, pri_event *ev)
pri_queue_frame(pri->pvts[chanpos], &f, pri);
}
sig_pri_unlock_private(pri->pvts[chanpos]);
+ sig_pri_span_devstate_changed(pri);
pri_retrieve_ack(pri->pri, ev->retrieve.call,
PVT_TO_CHANNEL(pri->pvts[chanpos]));
}
@@ -2094,10 +2828,12 @@ static void *pri_dchannel(void *vpri)
}
pri->resetting = 0;
/* Take the channels from inalarm condition */
- for (i = 0; i < pri->numchans; i++)
+ for (i = 0; i < pri->numchans; i++) {
if (pri->pvts[i]) {
pri->pvts[i]->inalarm = 0;
}
+ }
+ sig_pri_span_devstate_changed(pri);
break;
case PRI_EVENT_DCHAN_DOWN:
pri_find_dchan(pri);
@@ -2128,6 +2864,7 @@ static void *pri_dchannel(void *vpri)
p->inalarm = 1;
}
}
+ sig_pri_span_devstate_changed(pri);
}
break;
case PRI_EVENT_RESTART:
@@ -2180,6 +2917,11 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_KEYPAD_DIGIT:
+ if (sig_pri_is_cis_call(e->digit.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->digit.subcmds,
+ e->digit.call);
+ break;
+ }
chanpos = pri_find_principle(pri, e->digit.channel, e->digit.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "KEYPAD_DIGITs received on unconfigured channel %d/%d span %d\n",
@@ -2210,6 +2952,11 @@ static void *pri_dchannel(void *vpri)
break;
case PRI_EVENT_INFO_RECEIVED:
+ if (sig_pri_is_cis_call(e->ring.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->ring.subcmds,
+ e->ring.call);
+ break;
+ }
chanpos = pri_find_principle(pri, e->ring.channel, e->ring.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "INFO received on unconfigured channel %d/%d span %d\n",
@@ -2262,6 +3009,8 @@ static void *pri_dchannel(void *vpri)
snprintf(db_answer, sizeof(db_answer), "%s:%u",
SRVST_TYPE_OOS, *why);
ast_db_put(db_chan_name, SRVST_DBKEY, db_answer);
+ } else {
+ sig_pri_span_devstate_changed(pri);
}
break;
case 2: /* out-of-service */
@@ -2271,6 +3020,7 @@ static void *pri_dchannel(void *vpri)
snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS,
*why);
ast_db_put(db_chan_name, SRVST_DBKEY, db_answer);
+ sig_pri_span_devstate_changed(pri);
break;
default:
ast_log(LOG_ERROR, "Huh? changestatus is: %d\n", e->service.changestatus);
@@ -2301,7 +3051,12 @@ static void *pri_dchannel(void *vpri)
pri_destroycall(pri->pri, e->ring.call);
break;
}
- if (e->ring.channel == -1)
+ if (sig_pri_is_cis_call(e->ring.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->ring.subcmds,
+ e->ring.call);
+ break;
+ }
+ if (e->ring.channel == -1 || PRI_CHANNEL(e->ring.channel) == 0xFF)
chanpos = pri_find_empty_chan(pri, 1);
else
chanpos = pri_find_principle(pri, e->ring.channel, e->ring.call);
@@ -2644,6 +3399,11 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_RINGING:
+ if (sig_pri_is_cis_call(e->ringing.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->ringing.subcmds,
+ e->ringing.call);
+ break;
+ }
chanpos = pri_find_principle(pri, e->ringing.channel, e->ringing.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "Ringing requested on unconfigured channel %d/%d span %d\n",
@@ -2658,6 +3418,7 @@ static void *pri_dchannel(void *vpri)
sig_pri_handle_subcmds(pri, chanpos, e->e, e->ringing.channel,
e->ringing.subcmds, e->ringing.call);
+ sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCNR);
sig_pri_set_echocanceller(pri->pvts[chanpos], 1);
pri_queue_control(pri->pvts[chanpos], AST_CONTROL_RINGING, pri);
pri->pvts[chanpos]->alerting = 1;
@@ -2681,7 +3442,11 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_PROGRESS:
- /* Get chan value if e->e is not PRI_EVNT_RINGING */
+ if (sig_pri_is_cis_call(e->proceeding.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->proceeding.subcmds,
+ e->proceeding.call);
+ break;
+ }
chanpos = pri_find_principle(pri, e->proceeding.channel, e->proceeding.call);
if (chanpos > -1) {
sig_pri_lock_private(pri->pvts[chanpos]);
@@ -2731,6 +3496,11 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_PROCEEDING:
+ if (sig_pri_is_cis_call(e->proceeding.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->proceeding.subcmds,
+ e->proceeding.call);
+ break;
+ }
chanpos = pri_find_principle(pri, e->proceeding.channel, e->proceeding.call);
if (chanpos > -1) {
sig_pri_lock_private(pri->pvts[chanpos]);
@@ -2760,6 +3530,17 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_FACILITY:
+ if (!e->facility.call || sig_pri_is_cis_call(e->facility.channel)) {
+ /* Event came in on the dummy channel or a CIS call. */
+#if defined(HAVE_PRI_CALL_REROUTING)
+ sig_pri_handle_cis_subcmds(pri, e->e, e->facility.subcmds,
+ e->facility.subcall);
+#else
+ sig_pri_handle_cis_subcmds(pri, e->e, e->facility.subcmds,
+ e->facility.call);
+#endif /* !defined(HAVE_PRI_CALL_REROUTING) */
+ break;
+ }
chanpos = pri_find_principle(pri, e->facility.channel, e->facility.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "Facility requested on unconfigured channel %d/%d span %d\n",
@@ -2783,6 +3564,11 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_ANSWER:
+ if (sig_pri_is_cis_call(e->answer.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->answer.subcmds,
+ e->answer.call);
+ break;
+ }
chanpos = pri_find_principle(pri, e->answer.channel, e->answer.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "Answer on unconfigured channel %d/%d span %d\n",
@@ -2821,6 +3607,12 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_HANGUP:
+ if (sig_pri_is_cis_call(e->hangup.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds,
+ e->hangup.call);
+ pri_hangup(pri->pri, e->hangup.call, e->hangup.cause);
+ break;
+ }
chanpos = pri_find_principle(pri, e->hangup.channel, e->hangup.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "Hangup requested on unconfigured channel %d/%d span %d\n",
@@ -2834,6 +3626,14 @@ static void *pri_dchannel(void *vpri)
if (!pri->pvts[chanpos]->alreadyhungup) {
/* we're calling here dahdi_hangup so once we get there we need to clear p->call after calling pri_hangup */
pri->pvts[chanpos]->alreadyhungup = 1;
+ switch (e->hangup.cause) {
+ case PRI_CAUSE_USER_BUSY:
+ case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION:
+ sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCBS);
+ break;
+ default:
+ break;
+ }
if (pri->pvts[chanpos]->owner) {
/* Queue a BUSY instead of a hangup if our cause is appropriate */
pri->pvts[chanpos]->owner->hangupcause = e->hangup.cause;
@@ -2900,6 +3700,12 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_HANGUP_REQ:
+ if (sig_pri_is_cis_call(e->hangup.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds,
+ e->hangup.call);
+ pri_hangup(pri->pri, e->hangup.call, e->hangup.cause);
+ break;
+ }
chanpos = pri_find_principle(pri, e->hangup.channel, e->hangup.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "Hangup REQ requested on unconfigured channel %d/%d span %d\n",
@@ -2922,6 +3728,14 @@ static void *pri_dchannel(void *vpri)
sig_pri_lock_private(pri->pvts[chanpos]);
}
#endif /* defined(HAVE_PRI_CALL_HOLD) */
+ switch (e->hangup.cause) {
+ case PRI_CAUSE_USER_BUSY:
+ case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION:
+ sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCBS);
+ break;
+ default:
+ break;
+ }
if (pri->pvts[chanpos]->owner) {
pri->pvts[chanpos]->owner->hangupcause = e->hangup.cause;
switch (pri->pvts[chanpos]->owner->_state) {
@@ -2984,6 +3798,11 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_HANGUP_ACK:
+ if (sig_pri_is_cis_call(e->hangup.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds,
+ e->hangup.call);
+ break;
+ }
chanpos = pri_find_principle(pri, e->hangup.channel, e->hangup.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "Hangup ACK requested on unconfigured channel number %d/%d span %d\n",
@@ -3067,6 +3886,11 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_SETUP_ACK:
+ if (sig_pri_is_cis_call(e->setup_ack.channel)) {
+ sig_pri_handle_cis_subcmds(pri, e->e, e->setup_ack.subcmds,
+ e->setup_ack.call);
+ break;
+ }
chanpos = pri_find_principle(pri, e->setup_ack.channel, e->setup_ack.call);
if (chanpos < 0) {
ast_log(LOG_WARNING, "Received SETUP_ACKNOWLEDGE on unconfigured channel %d/%d span %d\n",
@@ -3090,6 +3914,15 @@ static void *pri_dchannel(void *vpri)
}
break;
case PRI_EVENT_NOTIFY:
+ if (sig_pri_is_cis_call(e->notify.channel)) {
+#if defined(HAVE_PRI_CALL_HOLD)
+ sig_pri_handle_cis_subcmds(pri, e->e, e->notify.subcmds,
+ e->notify.call);
+#else
+ sig_pri_handle_cis_subcmds(pri, e->e, e->notify.subcmds, NULL);
+#endif /* !defined(HAVE_PRI_CALL_HOLD) */
+ break;
+ }
#if defined(HAVE_PRI_CALL_HOLD)
chanpos = pri_find_principle(pri, e->notify.channel, e->notify.call);
#else
@@ -3130,6 +3963,7 @@ static void *pri_dchannel(void *vpri)
break;
#if defined(HAVE_PRI_CALL_HOLD)
case PRI_EVENT_HOLD:
+ /* We should not be getting any CIS calls with this message type. */
if (sig_pri_handle_hold(pri, e)) {
pri_hold_rej(pri->pri, e->hold.call,
PRI_CAUSE_RESOURCE_UNAVAIL_UNSPECIFIED);
@@ -3150,6 +3984,7 @@ static void *pri_dchannel(void *vpri)
#endif /* defined(HAVE_PRI_CALL_HOLD) */
#if defined(HAVE_PRI_CALL_HOLD)
case PRI_EVENT_RETRIEVE:
+ /* We should not be getting any CIS calls with this message type. */
sig_pri_handle_retrieve(pri, e);
break;
#endif /* defined(HAVE_PRI_CALL_HOLD) */
@@ -3189,7 +4024,7 @@ void sig_pri_init_pri(struct sig_pri_pri *pri)
int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast)
{
- int res = 0;
+ int res;
#ifdef SUPPORT_USERUSER
const char *useruser = pbx_builtin_getvar_helper(ast, "USERUSERINFO");
#endif
@@ -3213,47 +4048,43 @@ int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast)
p->exten[0] = '\0';
sig_pri_set_dialing(p, 0);
- if (!p->call) {
- res = 0;
- goto exit;
- }
-
/* Make sure we have a call (or REALLY have a call in the case of a PRI) */
if (!pri_grab(p, p->pri)) {
- if (p->alreadyhungup) {
- ast_log(LOG_DEBUG, "Already hungup... Calling hangup once, and clearing call\n");
+ if (p->call) {
+ if (p->alreadyhungup) {
+ ast_log(LOG_DEBUG, "Already hungup... Calling hangup once, and clearing call\n");
#ifdef SUPPORT_USERUSER
- pri_call_set_useruser(p->call, useruser);
+ pri_call_set_useruser(p->call, useruser);
#endif
- pri_hangup(p->pri->pri, p->call, -1);
- p->call = NULL;
- } else {
- const char *cause = pbx_builtin_getvar_helper(ast,"PRI_CAUSE");
- int icause = ast->hangupcause ? ast->hangupcause : -1;
- ast_log(LOG_DEBUG, "Not yet hungup... Calling hangup once with icause, and clearing call\n");
+ pri_hangup(p->pri->pri, p->call, -1);
+ p->call = NULL;
+ } else {
+ const char *cause = pbx_builtin_getvar_helper(ast,"PRI_CAUSE");
+ int icause = ast->hangupcause ? ast->hangupcause : -1;
+ ast_log(LOG_DEBUG, "Not yet hungup... Calling hangup once with icause, and clearing call\n");
#ifdef SUPPORT_USERUSER
- pri_call_set_useruser(p->call, useruser);
+ pri_call_set_useruser(p->call, useruser);
#endif
- p->alreadyhungup = 1;
- if (cause) {
- if (atoi(cause))
- icause = atoi(cause);
+ p->alreadyhungup = 1;
+ if (cause) {
+ if (atoi(cause))
+ icause = atoi(cause);
+ }
+ pri_hangup(p->pri->pri, p->call, icause);
}
- pri_hangup(p->pri->pri, p->call, icause);
}
- if (res < 0)
- ast_log(LOG_WARNING, "pri_disconnect failed\n");
+ sig_pri_span_devstate_changed(p->pri);
pri_rel(p->pri);
+ res = 0;
} else {
ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->pri->span);
res = -1;
}
-exit:
ast->tech_pvt = NULL;
return res;
}
@@ -3356,6 +4187,7 @@ int sig_pri_call(struct sig_pri_chan *p, struct ast_channel *ast, char *rdest, i
#ifdef SUPPORT_USERUSER
const char *useruser;
#endif
+ int core_id;
int pridialplan;
int dp_strip;
int prilocaldialplan;
@@ -3672,7 +4504,41 @@ int sig_pri_call(struct sig_pri_chan *p, struct ast_channel *ast, char *rdest, i
pri_sr_set_useruser(sr, useruser);
#endif
- if (pri_setup(p->pri->pri, p->call, sr)) {
+#if defined(HAVE_PRI_CCSS)
+ if (ast_cc_is_recall(ast, &core_id, sig_pri_cc_type_name)) {
+ struct ast_cc_monitor *monitor;
+ char device_name[AST_CHANNEL_NAME];
+
+ /* This is a CC recall call. */
+ ast_channel_get_device_name(ast, device_name, sizeof(device_name));
+ monitor = ast_cc_get_monitor_by_recall_core_id(core_id, device_name);
+ if (monitor) {
+ struct sig_pri_cc_monitor_instance *instance;
+
+ instance = monitor->private_data;
+
+ /* If this fails then we have monitor instance ambiguity. */
+ ast_assert(p->pri == instance->pri);
+
+ if (pri_cc_call(p->pri->pri, instance->cc_id, p->call, sr)) {
+ /* The CC recall call failed for some reason. */
+ ast_log(LOG_WARNING, "Unable to setup CC recall call to device %s\n",
+ device_name);
+ ao2_ref(monitor, -1);
+ pri_rel(p->pri);
+ pri_sr_free(sr);
+ return -1;
+ }
+ ao2_ref(monitor, -1);
+ } else {
+ core_id = -1;
+ }
+ } else
+#endif /* defined(HAVE_PRI_CCSS) */
+ {
+ core_id = -1;
+ }
+ if (core_id == -1 && pri_setup(p->pri->pri, p->call, sr)) {
ast_log(LOG_WARNING, "Unable to setup call to %s (using %s)\n",
c + p->stripmsd + dp_strip, dialplan2str(p->pri->dialplan));
pri_rel(p->pri);
@@ -3935,12 +4801,6 @@ int sig_pri_start_pri(struct sig_pri_pri *pri)
#ifdef HAVE_PRI_INBANDDISCONNECT
pri_set_inbanddisconnect(pri->dchans[i], pri->inbanddisconnect);
#endif
-#if defined(HAVE_PRI_CALL_HOLD)
- pri_hold_enable(pri->dchans[i], 1);
-#endif /* defined(HAVE_PRI_CALL_HOLD) */
-#if defined(HAVE_PRI_CALL_REROUTING)
- pri_reroute_enable(pri->dchans[i], 1);
-#endif /* defined(HAVE_PRI_CALL_REROUTING) */
/* Enslave to master if appropriate */
if (i)
pri_enslave(pri->dchans[0], pri->dchans[i]);
@@ -3951,7 +4811,7 @@ int sig_pri_start_pri(struct sig_pri_pri *pri)
ast_log(LOG_ERROR, "Unable to create PRI structure\n");
return -1;
}
- pri_set_debug(pri->dchans[i], DEFAULT_PRI_DEBUG);
+ pri_set_debug(pri->dchans[i], SIG_PRI_DEBUG_DEFAULT);
pri_set_nsf(pri->dchans[i], pri->nsf);
#ifdef PRI_GETSET_TIMERS
for (x = 0; x < PRI_MAX_TIMERS; x++) {
@@ -3960,8 +4820,23 @@ int sig_pri_start_pri(struct sig_pri_pri *pri)
}
#endif
}
+
/* Assume primary is the one we use */
pri->pri = pri->dchans[0];
+
+#if defined(HAVE_PRI_CALL_HOLD)
+ pri_hold_enable(pri->pri, 1);
+#endif /* defined(HAVE_PRI_CALL_HOLD) */
+#if defined(HAVE_PRI_CALL_REROUTING)
+ pri_reroute_enable(pri->pri, 1);
+#endif /* defined(HAVE_PRI_CALL_REROUTING) */
+#if defined(HAVE_PRI_CCSS)
+ pri_cc_enable(pri->pri, 1);
+ pri_cc_recall_mode(pri->pri, pri->cc_ptmp_recall_mode);
+ pri_cc_retain_signaling_req(pri->pri, pri->cc_qsig_signaling_link_req);
+ pri_cc_retain_signaling_rsp(pri->pri, pri->cc_qsig_signaling_link_rsp);
+#endif /* defined(HAVE_PRI_CCSS) */
+
pri->resetpos = -1;
if (ast_pthread_create_background(&pri->master, NULL, pri_dchannel, pri)) {
for (i = 0; i < NUM_DCHANS; i++) {
@@ -4160,4 +5035,601 @@ void sig_pri_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, str
}
}
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief PRI CC agent initialization.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ * \param pvt_chan Original channel the agent will attempt to recall.
+ *
+ * \details
+ * This callback is called when the CC core is initialized. Agents should allocate
+ * any private data necessary for the call and assign it to the private_data
+ * on the agent. Additionally, if any ast_cc_agent_flags are pertinent to the
+ * specific agent type, they should be set in this function as well.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_cc_agent_init(struct ast_cc_agent *agent, struct sig_pri_chan *pvt_chan)
+{
+ struct sig_pri_cc_agent_prv *cc_pvt;
+
+ cc_pvt = ast_calloc(1, sizeof(*cc_pvt));
+ if (!cc_pvt) {
+ return -1;
+ }
+
+ ast_mutex_lock(&pvt_chan->pri->lock);
+ cc_pvt->pri = pvt_chan->pri;
+ cc_pvt->cc_id = pri_cc_available(pvt_chan->pri->pri, pvt_chan->call);
+ ast_mutex_unlock(&pvt_chan->pri->lock);
+ if (cc_pvt->cc_id == -1) {
+ ast_free(cc_pvt);
+ return -1;
+ }
+ agent->private_data = cc_pvt;
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Start the offer timer.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * This is called by the core when the caller hangs up after
+ * a call for which CC may be requested. The agent should
+ * begin the timer as configured.
+ *
+ * The primary reason why this functionality is left to
+ * the specific agent implementations is due to the differing
+ * use of schedulers throughout the code. Some channel drivers
+ * may already have a scheduler context they wish to use, and
+ * amongst those, some may use the ast_sched API while others
+ * may use the ast_sched_thread API, which are incompatible.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_cc_agent_start_offer_timer(struct ast_cc_agent *agent)
+{
+ /* libpri maintains it's own offer timer in the form of T_RETENTION. */
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Stop the offer timer.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * This callback is called by the CC core when the caller
+ * has requested CC.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_cc_agent_stop_offer_timer(struct ast_cc_agent *agent)
+{
+ /* libpri maintains it's own offer timer in the form of T_RETENTION. */
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Acknowledge CC request.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * When the core receives knowledge that a called
+ * party has accepted a CC request, it will call
+ * this callback.
+ *
+ * The duty of this is to accept a CC request from
+ * the caller by acknowledging receipt of that request.
+ *
+ * \return Nothing
+ */
+void sig_pri_cc_agent_req_ack(struct ast_cc_agent *agent)
+{
+ struct sig_pri_cc_agent_prv *cc_pvt;
+ int res;
+
+ cc_pvt = agent->private_data;
+ ast_mutex_lock(&cc_pvt->pri->lock);
+ if (cc_pvt->cc_request_response_pending) {
+ cc_pvt->cc_request_response_pending = 0;
+ res = pri_cc_req_rsp(cc_pvt->pri->pri, cc_pvt->cc_id, 0/* success */);
+ } else {
+ res = 0;
+ }
+ ast_mutex_unlock(&cc_pvt->pri->lock);
+ if (res) {
+ ast_cc_failed(agent->core_id, "%s agent failed to send the CC request ack.",
+ sig_pri_cc_type_name);
+ }
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Request the status of the agent's device.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * Asynchronous request for the status of any caller
+ * which may be a valid caller for the CC transaction.
+ * Status responses should be made using the
+ * ast_cc_status_response function.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_cc_agent_status_req(struct ast_cc_agent *agent)
+{
+ struct sig_pri_cc_agent_prv *cc_pvt;
+
+ cc_pvt = agent->private_data;
+ ast_mutex_lock(&cc_pvt->pri->lock);
+ pri_cc_status_req(cc_pvt->pri->pri, cc_pvt->cc_id);
+ ast_mutex_unlock(&cc_pvt->pri->lock);
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Request for an agent's phone to stop ringing.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * The usefulness of this is quite limited. The only specific
+ * known case for this is if Asterisk requests CC over an ISDN
+ * PTMP link as the TE side. If other phones are in the same
+ * recall group as the Asterisk server, and one of those phones
+ * picks up the recall notice, then Asterisk will receive a
+ * "stop ringing" notification from the NT side of the PTMP
+ * link. This indication needs to be passed to the phone
+ * on the other side of the Asterisk server which originally
+ * placed the call so that it will stop ringing. Since the
+ * phone may be of any type, it is necessary to have a callback
+ * that the core can know about.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_cc_agent_stop_ringing(struct ast_cc_agent *agent)
+{
+ struct sig_pri_cc_agent_prv *cc_pvt;
+
+ cc_pvt = agent->private_data;
+ ast_mutex_lock(&cc_pvt->pri->lock);
+ pri_cc_stop_alerting(cc_pvt->pri->pri, cc_pvt->cc_id);
+ ast_mutex_unlock(&cc_pvt->pri->lock);
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Let the caller know that the callee has become free
+ * but that the caller cannot attempt to call back because
+ * he is either busy or there is congestion on his line.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * This is something that really only affects a scenario where
+ * a phone places a call over ISDN PTMP to Asterisk, who then
+ * connects over PTMP again to the ISDN network. For most agent
+ * types, there is no need to implement this callback at all
+ * because they don't really need to actually do anything in
+ * this situation. If you're having trouble understanding what
+ * the purpose of this callback is, then you can be safe simply
+ * not implementing it.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_cc_agent_party_b_free(struct ast_cc_agent *agent)
+{
+ struct sig_pri_cc_agent_prv *cc_pvt;
+
+ cc_pvt = agent->private_data;
+ ast_mutex_lock(&cc_pvt->pri->lock);
+ pri_cc_b_free(cc_pvt->pri->pri, cc_pvt->cc_id);
+ ast_mutex_unlock(&cc_pvt->pri->lock);
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Begin monitoring a busy device.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * The core will call this callback if the callee becomes
+ * available but the caller has reported that he is busy.
+ * The agent should begin monitoring the caller's device.
+ * When the caller becomes available again, the agent should
+ * call ast_cc_agent_caller_available.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_cc_agent_start_monitoring(struct ast_cc_agent *agent)
+{
+ /* libpri already knows when and how it needs to monitor Party A. */
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Alert the caller that it is time to try recalling.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * The core will call this function when it receives notice
+ * that a monitored party has become available.
+ *
+ * The agent's job is to send a message to the caller to
+ * notify it of such a change. If the agent is able to
+ * discern that the caller is currently unavailable, then
+ * the agent should react by calling the ast_cc_caller_unavailable
+ * function.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_cc_agent_callee_available(struct ast_cc_agent *agent)
+{
+ struct sig_pri_cc_agent_prv *cc_pvt;
+
+ cc_pvt = agent->private_data;
+ ast_mutex_lock(&cc_pvt->pri->lock);
+ pri_cc_remote_user_free(cc_pvt->pri->pri, cc_pvt->cc_id);
+ ast_mutex_unlock(&cc_pvt->pri->lock);
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Destroy private data on the agent.
+ * \since 1.8
+ *
+ * \param agent CC core agent control.
+ *
+ * \details
+ * The core will call this function upon completion
+ * or failure of CC.
+ *
+ * \note
+ * The agent private_data pointer may be NULL if the agent
+ * constructor failed.
+ *
+ * \return Nothing
+ */
+void sig_pri_cc_agent_destructor(struct ast_cc_agent *agent)
+{
+ struct sig_pri_cc_agent_prv *cc_pvt;
+ int res;
+
+ cc_pvt = agent->private_data;
+ if (!cc_pvt) {
+ /* The agent constructor probably failed. */
+ return;
+ }
+ ast_mutex_lock(&cc_pvt->pri->lock);
+ res = -1;
+ if (cc_pvt->cc_request_response_pending) {
+ res = pri_cc_req_rsp(cc_pvt->pri->pri, cc_pvt->cc_id, 2/* short_term_denial */);
+ }
+ if (res) {
+ pri_cc_cancel(cc_pvt->pri->pri, cc_pvt->cc_id);
+ }
+ ast_mutex_unlock(&cc_pvt->pri->lock);
+ ast_free(cc_pvt);
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Return the hash value of the given CC monitor instance object.
+ * \since 1.8
+ *
+ * \param obj pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback(). Ignored at the moment.
+ *
+ * \retval core_id
+ */
+static int sig_pri_cc_monitor_instance_hash_fn(const void *obj, const int flags)
+{
+ const struct sig_pri_cc_monitor_instance *monitor_instance = obj;
+
+ return monitor_instance->core_id;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \internal
+ * \brief Compere the monitor instance core_id key value.
+ * \since 1.8
+ *
+ * \param obj pointer to the (user-defined part) of an object.
+ * \param arg callback argument from ao2_callback()
+ * \param flags flags from ao2_callback()
+ *
+ * \return values are a combination of enum _cb_results.
+ */
+static int sig_pri_cc_monitor_instance_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct sig_pri_cc_monitor_instance *monitor_1 = obj;
+ struct sig_pri_cc_monitor_instance *monitor_2 = arg;
+
+ return monitor_1->core_id == monitor_2->core_id ? CMP_MATCH | CMP_STOP : 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Request CCSS.
+ * \since 1.8
+ *
+ * \param monitor CC core monitor control.
+ * \param available_timer_id Where to put the available timer scheduler id.
+ * Will never be NULL for a device monitor.
+ *
+ * \details
+ * Perform whatever steps are necessary in order to request CC.
+ * In addition, the monitor implementation is responsible for
+ * starting the available timer in this callback. The scheduler
+ * ID for the callback must be stored in the parent_link's child_avail_id
+ * field.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure.
+ */
+int sig_pri_cc_monitor_req_cc(struct ast_cc_monitor *monitor, int *available_timer_id)
+{
+ struct sig_pri_cc_monitor_instance *instance;
+ int cc_mode;
+ int res;
+
+ switch (monitor->service_offered) {
+ case AST_CC_CCBS:
+ cc_mode = 0;/* CCBS */
+ break;
+ case AST_CC_CCNR:
+ cc_mode = 1;/* CCNR */
+ break;
+ default:
+ /* CC service not supported by ISDN. */
+ return -1;
+ }
+
+ instance = monitor->private_data;
+
+ /* libpri handles it's own available timer. */
+ ast_mutex_lock(&instance->pri->lock);
+ res = pri_cc_req(instance->pri->pri, instance->cc_id, cc_mode);
+ ast_mutex_unlock(&instance->pri->lock);
+
+ return res;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Suspend monitoring.
+ * \since 1.8
+ *
+ * \param monitor CC core monitor control.
+ *
+ * \details
+ * Implementers must perform the necessary steps to suspend
+ * monitoring.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure.
+ */
+int sig_pri_cc_monitor_suspend(struct ast_cc_monitor *monitor)
+{
+ struct sig_pri_cc_monitor_instance *instance;
+
+ instance = monitor->private_data;
+ ast_mutex_lock(&instance->pri->lock);
+ pri_cc_status(instance->pri->pri, instance->cc_id, 1/* busy */);
+ ast_mutex_unlock(&instance->pri->lock);
+
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Unsuspend monitoring.
+ * \since 1.8
+ *
+ * \param monitor CC core monitor control.
+ *
+ * \details
+ * Perform the necessary steps to unsuspend monitoring.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure.
+ */
+int sig_pri_cc_monitor_unsuspend(struct ast_cc_monitor *monitor)
+{
+ struct sig_pri_cc_monitor_instance *instance;
+
+ instance = monitor->private_data;
+ ast_mutex_lock(&instance->pri->lock);
+ pri_cc_status(instance->pri->pri, instance->cc_id, 0/* free */);
+ ast_mutex_unlock(&instance->pri->lock);
+
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Status response to an ast_cc_monitor_status_request().
+ * \since 1.8
+ *
+ * \param monitor CC core monitor control.
+ * \param devstate Current status of a Party A device.
+ *
+ * \details
+ * Alert a monitor as to the status of the agent for which
+ * the monitor had previously requested a status request.
+ *
+ * \note Zero or more responses may come as a result.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure.
+ */
+int sig_pri_cc_monitor_status_rsp(struct ast_cc_monitor *monitor, enum ast_device_state devstate)
+{
+ struct sig_pri_cc_monitor_instance *instance;
+ int cc_status;
+
+ switch (devstate) {
+ case AST_DEVICE_UNKNOWN:
+ case AST_DEVICE_NOT_INUSE:
+ cc_status = 0;/* free */
+ break;
+ case AST_DEVICE_BUSY:
+ case AST_DEVICE_INUSE:
+ cc_status = 1;/* busy */
+ break;
+ default:
+ /* Don't know how to interpret this device state into free/busy status. */
+ return 0;
+ }
+ instance = monitor->private_data;
+ ast_mutex_lock(&instance->pri->lock);
+ pri_cc_status_req_rsp(instance->pri->pri, instance->cc_id, cc_status);
+ ast_mutex_unlock(&instance->pri->lock);
+
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Cancel the running available timer.
+ * \since 1.8
+ *
+ * \param monitor CC core monitor control.
+ * \param sched_id Available timer scheduler id to cancel.
+ * Will never be NULL for a device monitor.
+ *
+ * \details
+ * In most cases, this function will likely consist of just a
+ * call to AST_SCHED_DEL. It might have been possible to do this
+ * within the core, but unfortunately the mixture of sched_thread
+ * and sched usage in Asterisk prevents such usage.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure.
+ */
+int sig_pri_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id)
+{
+ /*
+ * libpri maintains it's own available timer as one of:
+ * T_CCBS2/T_CCBS5/T_CCBS6/QSIG_CCBS_T2
+ * T_CCNR2/T_CCNR5/T_CCNR6/QSIG_CCNR_T2
+ */
+ return 0;
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+#if defined(HAVE_PRI_CCSS)
+/*!
+ * \brief Destroy PRI private data on the monitor.
+ * \since 1.8
+ *
+ * \param monitor_pvt CC device monitor private data pointer.
+ *
+ * \details
+ * Implementers of this callback are responsible for destroying
+ * all heap-allocated data in the monitor's private_data pointer, including
+ * the private_data itself.
+ */
+void sig_pri_cc_monitor_destructor(void *monitor_pvt)
+{
+ struct sig_pri_cc_monitor_instance *instance;
+
+ instance = monitor_pvt;
+ if (!instance) {
+ return;
+ }
+ ao2_unlink(sig_pri_cc_monitors, instance);
+ ao2_ref(instance, -1);
+}
+#endif /* defined(HAVE_PRI_CCSS) */
+
+/*!
+ * \brief Load the sig_pri submodule.
+ * \since 1.8
+ *
+ * \param cc_type_name CC type name to use when looking up agent/monitor.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int sig_pri_load(const char *cc_type_name)
+{
+#if defined(HAVE_PRI_CCSS)
+ sig_pri_cc_type_name = cc_type_name;
+ sig_pri_cc_monitors = ao2_container_alloc(37, sig_pri_cc_monitor_instance_hash_fn,
+ sig_pri_cc_monitor_instance_cmp_fn);
+ if (!sig_pri_cc_monitors) {
+ return -1;
+ }
+#endif /* defined(HAVE_PRI_CCSS) */
+ return 0;
+}
+
+/*!
+ * \brief Unload the sig_pri submodule.
+ * \since 1.8
+ *
+ * \return Nothing
+ */
+void sig_pri_unload(void)
+{
+#if defined(HAVE_PRI_CCSS)
+ if (sig_pri_cc_monitors) {
+ ao2_ref(sig_pri_cc_monitors, -1);
+ sig_pri_cc_monitors = NULL;
+ }
+#endif /* defined(HAVE_PRI_CCSS) */
+}
+
#endif /* HAVE_PRI */
diff --git a/channels/sig_pri.h b/channels/sig_pri.h
index 0bccd6ab0..7ea92d752 100644
--- a/channels/sig_pri.h
+++ b/channels/sig_pri.h
@@ -27,8 +27,44 @@
#include "asterisk/channel.h"
#include "asterisk/frame.h"
+#include "asterisk/ccss.h"
#include <libpri.h>
#include <dahdi/user.h>
+#if defined(PRI_SUBCMD_CC_AVAILABLE)
+/* BUGBUG the HAVE_PRI_CCSS line is to be removed when the CCSS branch is merged to trunk and the configure script is updated. */
+#define HAVE_PRI_CCSS 1
+#endif /* defined(PRI_SUBCMD_CC_AVAILABLE) */
+
+#if defined(HAVE_PRI_CCSS)
+/*! PRI debug message flags when normal PRI debugging is turned on at the command line. */
+#define SIG_PRI_DEBUG_NORMAL \
+ (PRI_DEBUG_APDU | PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE \
+ | PRI_DEBUG_CC)
+
+/*! PRI debug message flags when intense PRI debugging is turned on at the command line. */
+#define SIG_PRI_DEBUG_INTENSE \
+ (PRI_DEBUG_APDU | PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE \
+ | PRI_DEBUG_CC | PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_DUMP)
+
+#else
+
+/*! PRI debug message flags when normal PRI debugging is turned on at the command line. */
+#define SIG_PRI_DEBUG_NORMAL \
+ (PRI_DEBUG_APDU | PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE)
+
+/*! PRI debug message flags when intense PRI debugging is turned on at the command line. */
+#define SIG_PRI_DEBUG_INTENSE \
+ (PRI_DEBUG_APDU | PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE \
+ | PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_DUMP)
+#endif /* !defined(HAVE_PRI_CCSS) */
+
+#if 0
+/*! PRI debug message flags set on initial startup. */
+#define SIG_PRI_DEBUG_DEFAULT SIG_PRI_DEBUG_NORMAL
+#else
+/*! PRI debug message flags set on initial startup. */
+#define SIG_PRI_DEBUG_DEFAULT 0
+#endif
enum sig_pri_tone {
SIG_PRI_TONE_RINGTONE = 0,
@@ -78,6 +114,14 @@ struct sig_pri_callback {
void (* const set_rdnis)(void *pvt, const char *rdnis);
void (* const queue_control)(void *pvt, int subclass);
int (* const new_nobch_intf)(struct sig_pri_pri *pri);
+ const char *(* const get_orig_dialstring)(void *pvt);
+ void (* const make_cc_dialstring)(void *pvt, char *buf, size_t buf_size);
+ void (* const update_span_devstate)(struct sig_pri_pri *pri);
+
+ /*! Reference the parent module. */
+ void (*module_ref)(void);
+ /*! Unreference the parent module. */
+ void (*module_unref)(void);
};
#define NUM_DCHANS 4 /*!< No more than 4 d-channels */
@@ -194,6 +238,7 @@ struct sig_pri_chan {
struct sig_pri_pri {
/* Should be set by user */
+ struct ast_cc_config_params *cc_params; /*!< CC config parameters for each new call. */
int pritimers[PRI_MAX_TIMERS];
int overlapdial; /*!< In overlap dialing mode */
int qsigchannelmapping; /*!< QSIG channel mapping type */
@@ -229,6 +274,11 @@ struct sig_pri_pri {
int switchtype; /*!< Type of switch to emulate */
int nsf; /*!< Network-Specific Facilities */
int trunkgroup; /*!< What our trunkgroup is */
+#if defined(HAVE_PRI_CCSS)
+ int cc_ptmp_recall_mode; /*!< CC PTMP recall mode. globalRecall(0), specificRecall(1) */
+ int cc_qsig_signaling_link_req; /*!< CC Q.SIG signaling link retention (Party A) release(0), retain(1), do-not-care(2) */
+ int cc_qsig_signaling_link_rsp; /*!< CC Q.SIG signaling link retention (Party B) release(0), retain(1) */
+#endif /* defined(HAVE_PRI_CCSS) */
int dchanavail[NUM_DCHANS]; /*!< Whether each channel is available */
int debug; /*!< set to true if to dump PRI event info (tested but never set) */
@@ -257,6 +307,37 @@ struct sig_pri_pri {
ast_mutex_t lock; /*!< libpri access Mutex */
time_t lastreset; /*!< time when unused channels were last reset */
struct sig_pri_callback *calls;
+ /*!
+ * \brief Congestion device state of the span.
+ * \details
+ * AST_DEVICE_NOT_INUSE - Span does not have all B channels in use.
+ * AST_DEVICE_BUSY - All B channels are in use.
+ * AST_DEVICE_UNAVAILABLE - Span is in alarm.
+ * \note
+ * Device name: DAHDI/I<span>/congestion
+ */
+ int congestion_devstate;
+#if defined(THRESHOLD_DEVSTATE_PLACEHOLDER)
+ /*! \todo An ISDN span threshold device state could be useful in determining how often a span utilization goes over a configurable threshold. */
+ /*!
+ * \brief User threshold device state of the span.
+ * \details
+ * AST_DEVICE_NOT_INUSE - There are no B channels in use.
+ * AST_DEVICE_INUSE - The number of B channels in use is less than
+ * the configured threshold but not zero.
+ * AST_DEVICE_BUSY - The number of B channels in use meets or exceeds
+ * the configured threshold.
+ * AST_DEVICE_UNAVAILABLE - Span is in alarm.
+ * \note
+ * Device name: DAHDI/I<span>/threshold
+ */
+ int threshold_devstate;
+ /*!
+ * \brief Number of B channels in use to consider the span in a busy state.
+ * \note Setting the threshold to zero is interpreted as all B channels.
+ */
+ int user_busy_threshold;
+#endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */
};
void sig_pri_extract_called_num_subaddr(struct sig_pri_chan *p, const char *rdest, char *called, size_t called_buff_size);
@@ -304,4 +385,25 @@ int pri_maintenance_bservice(struct pri *pri, struct sig_pri_chan *p, int change
void sig_pri_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, struct sig_pri_chan *pchan);
+int sig_pri_cc_agent_init(struct ast_cc_agent *agent, struct sig_pri_chan *pvt_chan);
+int sig_pri_cc_agent_start_offer_timer(struct ast_cc_agent *agent);
+int sig_pri_cc_agent_stop_offer_timer(struct ast_cc_agent *agent);
+void sig_pri_cc_agent_req_ack(struct ast_cc_agent *agent);
+int sig_pri_cc_agent_status_req(struct ast_cc_agent *agent);
+int sig_pri_cc_agent_stop_ringing(struct ast_cc_agent *agent);
+int sig_pri_cc_agent_party_b_free(struct ast_cc_agent *agent);
+int sig_pri_cc_agent_start_monitoring(struct ast_cc_agent *agent);
+int sig_pri_cc_agent_callee_available(struct ast_cc_agent *agent);
+void sig_pri_cc_agent_destructor(struct ast_cc_agent *agent);
+
+int sig_pri_cc_monitor_req_cc(struct ast_cc_monitor *monitor, int *available_timer_id);
+int sig_pri_cc_monitor_suspend(struct ast_cc_monitor *monitor);
+int sig_pri_cc_monitor_unsuspend(struct ast_cc_monitor *monitor);
+int sig_pri_cc_monitor_status_rsp(struct ast_cc_monitor *monitor, enum ast_device_state devstate);
+int sig_pri_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id);
+void sig_pri_cc_monitor_destructor(void *monitor_pvt);
+
+int sig_pri_load(const char *cc_type_name);
+void sig_pri_unload(void);
+
#endif /* _SIG_PRI_H */
diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
index ce87f0f23..1d900eb58 100644
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -153,7 +153,7 @@
* \todo This string should be set dynamically. We only support REFER and SUBSCRIBE if we have
* allowsubscribe and allowrefer on in sip.conf.
*/
-#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO"
+#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH"
/*! \brief SIP Extensions we support
* \note This should be generated based on the previous array
@@ -239,6 +239,7 @@
*/
/*@{*/
#define SIP_OUTGOING (1 << 0) /*!< D: Direction of the last transaction in this dialog */
+#define SIP_OFFER_CC (1 << 1) /*!< D: Offer CC on subsequent responses */
#define SIP_RINGING (1 << 2) /*!< D: Have sent 180 ringing */
#define SIP_PROGRESS_SENT (1 << 3) /*!< D: Have sent 183 message progress */
#define SIP_NEEDREINVITE (1 << 4) /*!< D: Do we need to send another reinvite? */
@@ -415,7 +416,8 @@ enum subscriptiontype {
DIALOG_INFO_XML,
CPIM_PIDF_XML,
PIDF_XML,
- MWI_NOTIFICATION
+ MWI_NOTIFICATION,
+ CALL_COMPLETION,
};
/*! \brief The number of media types in enum \ref media_type below. */
@@ -930,6 +932,7 @@ struct sip_pvt {
AST_STRING_FIELD(url); /*!< URL to be sent with next message to peer */
AST_STRING_FIELD(parkinglot); /*!< Parkinglot */
AST_STRING_FIELD(engine); /*!< RTP engine to use */
+ AST_STRING_FIELD(dialstring); /*!< The dialstring used to call this SIP endpoint */
);
char via[128]; /*!< Via: header */
struct sip_socket socket; /*!< The socket used for this dialog */
@@ -1066,6 +1069,8 @@ struct sip_pvt {
* The large-scale changes would be a good idea for implementing during an SDP rewrite.
*/
struct offered_media offered_media[OFFERED_MEDIA_COUNT];
+ struct ast_cc_config_params *cc_params;
+ struct sip_epa_entry *epa_entry;
};
/*! \brief sip packet - raw format for outbound packets that are sent or scheduled for transmission
@@ -1197,6 +1202,7 @@ struct sip_peer {
/*XXX Seems like we suddenly have two flags with the same content. Why? To be continued... */
enum sip_peer_type type; /*!< Distinguish between "user" and "peer" types. This is used solely for CLI and manager commands */
unsigned int disallowed_methods;
+ struct ast_cc_config_params *cc_params;
};
/*!
@@ -1286,4 +1292,359 @@ struct sip_subscription_mwi {
struct ast_dnsmgr_entry *dnsmgr; /*!< DNS refresh manager for subscription */
struct sockaddr_in us; /*!< Who the server thinks we are */
};
+
+/*!
+ * SIP PUBLISH support!
+ * PUBLISH support was added to chan_sip due to its use in the call-completion
+ * event package. In order to suspend and unsuspend monitoring of a called party,
+ * a PUBLISH message must be sent. Rather than try to hack in PUBLISH transmission
+ * and reception solely for the purposes of handling call-completion-related messages,
+ * an effort has been made to create a generic framework for handling PUBLISH messages.
+ *
+ * There are two main components to the effort, the event publication agent (EPA) and
+ * the event state compositor (ESC). Both of these terms appear in RFC 3903, and the
+ * implementation in Asterisk conforms to the defintions there. An EPA is a UAC that
+ * transmits PUBLISH requests. An ESC is a UAS that receives PUBLISH requests and
+ * acts appropriately based on the content of those requests.
+ *
+ * ESC:
+ * The main structure in chan_sip is the event_state_compositor. There is an
+ * event_state_compositor structure for each event package supported (as of Nov 2009
+ * this is only the call-completion package). The structure contains data which is
+ * intrinsic to the event package itself, such as the name of the package and a set
+ * of callbacks for handling incoming PUBLISH requests. In addition, the
+ * event_state_compositor struct contains an ao2_container of sip_esc_entries.
+ *
+ * A sip_esc_entry corresponds to an entity which has sent a PUBLISH to Asterisk. We are
+ * able to match the incoming PUBLISH to a sip_esc_entry using the Sip-If-Match header
+ * of the message. Of course, if none is present, then a new sip_esc_entry will be created.
+ *
+ * Once it is determined what type of PUBLISH request has come in (from RFC 3903, it may
+ * be an initial, modify, refresh, or remove), then the event package-specific callbacks
+ * may be called. If your event package doesn't need to take any specific action for a
+ * specific PUBLISH type, it is perfectly safe to not define the callback at all. The callback
+ * only needs to take care of application-specific information. If there is a problem, it is
+ * up to the callback to take care of sending an appropriate 4xx or 5xx response code. In such
+ * a case, the callback should return -1. This will tell the function that called the handler
+ * that an appropriate error response has been sent. If the callback returns 0, however, then
+ * the caller of the callback will generate a new entity tag and send a 200 OK response.
+ *
+ * ESC entries are reference-counted, however as an implementor of a specific event package,
+ * this should be transparent, since the reference counts are handled by the general ESC
+ * framework.
+ *
+ * EPA:
+ * The event publication agent in chan_sip is structured quite a bit differently than the
+ * ESC. With an ESC, an appropriate entry has to be found based on the contents of an incoming
+ * PUBLISH message. With an EPA, the application interested in sending the PUBLISH can maintain
+ * a reference to the appropriate EPA entry instead. Similarly, when matching a PUBLISH response
+ * to an appropriate EPA entry, the sip_pvt can maintain a reference to the corresponding
+ * EPA entry. The result of this train of thought is that there is no compelling reason to
+ * maintain a container of these entries.
+ *
+ * Instead, there is only the sip_epa_entry structure. Every sip_epa_entry has an entity tag
+ * that it maintains so that subsequent PUBLISH requests will be identifiable by the ESC on
+ * the far end. In addition, there is a static_data field which contains information that is
+ * common to all sip_epa_entries for a specific event package. This static data includes the
+ * name of the event package and callbacks for handling specific responses for outgoing PUBLISHes.
+ * Also, there is a field for pointing to instance-specific data. This can include the current
+ * published state or other identifying information that is specific to an instance of an EPA
+ * entry of a particular event package.
+ *
+ * When an application wishes to send a PUBLISH request, it simply will call create_epa_entry,
+ * followed by transmit_publish in order to send the PUBLISH. That's all that is necessary.
+ * Like with ESC entries, sip_epa_entries are reference counted. Unlike ESC entries, though,
+ * sip_epa_entries reference counts have to be maintained to some degree by the application making
+ * use of the sip_epa_entry. The application will acquire a reference to the EPA entry when it
+ * calls create_epa_entry. When the application has finished using the EPA entry (which may not
+ * be until after several PUBLISH transactions have taken place) it must use ao2_ref to decrease
+ * the reference count by 1.
+ */
+
+/*!
+ * \brief The states that can be represented in a SIP call-completion PUBLISH
+ */
+enum sip_cc_publish_state {
+ /*! Closed, i.e. unavailable */
+ CC_CLOSED,
+ /*! Open, i.e. available */
+ CC_OPEN,
+};
+
+/*!
+ * \brief The states that can be represented in a SIP call-completion NOTIFY
+ */
+enum sip_cc_notify_state {
+ /*! Queued, i.e. unavailable */
+ CC_QUEUED,
+ /*! Ready, i.e. available */
+ CC_READY,
+};
+
+/*!
+ * \brief The types of PUBLISH messages defined in RFC 3903
+ */
+enum sip_publish_type {
+ /*!
+ * \brief Unknown
+ *
+ * \details
+ * This actually is not defined in RFC 3903. We use this as a constant
+ * to indicate that an incoming PUBLISH does not fit into any of the
+ * other categories and is thus invalid.
+ */
+ SIP_PUBLISH_UNKNOWN,
+ /*!
+ * \brief Initial
+ *
+ * \details
+ * The first PUBLISH sent. This will contain a non-zero Expires header
+ * as well as a body that indicates the current state of the endpoint
+ * that has sent the message. The initial PUBLISH is the only type
+ * of PUBLISH to not contain a Sip-If-Match header in it.
+ */
+ SIP_PUBLISH_INITIAL,
+ /*!
+ * \brief Refresh
+ *
+ * \details
+ * Used to keep a published state from expiring. This will contain a
+ * non-zero Expires header but no body since its purpose is not to
+ * update state.
+ */
+ SIP_PUBLISH_REFRESH,
+ /*!
+ * \brief Modify
+ *
+ * \details
+ * Used to change state from its previous value. This will contain
+ * a body updating the published state. May or may not contain an
+ * Expires header.
+ */
+ SIP_PUBLISH_MODIFY,
+ /*!
+ * \brief Remove
+ *
+ * \details
+ * Used to remove published state from an ESC. This will contain
+ * an Expires header set to 0 and likely no body.
+ */
+ SIP_PUBLISH_REMOVE,
+};
+
+/*!
+ * Data which is the same for all instances of an EPA for a
+ * particular event package
+ */
+struct epa_static_data {
+ /*! The event type */
+ enum subscriptiontype event;
+ /*!
+ * The name of the event as it would
+ * appear in a SIP message
+ */
+ const char *name;
+ /*!
+ * The callback called when a 200 OK is received on an outbound PUBLISH
+ */
+ void (*handle_ok)(struct sip_pvt *, struct sip_request *, struct sip_epa_entry *);
+ /*!
+ * The callback called when an error response is received on an outbound PUBLISH
+ */
+ void (*handle_error)(struct sip_pvt *, const int resp, struct sip_request *, struct sip_epa_entry *);
+ /*!
+ * Destructor to call to clean up instance data
+ */
+ void (*destructor)(void *instance_data);
+};
+
+/*!
+ * \brief backend for an event publication agent
+ */
+struct epa_backend {
+ const struct epa_static_data *static_data;
+ AST_LIST_ENTRY(epa_backend) next;
+};
+
+struct sip_epa_entry {
+ /*!
+ * When we are going to send a publish, we need to
+ * know the type of PUBLISH to send.
+ */
+ enum sip_publish_type publish_type;
+ /*!
+ * When we send a PUBLISH, we have to be
+ * sure to include the entity tag that we
+ * received in the previous response.
+ */
+ char entity_tag[SIPBUFSIZE];
+ /*!
+ * The destination to which this EPA should send
+ * PUBLISHes. This may be the name of a SIP peer
+ * or a hostname.
+ */
+ char destination[SIPBUFSIZE];
+ /*!
+ * The body of the most recently-sent PUBLISH message.
+ * This is useful for situations such as authentication,
+ * in which we must send a message identical to the
+ * one previously sent
+ */
+ char body[SIPBUFSIZE];
+ /*!
+ * Every event package has some constant data and
+ * callbacks that all instances will share. This
+ * data resides in this field.
+ */
+ const struct epa_static_data *static_data;
+ /*!
+ * In addition to the static data that all instances
+ * of sip_epa_entry will have, each instance will
+ * require its own instance-specific data.
+ */
+ void *instance_data;
+};
+
+/*!
+ * \brief Instance data for a Call completion EPA entry
+ */
+struct cc_epa_entry {
+ /*!
+ * The core ID of the CC transaction
+ * for which this EPA entry belongs. This
+ * essentially acts as a unique identifier
+ * for the entry and is used in the hash
+ * and comparison functions
+ */
+ int core_id;
+ /*!
+ * We keep the last known state of the
+ * device in question handy in case
+ * it needs to be known by a third party.
+ * Also, in the case where for some reason
+ * we get asked to transmit state that we
+ * already sent, we can just ignore the
+ * request.
+ */
+ enum sip_cc_publish_state current_state;
+};
+
+struct event_state_compositor;
+
+/*!
+ * \brief common ESC items for all event types
+ *
+ * The entity_id field serves as a means by which
+ * A specific entry may be found.
+ */
+struct sip_esc_entry {
+ /*!
+ * The name of the party who
+ * sent us the PUBLISH. This will more
+ * than likely correspond to a peer name.
+ *
+ * This field's utility isn't really that
+ * great. It's mainly just a user-recognizable
+ * handle that can be printed in debug messages.
+ */
+ const char *device_name;
+ /*!
+ * The event package for which this esc_entry
+ * exists. Most of the time this isn't really
+ * necessary since you'll have easy access to the
+ * ESC which contains this entry. However, in
+ * some circumstances, we won't have the ESC
+ * available.
+ */
+ const char *event;
+ /*!
+ * The entity ID used when corresponding
+ * with the EPA on the other side. As the
+ * ESC, we generate an entity ID for each
+ * received PUBLISH and store it in this
+ * structure.
+ */
+ char entity_tag[30];
+ /*!
+ * The ID for the scheduler. We schedule
+ * destruction of a sip_esc_entry when we
+ * receive a PUBLISH. The destruction is
+ * scheduled for the duration received in
+ * the Expires header.
+ */
+ int sched_id;
+ /*!
+ * Each ESC entry will be for a specific
+ * event type. Those entries will need to
+ * carry data which is intrinsic to the
+ * ESC entry but which is specific to
+ * the event package
+ */
+ void *event_specific_data;
+};
+
+typedef int (* const esc_publish_callback)(struct sip_pvt *, struct sip_request *, struct event_state_compositor *, struct sip_esc_entry *);
+
+/*!
+ * \brief Callbacks for SIP ESCs
+ *
+ * \details
+ * The names of the callbacks are self-explanatory. The
+ * corresponding handler is called whenever the specific
+ * type of PUBLISH is received.
+ */
+struct sip_esc_publish_callbacks {
+ const esc_publish_callback initial_handler;
+ const esc_publish_callback refresh_handler;
+ const esc_publish_callback modify_handler;
+ const esc_publish_callback remove_handler;
+};
+
+struct sip_cc_agent_pvt {
+ int offer_timer_id;
+ /* A copy of the original call's Call-ID.
+ * We use this as a search key when attempting
+ * to find a particular sip_pvt.
+ */
+ char original_callid[SIPBUFSIZE];
+ /* A copy of the exten called originally.
+ * We use this to set the proper extension
+ * to dial during the recall since the incoming
+ * request URI is one that was generated just
+ * for the recall
+ */
+ char original_exten[SIPBUFSIZE];
+ /* A reference to the dialog which we will
+ * be sending a NOTIFY on when it comes time
+ * to send one
+ */
+ struct sip_pvt *subscribe_pvt;
+ /* When we send a NOTIFY, we include a URI
+ * that should be used by the caller when he
+ * wishes to send a PUBLISH or INVITE to us.
+ * We store that URI here.
+ */
+ char notify_uri[SIPBUFSIZE];
+ /* When we advertise call completion to a caller,
+ * we provide a URI for the caller to use when
+ * he sends us a SUBSCRIBE. We store it for matching
+ * purposes when we receive the SUBSCRIBE from the
+ * caller.
+ */
+ char subscribe_uri[SIPBUFSIZE];
+ char is_available;
+};
+
+struct sip_monitor_instance {
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(subscribe_uri);
+ AST_STRING_FIELD(notify_uri);
+ AST_STRING_FIELD(peername);
+ AST_STRING_FIELD(device_name);
+ );
+ int core_id;
+ struct sip_pvt *subscription_pvt;
+ struct sip_epa_entry *suspension_entry;
+};
+
#endif