summaryrefslogtreecommitdiff
path: root/channels
diff options
context:
space:
mode:
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