Merge Call completion support into trunk.
authorMark Michelson <mmichelson@digium.com>
Fri, 9 Apr 2010 15:31:32 +0000 (15:31 +0000)
committerMark Michelson <mmichelson@digium.com>
Fri, 9 Apr 2010 15:31:32 +0000 (15:31 +0000)
From Reviewboard:
CCSS stands for Call Completion Supplementary Services. An admittedly out-of-date
overview of the architecture can be found in the file doc/CCSS_architecture.pdf
in the CCSS branch. Off the top of my head, the big differences between what is
implemented and what is in the document are as follows:

1. We did not end up modifying the Hangup application at all.
2. The document states that a single call completion monitor may be used across
   multiple calls to the same device. This proved to not be such a good idea
   when implementing protocol-specific monitors, and so we ended up using one
   monitor per-device per-call.
3. There are some configuration options which were conceived after the document
   was written. These are documented in the ccss.conf.sample that is on this
   review request.

For some basic understanding of terminology used throughout this code, see the
ccss.tex document that is on this review.

This implements CCBS and CCNR in several flavors.

First up is a "generic" implementation, which can work over any channel technology
provided that the channel technology can accurately report device state. Call
completion is requested using the dialplan application CallCompletionRequest and can
be canceled using CallCompletionCancel. Device state subscriptions are used in order
to monitor the state of called parties.

Next, there is a SIP-specific implementation of call completion. This method uses the
methods outlined in draft-ietf-bliss-call-completion-06 to implement call completion
using SIP signaling. There are a few things to note here:

* The agent/monitor terminology used throughout Asterisk sometimes is the reverse of
  what is defined in the referenced draft.

* Implementation of the draft required support for SIP PUBLISH. I attempted to write
  this in a generic-enough fashion such that if someone were to want to write PUBLISH
  support for other event packages, such as dialog-state or presence, most of the effort
  would be in writing callbacks specific to the event package.

* A subportion of supporting PUBLISH reception was that we had to implement a PIDF
  parser. The PIDF support added is a bit minimal. I first wrote a validation
  routine to ensure that the PIDF document is formatted properly. The rest of the
  PIDF reading is done in-line in the call-completion-specific PUBLISH-handling
  code. In other words, while there is PIDF support here, it is not in any state
  where it could easily be applied to other event packages as is.

Finally, there are a variety of ISDN-related call completion protocols supported. These
were written by Richard Mudgett, and as such I can't really say much about their
implementation. There are notes in the CHANGES file that indicate the ISDN protocols
over which call completion is supported.

Review: https://reviewboard.asterisk.org/r/523

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@256528 65c4cc65-6c06-0410-ace0-fbb531ad65f3

30 files changed:
CHANGES
apps/app_dial.c
channels/chan_dahdi.c
channels/chan_local.c
channels/chan_sip.c
channels/sig_analog.c
channels/sig_analog.h
channels/sig_pri.c
channels/sig_pri.h
channels/sip/include/sip.h
configs/ccss.conf.sample [new file with mode: 0644]
configs/chan_dahdi.conf.sample
configs/manager.conf.sample
configure.ac
doc/tex/asterisk.tex
doc/tex/ccss.tex [new file with mode: 0644]
funcs/func_callcompletion.c [new file with mode: 0644]
include/asterisk/ccss.h [new file with mode: 0644]
include/asterisk/channel.h
include/asterisk/channelstate.h [new file with mode: 0644]
include/asterisk/devicestate.h
include/asterisk/frame.h
include/asterisk/manager.h
include/asterisk/rtp_engine.h
include/asterisk/xml.h
main/asterisk.c
main/ccss.c [new file with mode: 0644]
main/channel.c
main/manager.c
main/xml.c

diff --git a/CHANGES b/CHANGES
index 2ea3e5f..218da9c 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -387,6 +387,13 @@ Calendaring for Asterisk
    iCalendar, CalDAV, and Exchange Server calendars are supported (Exchange support
    only tested on Exchange Server 2003 with no support for forms-based authentication).
 
+Call Completion Supplementary Services for Asterisk
+---------------------------------------------------
+ * Call completion support has been added for SIP, DAHDI/ISDN, and DAHDI/analog.
+   DAHDI/ISDN supports call completion for the following switch types:
+   EuroIsdn(ETSI) for PTP and PTMP modes, and Qsig.
+   See doc/CCSS_architecture.pdf and doc/tex/ccss.tex(asterisk.pdf) for details.
+
 Multicast RTP Support
 ---------------------
  * A new RTP engine and channel driver have been added which supports Multicast RTP.
index 8a58932..b1de21d 100644 (file)
@@ -62,6 +62,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/global_datastores.h"
 #include "asterisk/dsp.h"
 #include "asterisk/cel.h"
+#include "asterisk/ccss.h"
 #include "asterisk/indications.h"
 
 /*** DOCUMENTATION
@@ -810,6 +811,12 @@ static void do_forward(struct chanlist *o,
                                ast_channel_make_compatible(o->chan, in);
                        ast_channel_inherit_variables(in, o->chan);
                        ast_channel_datastore_inherit(in, o->chan);
+                       /* When a call is forwarded, we don't want to track new interfaces
+                        * dialed for CC purposes. Setting the done flag will ensure that
+                        * any Dial operations that happen later won't record CC interfaces.
+                        */
+                       ast_ignore_cc(o->chan);
+                       ast_log(LOG_NOTICE, "Not accepting call completion offers from call-forward recipient %s\n", o->chan->name);
                } else
                        ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s' (cause = %d)\n", tech, stuff, cause);
        }
@@ -904,7 +911,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
        struct chanlist *outgoing, int *to, struct ast_flags64 *peerflags,
        char *opt_args[],
        struct privacy_args *pa,
-       const struct cause_args *num_in, int *result, char *dtmf_progress)
+       const struct cause_args *num_in, int *result, char *dtmf_progress,
+       const int ignore_cc)
 {
        struct cause_args num = *num_in;
        int prestart = num.busy + num.congestion + num.nochan;
@@ -917,6 +925,10 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 #endif
        struct ast_party_connected_line connected_caller;
        struct ast_str *featurecode = ast_str_alloca(FEATURE_MAX_LEN + 1);
+       int cc_recall_core_id;
+       int is_cc_recall;
+       int cc_frame_received = 0;
+       int num_ringing = 0;
 
        ast_party_connected_line_init(&connected_caller);
        if (single) {
@@ -938,6 +950,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                }
        }
 
+       is_cc_recall = ast_cc_is_recall(in, &cc_recall_core_id, NULL);
+
 #ifdef HAVE_EPOLL
        for (epollo = outgoing; epollo; epollo = epollo->next)
                ast_poll_channel_add(in, epollo->chan);
@@ -970,6 +984,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                ast_verb(3, "No one is available to answer at this time (%d:%d/%d/%d)\n", numlines, num.busy, num.congestion, num.nochan);
                        }
                        *to = 0;
+                       if (is_cc_recall) {
+                               ast_cc_failed(cc_recall_core_id, "Everyone is busy/congested for the recall. How sad");
+                       }
                        return NULL;
                }
                winner = ast_waitfor_n(watchers, pos, to);
@@ -1014,6 +1031,15 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                        /* here, o->chan == c == winner */
                        if (!ast_strlen_zero(c->call_forward)) {
                                pa->sentringing = 0;
+                               if (!ignore_cc && (f = ast_read(c))) {
+                                       if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_CC) {
+                                               /* This channel is forwarding the call, and is capable of CC, so
+                                                * be sure to add the new device interface to the list
+                                                */
+                                               ast_handle_cc_control_frame(in, c, f->data.ptr);
+                                       }
+                                       ast_frfree(f);
+                               }
                                do_forward(o, &num, peerflags, single, to);
                                continue;
                        }
@@ -1088,13 +1114,41 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                        handle_cause(AST_CAUSE_CONGESTION, &num);
                                        break;
                                case AST_CONTROL_RINGING:
-                                       ast_verb(3, "%s is ringing\n", c->name);
-                                       /* Setup early media if appropriate */
-                                       if (single && CAN_EARLY_BRIDGE(peerflags, in, c))
-                                               ast_channel_early_bridge(in, c);
-                                       if (!(pa->sentringing) && !ast_test_flag64(outgoing, OPT_MUSICBACK) && ast_strlen_zero(opt_args[OPT_ARG_RINGBACK])) {
-                                               ast_indicate(in, AST_CONTROL_RINGING);
-                                               pa->sentringing++;
+                                       /* This is a tricky area to get right when using a native
+                                        * CC agent. The reason is that we do the best we can to send only a
+                                        * single ringing notification to the caller.
+                                        *
+                                        * Call completion complicates the logic used here. CCNR is typically
+                                        * offered during a ringing message. Let's say that party A calls
+                                        * parties B, C, and D. B and C do not support CC requests, but D
+                                        * does. If we were to receive a ringing notification from B before
+                                        * the others, then we would end up sending a ringing message to
+                                        * A with no CCNR offer present.
+                                        *
+                                        * The approach that we have taken is that if we receive a ringing
+                                        * response from a party and no CCNR offer is present, we need to
+                                        * wait. Specifically, we need to wait until either a) a called party
+                                        * offers CCNR in its ringing response or b) all called parties have
+                                        * responded in some way to our call and none offers CCNR.
+                                        *
+                                        * The drawback to this is that if one of the parties has a delayed
+                                        * response or, god forbid, one just plain doesn't respond to our
+                                        * outgoing call, then this will result in a significant delay between
+                                        * when the caller places the call and hears ringback.
+                                        *
+                                        * Note also that if CC is disabled for this call, then it is perfectly
+                                        * fine for ringing frames to get sent through.
+                                        */
+                                       ++num_ringing;
+                                       if (ignore_cc || cc_frame_received || num_ringing == numlines) {
+                                               ast_verb(3, "%s is ringing\n", c->name);
+                                               /* Setup early media if appropriate */
+                                               if (single && CAN_EARLY_BRIDGE(peerflags, in, c))
+                                                       ast_channel_early_bridge(in, c);
+                                               if (!(pa->sentringing) && !ast_test_flag64(outgoing, OPT_MUSICBACK) && ast_strlen_zero(opt_args[OPT_ARG_RINGBACK])) {
+                                                       ast_indicate(in, AST_CONTROL_RINGING);
+                                                       pa->sentringing++;
+                                               }
                                        }
                                        break;
                                case AST_CONTROL_PROGRESS:
@@ -1163,6 +1217,12 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                case AST_CONTROL_FLASH:
                                        /* Ignore going off hook and flash */
                                        break;
+                               case AST_CONTROL_CC:
+                                       if (!ignore_cc) {
+                                               ast_handle_cc_control_frame(in, c, f->data.ptr);
+                                               cc_frame_received = 1;
+                                       }
+                                       break;
                                case -1:
                                        if (!ast_test_flag64(outgoing, OPT_RINGBACK | OPT_MUSICBACK)) {
                                                ast_verb(3, "%s stopped sounds\n", c->name);
@@ -1212,6 +1272,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                        }
                                        ast_frfree(f);
                                }
+                               if (is_cc_recall) {
+                                       ast_cc_completed(in, "CC completed, although the caller hung up (cancelled)");
+                               }
                                return NULL;
                        }
 
@@ -1229,6 +1292,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                                strcpy(pa->status, "CANCEL");
                                                ast_frfree(f);
                                                ast_channel_unlock(in);
+                                               if (is_cc_recall) {
+                                                       ast_cc_completed(in, "CC completed, but the caller used DTMF to exit");
+                                               }
                                                return NULL;
                                        }
                                        ast_channel_unlock(in);
@@ -1241,6 +1307,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                        strcpy(pa->status, "CANCEL");
                                        ast_cdr_noanswer(in->cdr);
                                        ast_frfree(f);
+                                       if (is_cc_recall) {
+                                               ast_cc_completed(in, "CC completed, but the caller hung up with DTMF");
+                                       }
                                        return NULL;
                                }
                        }
@@ -1283,6 +1352,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
        }
 #endif
 
+       if (is_cc_recall) {
+               ast_cc_completed(in, "Recall completed!");
+       }
        return peer;
 }
 
@@ -1656,6 +1728,8 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
        char *opt_args[OPT_ARG_ARRAY_SIZE];
        struct ast_datastore *datastore = NULL;
        int fulldial = 0, num_dialed = 0;
+       int ignore_cc = 0;
+       char device_name[AST_CHANNEL_NAME];
 
        /* Reset all DIAL variables back to blank, to prevent confusion (in case we don't reset all of them). */
        pbx_builtin_setvar_helper(chan, "DIALSTATUS", "");
@@ -1686,6 +1760,10 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                goto done;
        }
 
+       if (ast_cc_call_init(chan, &ignore_cc)) {
+               goto done;
+       }
+
        if (ast_test_flag64(&opts, OPT_SCREEN_NOINTRO) && !ast_strlen_zero(opt_args[OPT_ARG_SCREEN_NOINTRO])) {
                delprivintro = atoi(opt_args[OPT_ARG_SCREEN_NOINTRO]);
 
@@ -1871,8 +1949,17 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                        if (!rest) /* we are on the last destination */
                                chan->hangupcause = cause;
                        chanlist_free(tmp);
+                       if (!ignore_cc && (cause == AST_CAUSE_BUSY || cause == AST_CAUSE_CONGESTION)) {
+                               if (!ast_cc_callback(chan, tech, numsubst, ast_cc_busy_interface)) {
+                                       ast_cc_extension_monitor_add_dialstring(chan, interface, "");
+                               }
+                       }
                        continue;
                }
+               ast_channel_get_device_name(tc, device_name, sizeof(device_name));
+               if (!ignore_cc) {
+                       ast_cc_extension_monitor_add_dialstring(chan, interface, device_name);
+               }
                pbx_builtin_setvar_helper(tc, "DIALEDPEERNUMBER", numsubst);
 
                ast_channel_lock(tc);
@@ -1965,6 +2052,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                                chan->hangupcause = tc->hangupcause;
                        }
                        ast_channel_unlock(chan);
+                       ast_cc_call_failed(chan, tc, interface);
                        ast_hangup(tc);
                        tc = NULL;
                        chanlist_free(tmp);
@@ -2038,7 +2126,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                }
        }
 
-       peer = wait_for_answer(chan, outgoing, &to, peerflags, opt_args, &pa, &num, &result, dtmf_progress);
+       peer = wait_for_answer(chan, outgoing, &to, peerflags, opt_args, &pa, &num, &result, dtmf_progress, ignore_cc);
 
        /* The ast_channel_datastore_remove() function could fail here if the
         * datastore was moved to another channel during a masquerade. If this is
@@ -2513,6 +2601,7 @@ done:
        if (config.start_sound) {
                ast_free((char *)config.start_sound);
        }
+       ast_ignore_cc(chan);
        return res;
 }
 
index 356106e..d9ae8a6 100644 (file)
@@ -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));
index 5e522e7..b8052b0 100644 (file)
@@ -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;
index bd6cb18..91773b0 100644 (file)
@@ -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,3195 +1631,3779 @@ const struct ast_channel_tech sip_tech = {
  */
 struct ast_channel_tech sip_tech_info;
 
-/*! \brief Working TLS connection configuration */
-static struct ast_tls_config sip_tls_cfg;
-
-/*! \brief Default TLS connection configuration */
-static struct ast_tls_config default_tls_cfg;
+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);
 
-/*! \brief The TCP server definition */
-static struct ast_tcptls_session_args sip_tcp_desc = {
-       .accept_fd = -1,
-       .master = AST_PTHREADT_NULL,
-       .tls_cfg = NULL,
-       .poll_timeout = -1,
-       .name = "SIP TCP server",
-       .accept_fn = ast_tcptls_server_root,
-       .worker_fn = sip_tcp_worker_fn,
+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,
 };
 
-/*! \brief The TCP/TLS server definition */
-static struct ast_tcptls_session_args sip_tls_desc = {
-       .accept_fd = -1,
-       .master = AST_PTHREADT_NULL,
-       .tls_cfg = &sip_tls_cfg,
-       .poll_timeout = -1,
-       .name = "SIP TLS server",
-       .accept_fn = ast_tcptls_server_root,
-       .worker_fn = sip_tcp_worker_fn,
-};
+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;
 
-/*! \brief Append to SIP dialog history
-       \return Always returns 0 */
-#define append_history(p, event, fmt , args... )       append_history_full(p, "%-15s " fmt, event, ## args)
+       return !strcmp(agent_pvt->notify_uri, uri) ? CMP_MATCH | CMP_STOP : 0;
+}
 
-struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
+static struct ast_cc_agent *find_sip_cc_agent_by_notify_uri(const char * const uri)
 {
-       if (p)
-#ifdef REF_DEBUG
-               __ao2_ref_debug(p, 1, tag, file, line, func);
-#else
-               ao2_ref(p, 1);
-#endif
-       else
-               ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
-       return p;
+       struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_notify_uri_helper, (char *)uri, "SIP");
+       return agent;
 }
 
-struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
+static int find_by_subscribe_uri_helper(void *obj, void *arg, int flags)
 {
-       if (p)
-#ifdef REF_DEBUG
-               __ao2_ref_debug(p, -1, tag, file, line, func);
-#else
-               ao2_ref(p, -1);
-#endif
-       return NULL;
+       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;
 }
 
-/*! \brief map from an integer value to a string.
- * If no match is found, return errorstring
- */
-static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
+static struct ast_cc_agent *find_sip_cc_agent_by_subscribe_uri(const char * const uri)
 {
-       const struct _map_x_s *cur;
-
-       for (cur = table; cur->s; cur++)
-               if (cur->x == x)
-                       return cur->s;
-       return errorstring;
+       struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_subscribe_uri_helper, (char *)uri, "SIP");
+       return agent;
 }
 
-/*! \brief map from a string to an integer value, case insensitive.
- * If no match is found, return errorvalue.
- */
-static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
+static int find_by_callid_helper(void *obj, void *arg, int flags)
 {
-       const struct _map_x_s *cur;
+       struct ast_cc_agent *agent = obj;
+       struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+       struct sip_pvt *call_pvt = arg;
 
-       for (cur = table; cur->s; cur++)
-               if (!strcasecmp(cur->s, s))
-                       return cur->x;
-       return errorvalue;
+       return !strcmp(agent_pvt->original_callid, call_pvt->callid) ? CMP_MATCH | CMP_STOP : 0;
 }
 
-static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
+static struct ast_cc_agent *find_sip_cc_agent_by_original_callid(struct sip_pvt *pvt)
 {
-       enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
-       int i;
+       struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_callid_helper, pvt, "SIP");
+       return agent;
+}
 
-       for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
-               if (!strcasecmp(text, sip_reason_table[i].text)) {
-                       ast = sip_reason_table[i].code;
-                       break;
-               }
+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;
        }
 
-       return ast;
+       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 const char *sip_reason_code_to_str(enum AST_REDIRECTING_REASON code)
+static int sip_offer_timer_expire(const void *data)
 {
-       if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
-               return sip_reason_table[code].text;
-       }
+       struct ast_cc_agent *agent = (struct ast_cc_agent *) data;
+       struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
 
-       return "unknown";
+       agent_pvt->offer_timer_id = -1;
+
+       return ast_cc_failed(agent->core_id, "SIP agent %s's offer timer expired", agent->device_name);
 }
 
-/*!
- * \brief generic function for determining if a correct transport is being
- * used to contact a peer
- *
- * this is done as a macro so that the "tmpl" var can be passed either a
- * sip_request or a sip_peer
- */
-#define check_request_transport(peer, tmpl) ({ \
-       int ret = 0; \
-       if (peer->socket.type == tmpl->socket.type) \
-               ; \
-       else if (!(peer->transports & tmpl->socket.type)) {\
-               ast_log(LOG_ERROR, \
-                       "'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \
-                       get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \
-                       ); \
-               ret = 1; \
-       } else if (peer->socket.type & SIP_TRANSPORT_TLS) { \
-               ast_log(LOG_WARNING, \
-                       "peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \
-                       peer->name, get_transport(tmpl->socket.type) \
-               ); \
-       } else { \
-               ast_debug(1, \
-                       "peer '%s' has contacted us over %s even though we prefer %s.\n", \
-                       peer->name, get_transport(tmpl->socket.type), get_transport(peer->socket.type) \
-               ); \
-       }\
-       (ret); \
-})
+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;
 
-/*! \brief
- * duplicate a list of channel variables, \return the copy.
- */
-static struct ast_variable *copy_vars(struct ast_variable *src)
+       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 ast_variable *res = NULL, *tmp, *v = NULL;
+       struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
 
-       for (v = src ; v ; v = v->next) {
-               if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
-                       tmp->next = res;
-                       res = tmp;
-               }
-       }
-       return res;
+       AST_SCHED_DEL(sched, agent_pvt->offer_timer_id);
+       return 0;
 }
 
-static void tcptls_packet_destructor(void *obj)
+static void sip_cc_agent_ack(struct ast_cc_agent *agent)
 {
-       struct tcptls_packet *packet = obj;
+       struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
 
-       ast_free(packet->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 void sip_tcptls_client_args_destructor(void *obj)
+static int sip_cc_agent_status_request(struct ast_cc_agent *agent)
 {
-       struct ast_tcptls_session_args *args = obj;
-       if (args->tls_cfg) {
-               ast_free(args->tls_cfg->certfile);
-               ast_free(args->tls_cfg->pvtfile);
-               ast_free(args->tls_cfg->cipher);
-               ast_free(args->tls_cfg->cafile);
-               ast_free(args->tls_cfg->capath);
-       }
-       ast_free(args->tls_cfg);
-       ast_free((char *) args->name);
+       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 void sip_threadinfo_destructor(void *obj)
+static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent)
 {
-       struct sip_threadinfo *th = obj;
-       struct tcptls_packet *packet;
-       if (th->alert_pipe[1] > -1) {
-               close(th->alert_pipe[0]);
+       /* 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);
        }
-       if (th->alert_pipe[1] > -1) {
-               close(th->alert_pipe[1]);
+       /* 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;
        }
-       th->alert_pipe[0] = th->alert_pipe[1] = -1;
 
-       while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) {
-               ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue");
+       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);
+}
 
-       if (th->tcptls_session) {
-               ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object");
+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);
 }
 
-/*! \brief creates a sip_threadinfo object and links it into the threadt table. */
-static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport)
+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_threadinfo *th;
+       struct sip_monitor_instance *monitor_instance = ao2_alloc(sizeof(*monitor_instance), sip_monitor_instance_destructor);
 
-       if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) {
+       if (!monitor_instance) {
                return NULL;
        }
 
-       th->alert_pipe[0] = th->alert_pipe[1] = -1;
-
-       if (pipe(th->alert_pipe) == -1) {
-               ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo");
-               ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno));
+       if (ast_string_field_init(monitor_instance, 256)) {
+               ao2_ref(monitor_instance, -1);
                return NULL;
        }
-       ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_threadinfo object");
-       th->tcptls_session = tcptls_session;
-       th->type = transport ? transport : (tcptls_session->ssl ? SIP_TRANSPORT_TLS: SIP_TRANSPORT_TCP);
-       ao2_t_link(threadt, th, "Adding new tcptls helper thread");
-       ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains");
-       return th;
+
+       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;
 }
 
-/*! \brief used to indicate to a tcptls thread that data is ready to be written */
-static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len)
+static int find_sip_monitor_instance_by_subscription_pvt(void *obj, void *arg, int flags)
 {
-       int res = len;
-       struct sip_threadinfo *th = NULL;
-       struct tcptls_packet *packet = NULL;
-       struct sip_threadinfo tmp = {
-               .tcptls_session = tcptls_session,
-       };
-       enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA;
+       struct sip_monitor_instance *monitor_instance = obj;
+       return monitor_instance->subscription_pvt == arg ? CMP_MATCH | CMP_STOP : 0;
+}
 
-       if (!tcptls_session) {
-               return XMIT_ERROR;
-       }
+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;
+}
 
-       ast_mutex_lock(&tcptls_session->lock);
+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);
 
-       if ((tcptls_session->fd == -1) ||
-               !(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) ||
-               !(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) ||
-               !(packet->data = ast_str_create(len))) {
-               goto tcptls_write_setup_error;
-       }
+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,
+};
 
-       /* goto tcptls_write_error should _NOT_ be used beyond this point */
-       ast_str_set(&packet->data, 0, "%s", (char *) buf);
-       packet->len = len;
+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;
 
-       /* alert tcptls thread handler that there is a packet to be sent.
-        * must lock the thread info object to guarantee control of the
-        * packet queue */
-       ao2_lock(th);
-       if (write(th->alert_pipe[1], &alert, sizeof(alert)) == -1) {
-               ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n", strerror(errno));
-               ao2_t_ref(packet, -1, "could not write to alert pipe, remove packet");
-               packet = NULL;
-               res = XMIT_ERROR;
-       } else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */
-               AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry);
+       if (!monitor_instance) {
+               return -1;
        }
-       ao2_unlock(th);
-
-       ast_mutex_unlock(&tcptls_session->lock);
-       ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it");
-       return res;
 
-tcptls_write_setup_error:
-       if (th) {
-               ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo obj, could not create packet");
-       }
-       if (packet) {
-               ao2_t_ref(packet, -1, "could not allocate packet's data");
+       if (!(monitor_instance->subscription_pvt = sip_alloc(NULL, NULL, 0, SIP_SUBSCRIBE, NULL))) {
+               return -1;
        }
-       ast_mutex_unlock(&tcptls_session->lock);
 
-       return XMIT_ERROR;
+       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;
 }
 
-/*! \brief SIP TCP connection handler */
-static void *sip_tcp_worker_fn(void *data)
+static int construct_pidf_body(enum sip_cc_publish_state state, char *pidf_body, size_t size, const char *presentity)
 {
-       struct ast_tcptls_session_instance *tcptls_session = data;
+       struct ast_str *body = ast_str_alloca(size);
+       char tuple_id[32];
 
-       return _sip_tcp_helper_thread(NULL, tcptls_session);
+       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;
 }
 
-/*! \brief SIP TCP thread management function
-       This function reads from the socket, parses the packet into a request
-*/
-static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_session_instance *tcptls_session)
+static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor)
 {
-       int res, cl;
-       struct sip_request req = { 0, } , reqcpy = { 0, };
-       struct sip_threadinfo *me = NULL;
-       char buf[1024] = "";
-       struct pollfd fds[2] = { { 0 }, { 0 }, };
-       struct ast_tcptls_session_args *ca = NULL;
+       struct sip_monitor_instance *monitor_instance = monitor->private_data;
+       enum sip_publish_type publish_type;
+       struct cc_epa_entry *cc_entry;
 
-       /* If this is a server session, then the connection has already been setup,
-        * simply create the threadinfo object so we can access this thread for writing.
-        * 
-        * if this is a client connection more work must be done.
-        * 1. We own the parent session args for a client connection.  This pointer needs
-        *    to be held on to so we can decrement it's ref count on thread destruction.
-        * 2. The threadinfo object was created before this thread was launched, however
-        *    it must be found within the threadt table.
-        * 3. Last, the tcptls_session must be started.
-        */
-       if (!tcptls_session->client) {
-               if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) {
-                       goto cleanup;
+       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;
                }
-               ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
+               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 {
-               struct sip_threadinfo tmp = {
-                       .tcptls_session = tcptls_session,
-               };
+               publish_type = SIP_PUBLISH_MODIFY;
+               cc_entry = monitor_instance->suspension_entry->instance_data;
+       }
 
-               if ((!(ca = tcptls_session->parent)) ||
-                       (!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
-                       (!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
-                       goto cleanup;
-               }
+       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);
+}
 
-       me->threadid = pthread_self();
-       ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
+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;
+}
 
-       /* set up pollfd to watch for reads on both the socket and the alert_pipe */
-       fds[0].fd = tcptls_session->fd;
-       fds[1].fd = me->alert_pipe[0];
-       fds[0].events = fds[1].events = POLLIN | POLLPRI;
+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 (!(req.data = ast_str_create(SIP_MIN_PACKET)))
-               goto cleanup;
-       if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET)))
-               goto cleanup;
+       if (!monitor_instance) {
+               return -1;
+       }
 
-       for (;;) {
-               struct ast_str *str_save;
+       ast_assert(monitor_instance->suspension_entry != NULL);
 
-               res = ast_poll(fds, 2, -1); /* polls for both socket and alert_pipe */
-               if (res < 0) {
-                       ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
-                       goto cleanup;
-               }
+       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);
+}
 
-               /* handle the socket event, check for both reads from the socket fd,
-                * and writes from alert_pipe fd */
-               if (fds[0].revents) { /* there is data on the socket to be read */
+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;
+}
 
-                       fds[0].revents = 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);
+}
 
-                       /* clear request structure */
-                       str_save = req.data;
-                       memset(&req, 0, sizeof(req));
-                       req.data = str_save;
-                       ast_str_reset(req.data);
+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;
 
-                       str_save = reqcpy.data;
-                       memset(&reqcpy, 0, sizeof(reqcpy));
-                       reqcpy.data = str_save;
-                       ast_str_reset(reqcpy.data);
+       if (ast_strlen_zero(call_info)) {
+               /* No Call-Info present. Definitely no CC offer */
+               return -1;
+       }
 
-                       memset(buf, 0, sizeof(buf));
+       uri = strsep(&call_info, ";");
 
-                       if (tcptls_session->ssl) {
-                               set_socket_transport(&req.socket, SIP_TRANSPORT_TLS);
-                               req.socket.port = htons(ourport_tls);
-                       } else {
-                               set_socket_transport(&req.socket, SIP_TRANSPORT_TCP);
-                               req.socket.port = htons(ourport_tcp);
-                       }
-                       req.socket.fd = tcptls_session->fd;
+       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;
+       }
 
-                       /* Read in headers one line at a time */
-                       while (req.len < 4 || strncmp(REQ_OFFSET_TO_STR(&req, len - 4), "\r\n\r\n", 4)) {
-                               ast_mutex_lock(&tcptls_session->lock);
-                               if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
-                                       ast_mutex_unlock(&tcptls_session->lock);
-                                       goto cleanup;
-                               }
-                               ast_mutex_unlock(&tcptls_session->lock);
-                               if (me->stop)
-                                        goto cleanup;
-                               ast_str_append(&req.data, 0, "%s", buf);
-                               req.len = req.data->used;
-                       }
-                       copy_request(&reqcpy, &req);
-                       parse_request(&reqcpy);
-                       /* In order to know how much to read, we need the content-length header */
-                       if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
-                               while (cl > 0) {
-                                       size_t bytes_read;
-                                       ast_mutex_lock(&tcptls_session->lock);
-                                       if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
-                                               ast_mutex_unlock(&tcptls_session->lock);
-                                               goto cleanup;
-                                       }
-                                       buf[bytes_read] = '\0';
-                                       ast_mutex_unlock(&tcptls_session->lock);
-                                       if (me->stop)
-                                               goto cleanup;
-                                       cl -= strlen(buf);
-                                       ast_str_append(&req.data, 0, "%s", buf);
-                                       req.len = req.data->used;
-                               }
-                       }
-                       /*! \todo XXX If there's no Content-Length or if the content-length and what
-                                       we receive is not the same - we should generate an error */
-
-                       req.socket.tcptls_session = tcptls_session;
-                       handle_request_do(&req, &tcptls_session->remote_address);
+       /* 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 (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
-                       enum sip_tcptls_alert alert;
-                       struct tcptls_packet *packet;
-
-                       fds[1].revents = 0;
-
-                       if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) {
-                               ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
-                               continue;
-                       }
+       if ((*service = service_string_to_service_type(service_str)) == AST_CC_NONE) {
+               /* Invalid service offered */
+               return -1;
+       }
 
-                       switch (alert) {
-                       case TCPTLS_ALERT_STOP:
-                               goto cleanup;
-                       case TCPTLS_ALERT_DATA:
-                               ao2_lock(me);
-                               if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) {
-                                       ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty");
-                               } else if (ast_tcptls_server_write(tcptls_session, ast_str_buffer(packet->data), packet->len) == -1) {
-                                       ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n");
-                               }
+       ast_copy_string(subscribe_uri, get_in_brackets(uri), size);
 
-                               if (packet) {
-                                       ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed");
-                               }
-                               ao2_unlock(me);
-                               break;
-                       default:
-                               ast_log(LOG_ERROR, "Unknown tcptls thread alert '%d'\n", alert);
-                       }
-               }
-       }
+       return 0;
+}
 
-       ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
+/*
+ * \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];
 
-cleanup:
-       if (me) {
-               ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing");
-               ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref");
-       }
-       if (reqcpy.data) {
-               ast_free(reqcpy.data);
+       if (monitor_policy == AST_CC_MONITOR_NEVER) {
+               /* Don't bother, just return */
+               return;
        }
 
-       if (req.data) {
-               ast_free(req.data);
-               req.data = NULL;
+       if ((core_id = ast_cc_get_current_core_id(pvt->owner)) == -1) {
+               /* For some reason, CC is invalid, so don't try it! */
+               return;
        }
 
-       /* if client, we own the parent session arguments and must decrement ref */
-       if (ca) {
-               ao2_t_ref(ca, -1, "closing tcptls thread, getting rid of client tcptls_session arguments");
-       }
+       ast_channel_get_device_name(pvt->owner, interface_name, sizeof(interface_name));
 
-       if (tcptls_session) {
-               ast_mutex_lock(&tcptls_session->lock);
-               if (tcptls_session->f) {
-                       fclose(tcptls_session->f);
-                       tcptls_session->f = NULL;
+       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;
                }
-               if (tcptls_session->fd != -1) {
-                       close(tcptls_session->fd);
-                       tcptls_session->fd = -1;
+               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;
                }
-               tcptls_session->parent = NULL;
-               ast_mutex_unlock(&tcptls_session->lock);
+               /* 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;
+       }
 
-               ao2_ref(tcptls_session, -1);
-               tcptls_session = NULL;
+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);
        }
-       return NULL;
 }
 
+/*! \brief Working TLS connection configuration */
+static struct ast_tls_config sip_tls_cfg;
 
-/*!
- * helper functions to unreference various types of objects.
- * By handling them this way, we don't have to declare the
- * destructor on each call, which removes the chance of errors.
- */
-static void *unref_peer(struct sip_peer *peer, char *tag)
+/*! \brief Default TLS connection configuration */
+static struct ast_tls_config default_tls_cfg;
+
+/*! \brief The TCP server definition */
+static struct ast_tcptls_session_args sip_tcp_desc = {
+       .accept_fd = -1,
+       .master = AST_PTHREADT_NULL,
+       .tls_cfg = NULL,
+       .poll_timeout = -1,
+       .name = "SIP TCP server",
+       .accept_fn = ast_tcptls_server_root,
+       .worker_fn = sip_tcp_worker_fn,
+};
+
+/*! \brief The TCP/TLS server definition */
+static struct ast_tcptls_session_args sip_tls_desc = {
+       .accept_fd = -1,
+       .master = AST_PTHREADT_NULL,
+       .tls_cfg = &sip_tls_cfg,
+       .poll_timeout = -1,
+       .name = "SIP TLS server",
+       .accept_fn = ast_tcptls_server_root,
+       .worker_fn = sip_tcp_worker_fn,
+};
+
+/*! \brief Append to SIP dialog history
+       \return Always returns 0 */
+#define append_history(p, event, fmt , args... )       append_history_full(p, "%-15s " fmt, event, ## args)
+
+struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
 {
-       ao2_t_ref(peer, -1, tag);
-       return NULL;
+       if (p)
+#ifdef REF_DEBUG
+               __ao2_ref_debug(p, 1, tag, file, line, func);
+#else
+               ao2_ref(p, 1);
+#endif
+       else
+               ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+       return p;
 }
 
-static struct sip_peer *ref_peer(struct sip_peer *peer, char *tag)
+struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
 {
-       ao2_t_ref(peer, 1, tag);
-       return peer;
+       if (p)
+#ifdef REF_DEBUG
+               __ao2_ref_debug(p, -1, tag, file, line, func);
+#else
+               ao2_ref(p, -1);
+#endif
+       return NULL;
 }
 
-/*! \brief maintain proper refcounts for a sip_pvt's outboundproxy
- *
- * This function sets pvt's outboundproxy pointer to the one referenced
- * by the proxy parameter. Because proxy may be a refcounted object, and
- * because pvt's old outboundproxy may also be a refcounted object, we need
- * to maintain the proper refcounts.
- *
- * \param pvt The sip_pvt for which we wish to set the outboundproxy
- * \param proxy The sip_proxy which we will point pvt towards.
- * \return Returns void
+/*! \brief map from an integer value to a string.
+ * If no match is found, return errorstring
  */
-static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
+static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
 {
-       struct sip_proxy *old_obproxy = pvt->outboundproxy;
-       /* The sip_cfg.outboundproxy is statically allocated, and so
-        * we don't ever need to adjust refcounts for it
-        */
-       if (proxy && proxy != &sip_cfg.outboundproxy) {
-               ao2_ref(proxy, +1);
-       }
-       pvt->outboundproxy = proxy;
-       if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
-               ao2_ref(old_obproxy, -1);
-       }
+       const struct _map_x_s *cur;
+
+       for (cur = table; cur->s; cur++)
+               if (cur->x == x)
+                       return cur->s;
+       return errorstring;
 }
 
-/*!
- * \brief Unlink a dialog from the dialogs container, as well as any other places
- * that it may be currently stored.
- *
- * \note A reference to the dialog must be held before calling this function, and this
- * function does not release that reference.
+/*! \brief map from a string to an integer value, case insensitive.
+ * If no match is found, return errorvalue.
  */
-void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist)
+static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
 {
-       struct sip_pkt *cp;
-
-       dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
+       const struct _map_x_s *cur;
 
-       ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
+       for (cur = table; cur->s; cur++)
+               if (!strcasecmp(cur->s, s))
+                       return cur->x;
+       return errorvalue;
+}
 
-       /* Unlink us from the owner (channel) if we have one */
-       if (dialog->owner) {
-               if (lockowner)
-                       ast_channel_lock(dialog->owner);
-               ast_debug(1, "Detaching from channel %s\n", dialog->owner->name);
-               dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
-               if (lockowner)
-                       ast_channel_unlock(dialog->owner);
-       }
-       if (dialog->registry) {
-               if (dialog->registry->call == dialog)
-                       dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
-               dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
-       }
-       if (dialog->stateid > -1) {
-               ast_extension_state_del(dialog->stateid, NULL);
-               dialog_unref(dialog, "removing extension_state, should unref the associated dialog ptr that was stored there.");
-               dialog->stateid = -1; /* shouldn't we 'zero' this out? */
-       }
-       /* Remove link from peer to subscription of MWI */
-       if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog)
-               dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
-       if (dialog->relatedpeer && dialog->relatedpeer->call == dialog)
-               dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
+static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
+{
+       enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
+       int i;
 
-       /* remove all current packets in this dialog */
-       while((cp = dialog->packets)) {
-               dialog->packets = dialog->packets->next;
-               AST_SCHED_DEL(sched, cp->retransid);
-               dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
-               if (cp->data) {
-                       ast_free(cp->data);
+       for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
+               if (!strcasecmp(text, sip_reason_table[i].text)) {
+                       ast = sip_reason_table[i].code;
+                       break;
                }
-               ast_free(cp);
        }
 
-       AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
+       return ast;
+}
 
-       AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr"));
-       
-       if (dialog->autokillid > -1)
-               AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr"));
-
-       if (dialog->request_queue_sched_id > -1) {
-               AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr"));
-       }
-
-       AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
-
-       if (dialog->t38id > -1) {
-               AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
+static const char *sip_reason_code_to_str(enum AST_REDIRECTING_REASON code)
+{
+       if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
+               return sip_reason_table[code].text;
        }
 
-       dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
-       return NULL;
+       return "unknown";
 }
 
-void *registry_unref(struct sip_registry *reg, char *tag)
-{
-       ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1);
-       ASTOBJ_UNREF(reg, sip_registry_destroy);
-       return NULL;
-}
+/*!
+ * \brief generic function for determining if a correct transport is being
+ * used to contact a peer
+ *
+ * this is done as a macro so that the "tmpl" var can be passed either a
+ * sip_request or a sip_peer
+ */
+#define check_request_transport(peer, tmpl) ({ \
+       int ret = 0; \
+       if (peer->socket.type == tmpl->socket.type) \
+               ; \
+       else if (!(peer->transports & tmpl->socket.type)) {\
+               ast_log(LOG_ERROR, \
+                       "'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \
+                       get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \
+                       ); \
+               ret = 1; \
+       } else if (peer->socket.type & SIP_TRANSPORT_TLS) { \
+               ast_log(LOG_WARNING, \
+                       "peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \
+                       peer->name, get_transport(tmpl->socket.type) \
+               ); \
+       } else { \
+               ast_debug(1, \
+                       "peer '%s' has contacted us over %s even though we prefer %s.\n", \
+                       peer->name, get_transport(tmpl->socket.type), get_transport(peer->socket.type) \
+               ); \
+       }\
+       (ret); \
+})
 
-/*! \brief Add object reference to SIP registry */
-static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag)
+/*! \brief
+ * duplicate a list of channel variables, \return the copy.
+ */
+static struct ast_variable *copy_vars(struct ast_variable *src)
 {
-       ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
-       return ASTOBJ_REF(reg); /* Add pointer to registry in packet */
-}
-
-/*! \brief Interface structure with callbacks used to connect to UDPTL module*/
-static struct ast_udptl_protocol sip_udptl = {
-       type: "SIP",
-       get_udptl_info: sip_get_udptl_peer,
-       set_udptl_peer: sip_set_udptl_peer,
-};
-
-static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
-       __attribute__((format(printf, 2, 3)));
-
+       struct ast_variable *res = NULL, *tmp, *v = NULL;
 
-/*! \brief Convert transfer status to string */
-static const char *referstatus2str(enum referstatus rstatus)
-{
-       return map_x_s(referstatusstrings, rstatus, "");
+       for (v = src ; v ; v = v->next) {
+               if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
+                       tmp->next = res;
+                       res = tmp;
+               }
+       }
+       return res;
 }
 
-static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
+static void tcptls_packet_destructor(void *obj)
 {
-       append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason);
-       pvt->needdestroy = 1;
-}
+       struct tcptls_packet *packet = obj;
 
-/*! \brief Initialize the initital request packet in the pvt structure.
-       This packet is used for creating replies and future requests in
-       a dialog */
-static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
-{
-       if (p->initreq.headers)
-               ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
-       else
-               ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
-       /* Use this as the basis */
-       copy_request(&p->initreq, req);
-       parse_request(&p->initreq);
-       if (req->debug)
-               ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+       ast_free(packet->data);
 }
 
-/*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */
-static void sip_alreadygone(struct sip_pvt *dialog)
+static void sip_tcptls_client_args_destructor(void *obj)
 {
-       ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
-       dialog->alreadygone = 1;
+       struct ast_tcptls_session_args *args = obj;
+       if (args->tls_cfg) {
+               ast_free(args->tls_cfg->certfile);
+               ast_free(args->tls_cfg->pvtfile);
+               ast_free(args->tls_cfg->cipher);
+               ast_free(args->tls_cfg->cafile);
+               ast_free(args->tls_cfg->capath);
+       }
+       ast_free(args->tls_cfg);
+       ast_free((char *) args->name);
 }
 
-/*! Resolve DNS srv name or host name in a sip_proxy structure */
-static int proxy_update(struct sip_proxy *proxy)
+static void sip_threadinfo_destructor(void *obj)
 {
-       /* if it's actually an IP address and not a name,
-           there's no need for a managed lookup */
-       if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
-               /* Ok, not an IP address, then let's check if it's a domain or host */
-               /* XXX Todo - if we have proxy port, don't do SRV */
-               if (ast_get_ip_or_srv(&proxy->ip, proxy->name, sip_cfg.srvlookup ? "_sip._udp" : NULL) < 0) {
-                       ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
-                       return FALSE;
-               }
+       struct sip_threadinfo *th = obj;
+       struct tcptls_packet *packet;
+       if (th->alert_pipe[1] > -1) {
+               close(th->alert_pipe[0]);
        }
-       proxy->last_dnsupdate = time(NULL);
-       return TRUE;
-}
+       if (th->alert_pipe[1] > -1) {
+               close(th->alert_pipe[1]);
+       }
+       th->alert_pipe[0] = th->alert_pipe[1] = -1;
 
-/*! \brief converts ascii port to int representation. If no
- *  pt buffer is provided or the pt has errors when being converted
- *  to an int value, the port provided as the standard is used.
- */
-unsigned int port_str2int(const char *pt, unsigned int standard)
-{
-       int port = standard;
-       if (ast_strlen_zero(pt) || (sscanf(pt, "%30d", &port) != 1) || (port < 1) || (port > 65535)) {
-               port = standard;
+       while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) {
+               ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue");
        }
 
-       return port;
+       if (th->tcptls_session) {
+               ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object");
+       }
 }
 
-/*! \brief Allocate and initialize sip proxy */
-static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
+/*! \brief creates a sip_threadinfo object and links it into the threadt table. */
+static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport)
 {
-       struct sip_proxy *proxy;
+       struct sip_threadinfo *th;
 
-       if (ast_strlen_zero(name)) {
+       if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) {
                return NULL;
        }
 
-       proxy = ao2_alloc(sizeof(*proxy), NULL);
-       if (!proxy)
-               return NULL;
-       proxy->force = force;
-       ast_copy_string(proxy->name, name, sizeof(proxy->name));
-       proxy->ip.sin_port = htons(port_str2int(port, STANDARD_SIP_PORT));
-       proxy_update(proxy);
-       return proxy;
-}
+       th->alert_pipe[0] = th->alert_pipe[1] = -1;
 
-/*! \brief Get default outbound proxy or global proxy */
-static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
-{
-       if (peer && peer->outboundproxy) {
-               if (sipdebug)
-                       ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
-               append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
-               return peer->outboundproxy;
-       }
-       if (sip_cfg.outboundproxy.name[0]) {
-               if (sipdebug)
-                       ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
-               append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name);
-               return &sip_cfg.outboundproxy;
+       if (pipe(th->alert_pipe) == -1) {
+               ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo");
+               ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno));
+               return NULL;
        }
-       if (sipdebug)
-               ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
-       return NULL;
+       ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_threadinfo object");
+       th->tcptls_session = tcptls_session;
+       th->type = transport ? transport : (tcptls_session->ssl ? SIP_TRANSPORT_TLS: SIP_TRANSPORT_TCP);
+       ao2_t_link(threadt, th, "Adding new tcptls helper thread");
+       ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains");
+       return th;
 }
 
-/*! \brief returns true if 'name' (with optional trailing whitespace)
- * matches the sip method 'id'.
- * Strictly speaking, SIP methods are case SENSITIVE, but we do
- * a case-insensitive comparison to be more tolerant.
- * following Jon Postel's rule: Be gentle in what you accept, strict with what you send
- */
-static int method_match(enum sipmethod id, const char *name)
+/*! \brief used to indicate to a tcptls thread that data is ready to be written */
+static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len)
 {
-       int len = strlen(sip_methods[id].text);
-       int l_name = name ? strlen(name) : 0;
-       /* true if the string is long enough, and ends with whitespace, and matches */
-       return (l_name >= len && name[len] < 33 &&
-               !strncasecmp(sip_methods[id].text, name, len));
-}
+       int res = len;
+       struct sip_threadinfo *th = NULL;
+       struct tcptls_packet *packet = NULL;
+       struct sip_threadinfo tmp = {
+               .tcptls_session = tcptls_session,
+       };
+       enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA;
 
-/*! \brief  find_sip_method: Find SIP method from header */
-static int find_sip_method(const char *msg)
-{
-       int i, res = 0;
-       
-       if (ast_strlen_zero(msg))
-               return 0;
-       for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) {
-               if (method_match(i, msg))
-                       res = sip_methods[i].id;
+       if (!tcptls_session) {
+               return XMIT_ERROR;
        }
-       return res;
-}
-
-/*! \brief Parse supported header in incoming packet */
-static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
-{
-       char *next, *sep;
-       char *temp;
-       unsigned int profile = 0;
-       int i, found;
 
-       if (ast_strlen_zero(supported) )
-               return 0;
-       temp = ast_strdupa(supported);
+       ast_mutex_lock(&tcptls_session->lock);
 
-       if (sipdebug)
-               ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
-
-       for (next = temp; next; next = sep) {
-               found = FALSE;
-               if ( (sep = strchr(next, ',')) != NULL)
-                       *sep++ = '\0';
-               next = ast_skip_blanks(next);
-               if (sipdebug)
-                       ast_debug(3, "Found SIP option: -%s-\n", next);
-               for (i = 0; i < ARRAY_LEN(sip_options); i++) {
-                       if (!strcasecmp(next, sip_options[i].text)) {
-                               profile |= sip_options[i].id;
-                               found = TRUE;
-                               if (sipdebug)
-                                       ast_debug(3, "Matched SIP option: %s\n", next);
-                               break;
-                       }
-               }
+       if ((tcptls_session->fd == -1) ||
+               !(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) ||
+               !(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) ||
+               !(packet->data = ast_str_create(len))) {
+               goto tcptls_write_setup_error;
+       }
 
-               /* This function is used to parse both Suported: and Require: headers.
-               Let the caller of this function know that an unknown option tag was
-               encountered, so that if the UAC requires it then the request can be
-               rejected with a 420 response. */
-               if (!found)
-                       profile |= SIP_OPT_UNKNOWN;
+       /* goto tcptls_write_error should _NOT_ be used beyond this point */
+       ast_str_set(&packet->data, 0, "%s", (char *) buf);
+       packet->len = len;
 
-               if (!found && sipdebug) {
-                       if (!strncasecmp(next, "x-", 2))
-                               ast_debug(3, "Found private SIP option, not supported: %s\n", next);
-                       else
-                               ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
-               }
+       /* alert tcptls thread handler that there is a packet to be sent.
+        * must lock the thread info object to guarantee control of the
+        * packet queue */
+       ao2_lock(th);
+       if (write(th->alert_pipe[1], &alert, sizeof(alert)) == -1) {
+               ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n", strerror(errno));
+               ao2_t_ref(packet, -1, "could not write to alert pipe, remove packet");
+               packet = NULL;
+               res = XMIT_ERROR;
+       } else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */
+               AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry);
        }
+       ao2_unlock(th);
 
-       if (pvt)
-               pvt->sipoptions = profile;
-       return profile;
-}
+       ast_mutex_unlock(&tcptls_session->lock);
+       ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it");
+       return res;
 
-/*! \brief See if we pass debug IP filter */
-static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
-{
-       if (!sipdebug)
-               return 0;
-       if (debugaddr.sin_addr.s_addr) {
-               if (((ntohs(debugaddr.sin_port) != 0)
-                       && (debugaddr.sin_port != addr->sin_port))
-                       || (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
-                       return 0;
+tcptls_write_setup_error:
+       if (th) {
+               ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo obj, could not create packet");
        }
-       return 1;
-}
-
-/*! \brief The real destination address for a write */
-static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
-{
-       if (p->outboundproxy)
-               return &p->outboundproxy->ip;
+       if (packet) {
+               ao2_t_ref(packet, -1, "could not allocate packet's data");
+       }
+       ast_mutex_unlock(&tcptls_session->lock);
 
-       return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa;
+       return XMIT_ERROR;
 }
 
-/*! \brief Display SIP nat mode */
-static const char *sip_nat_mode(const struct sip_pvt *p)
+/*! \brief SIP TCP connection handler */
+static void *sip_tcp_worker_fn(void *data)
 {
-       return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT";
-}
+       struct ast_tcptls_session_instance *tcptls_session = data;
 
-/*! \brief Test PVT for debugging output */
-static inline int sip_debug_test_pvt(struct sip_pvt *p)
-{
-       if (!sipdebug)
-               return 0;
-       return sip_debug_test_addr(sip_real_dst(p));
+       return _sip_tcp_helper_thread(NULL, tcptls_session);
 }
 
-/*! \brief Return int representing a bit field of transport types found in const char *transport */
-static int get_transport_str2enum(const char *transport)
+/*! \brief SIP TCP thread management function
+       This function reads from the socket, parses the packet into a request
+*/
+static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_session_instance *tcptls_session)
 {
-       int res = 0;
+       int res, cl;
+       struct sip_request req = { 0, } , reqcpy = { 0, };
+       struct sip_threadinfo *me = NULL;
+       char buf[1024] = "";
+       struct pollfd fds[2] = { { 0 }, { 0 }, };
+       struct ast_tcptls_session_args *ca = NULL;
 
-       if (ast_strlen_zero(transport)) {
-               return res;
-       }
+       /* If this is a server session, then the connection has already been setup,
+        * simply create the threadinfo object so we can access this thread for writing.
+        * 
+        * if this is a client connection more work must be done.
+        * 1. We own the parent session args for a client connection.  This pointer needs
+        *    to be held on to so we can decrement it's ref count on thread destruction.
+        * 2. The threadinfo object was created before this thread was launched, however
+        *    it must be found within the threadt table.
+        * 3. Last, the tcptls_session must be started.
+        */
+       if (!tcptls_session->client) {
+               if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) {
+                       goto cleanup;
+               }
+               ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
+       } else {
+               struct sip_threadinfo tmp = {
+                       .tcptls_session = tcptls_session,
+               };
 
-       if (!strcasecmp(transport, "udp")) {
-               res |= SIP_TRANSPORT_UDP;
-       }
-       if (!strcasecmp(transport, "tcp")) {
-               res |= SIP_TRANSPORT_TCP;
-       }
-       if (!strcasecmp(transport, "tls")) {
-               res |= SIP_TRANSPORT_TLS;
+               if ((!(ca = tcptls_session->parent)) ||
+                       (!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
+                       (!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
+                       goto cleanup;
+               }
        }
 
-       return res;
-}
+       me->threadid = pthread_self();
+       ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
 
-/*! \brief Return configuration of transports for a device */
-static inline const char *get_transport_list(unsigned int transports) {
-       switch (transports) {
-               case SIP_TRANSPORT_UDP:
-                       return "UDP";
-               case SIP_TRANSPORT_TCP:
-                       return "TCP";
-               case SIP_TRANSPORT_TLS:
-                       return "TLS";
-               case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
-                       return "TCP,UDP";
-               case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
-                       return "TLS,UDP";
-               case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
-                       return "TLS,TCP";
-               default:
-                       return transports ?
-                               "TLS,TCP,UDP" : "UNKNOWN";      
-       }
-}
+       /* set up pollfd to watch for reads on both the socket and the alert_pipe */
+       fds[0].fd = tcptls_session->fd;
+       fds[1].fd = me->alert_pipe[0];
+       fds[0].events = fds[1].events = POLLIN | POLLPRI;
 
-/*! \brief Return transport as string */
-static inline const char *get_transport(enum sip_transport t)
-{
-       switch (t) {
-       case SIP_TRANSPORT_UDP:
-               return "UDP";
-       case SIP_TRANSPORT_TCP:
-               return "TCP";
-       case SIP_TRANSPORT_TLS:
-               return "TLS";
-       }
+       if (!(req.data = ast_str_create(SIP_MIN_PACKET)))
+               goto cleanup;
+       if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET)))
+               goto cleanup;
 
-       return "UNKNOWN";
-}
+       for (;;) {
+               struct ast_str *str_save;
 
-/*! \brief Return transport of dialog.
-       \note this is based on a false assumption. We don't always use the
-       outbound proxy for all requests in a dialog. It depends on the
-       "force" parameter. The FIRST request is always sent to the ob proxy.
-       \todo Fix this function to work correctly
-*/
-static inline const char *get_transport_pvt(struct sip_pvt *p)
-{
-       if (p->outboundproxy && p->outboundproxy->transport) {
-               set_socket_transport(&p->socket, p->outboundproxy->transport);
-       }
+               res = ast_poll(fds, 2, -1); /* polls for both socket and alert_pipe */
+               if (res < 0) {
+                       ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
+                       goto cleanup;
+               }
 
-       return get_transport(p->socket.type);
-}
+               /* handle the socket event, check for both reads from the socket fd,
+                * and writes from alert_pipe fd */
+               if (fds[0].revents) { /* there is data on the socket to be read */
 
-/*! \brief Transmit SIP message
-       Sends a SIP request or response on a given socket (in the pvt)
-       Called by retrans_pkt, send_request, send_response and
-       __sip_reliable_xmit
-       \return length of transmitted message, XMIT_ERROR on known network failures -1 on other failures.
-*/
-static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len)
-{
-       int res = 0;
-       const struct sockaddr_in *dst = sip_real_dst(p);
+                       fds[0].revents = 0;
 
-       ast_debug(2, "Trying to put '%.11s' onto %s socket destined for %s:%d\n", data->str, get_transport_pvt(p), ast_inet_ntoa(dst->sin_addr), htons(dst->sin_port));
+                       /* clear request structure */
+                       str_save = req.data;
+                       memset(&req, 0, sizeof(req));
+                       req.data = str_save;
+                       ast_str_reset(req.data);
 
-       if (sip_prepare_socket(p) < 0)
-               return XMIT_ERROR;
+                       str_save = reqcpy.data;
+                       memset(&reqcpy, 0, sizeof(reqcpy));
+                       reqcpy.data = str_save;
+                       ast_str_reset(reqcpy.data);
 
-       if (p->socket.type == SIP_TRANSPORT_UDP) {
-               res = sendto(p->socket.fd, data->str, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
-       } else if (p->socket.tcptls_session) {
-               res = sip_tcptls_write(p->socket.tcptls_session, data->str, len);
-       } else {
-               ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
-               return XMIT_ERROR;
-       }
+                       memset(buf, 0, sizeof(buf));
 
-       if (res == -1) {
-               switch (errno) {
-               case EBADF:             /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
-               case EHOSTUNREACH:      /* Host can't be reached */
-               case ENETDOWN:          /* Interface down */
-               case ENETUNREACH:       /* Network failure */
-               case ECONNREFUSED:      /* ICMP port unreachable */
-                       res = XMIT_ERROR;       /* Don't bother with trying to transmit again */
-               }
-       }
-       if (res != len)
-               ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
+                       if (tcptls_session->ssl) {
+                               set_socket_transport(&req.socket, SIP_TRANSPORT_TLS);
+                               req.socket.port = htons(ourport_tls);
+                       } else {
+                               set_socket_transport(&req.socket, SIP_TRANSPORT_TCP);
+                               req.socket.port = htons(ourport_tcp);
+                       }
+                       req.socket.fd = tcptls_session->fd;
 
-       return res;
-}
+                       /* Read in headers one line at a time */
+                       while (req.len < 4 || strncmp(REQ_OFFSET_TO_STR(&req, len - 4), "\r\n\r\n", 4)) {
+                               ast_mutex_lock(&tcptls_session->lock);
+                               if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
+                                       ast_mutex_unlock(&tcptls_session->lock);
+                                       goto cleanup;
+                               }
+                               ast_mutex_unlock(&tcptls_session->lock);
+                               if (me->stop)
+                                        goto cleanup;
+                               ast_str_append(&req.data, 0, "%s", buf);
+                               req.len = req.data->used;
+                       }
+                       copy_request(&reqcpy, &req);
+                       parse_request(&reqcpy);
+                       /* In order to know how much to read, we need the content-length header */
+                       if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
+                               while (cl > 0) {
+                                       size_t bytes_read;
+                                       ast_mutex_lock(&tcptls_session->lock);
+                                       if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
+                                               ast_mutex_unlock(&tcptls_session->lock);
+                                               goto cleanup;
+                                       }
+                                       buf[bytes_read] = '\0';
+                                       ast_mutex_unlock(&tcptls_session->lock);
+                                       if (me->stop)
+                                               goto cleanup;
+                                       cl -= strlen(buf);
+                                       ast_str_append(&req.data, 0, "%s", buf);
+                                       req.len = req.data->used;
+                               }
+                       }
+                       /*! \todo XXX If there's no Content-Length or if the content-length and what
+                                       we receive is not the same - we should generate an error */
 
-/*! \brief Build a Via header for a request */
-static void build_via(struct sip_pvt *p)
-{
-       /* Work around buggy UNIDEN UIP200 firmware */
-       const char *rport = (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)) ? ";rport" : "";
+                       req.socket.tcptls_session = tcptls_session;
+                       handle_request_do(&req, &tcptls_session->remote_address);
+               }
 
-       /* z9hG4bK is a magic cookie.  See RFC 3261 section 8.1.1.7 */
-       snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
-                get_transport_pvt(p),
-                ast_inet_ntoa(p->ourip.sin_addr),
-                ntohs(p->ourip.sin_port), (int) p->branch, rport);
-}
+               if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
+                       enum sip_tcptls_alert alert;
+                       struct tcptls_packet *packet;
 
-/*! \brief NAT fix - decide which IP address to use for Asterisk server?
- *
- * Using the localaddr structure built up with localnet statements in sip.conf
- * apply it to their address to see if we need to substitute our
- * externip or can get away with our internal bindaddr
- * 'us' is always overwritten.
- */
-static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us, struct sip_pvt *p)
-{
-       struct sockaddr_in theirs;
-       /* Set want_remap to non-zero if we want to remap 'us' to an externally
-        * reachable IP address and port. This is done if:
-        * 1. we have a localaddr list (containing 'internal' addresses marked
-        *    as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
-        *    and AST_SENSE_ALLOW on 'external' ones);
-        * 2. either stunaddr or externip is set, so we know what to use as the
-        *    externally visible address;
-        * 3. the remote address, 'them', is external;
-        * 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
-        *    when passed to ast_apply_ha() so it does need to be remapped.
-        *    This fourth condition is checked later.
-        */
-       int want_remap;
+                       fds[1].revents = 0;
 
-       *us = internip;         /* starting guess for the internal address */
-       /* now ask the system what would it use to talk to 'them' */
-       ast_ouraddrfor(them, &us->sin_addr);
-       theirs.sin_addr = *them;
+                       if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) {
+                               ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
+                               continue;
+                       }
 
-       want_remap = localaddr &&
-               (externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
-               ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
+                       switch (alert) {
+                       case TCPTLS_ALERT_STOP:
+                               goto cleanup;
+                       case TCPTLS_ALERT_DATA:
+                               ao2_lock(me);
+                               if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) {
+                                       ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty");
+                               } else if (ast_tcptls_server_write(tcptls_session, ast_str_buffer(packet->data), packet->len) == -1) {
+                                       ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n");
+                               }
 
-       if (want_remap &&
-           (!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
-               /* if we used externhost or stun, see if it is time to refresh the info */
-               if (externexpire && time(NULL) >= externexpire) {
-                       if (stunaddr.sin_addr.s_addr) {
-                               ast_stun_request(sipsock, &stunaddr, NULL, &externip);
-                       } else {
-                               if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
-                                       ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
-                       }
-                       externexpire = time(NULL) + externrefresh;
-               }
-               if (externip.sin_addr.s_addr) {
-                       *us = externip;
-                       switch (p->socket.type) {
-                       case SIP_TRANSPORT_TCP:
-                               us->sin_port = htons(externtcpport);
-                               break;
-                       case SIP_TRANSPORT_TLS:
-                               us->sin_port = htons(externtlsport);
+                               if (packet) {
+                                       ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed");
+                               }
+                               ao2_unlock(me);
                                break;
-                       case SIP_TRANSPORT_UDP:
-                               break; /* fall through */
                        default:
-                               us->sin_port = htons(STANDARD_SIP_PORT); /* we should never get here */
-                       }
-               }
-               else
-                       ast_log(LOG_WARNING, "stun failed\n");
-               ast_debug(1, "Target address %s is not local, substituting externip\n",
-                       ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
-       } else if (p) {
-               /* no remapping, but we bind to a specific address, so use it. */
-               switch (p->socket.type) {
-               case SIP_TRANSPORT_TCP:
-                       if (sip_tcp_desc.local_address.sin_addr.s_addr) {
-                               *us = sip_tcp_desc.local_address;
-                       } else {
-                               us->sin_port = sip_tcp_desc.local_address.sin_port;
-                       }
-                       break;
-               case SIP_TRANSPORT_TLS:
-                       if (sip_tls_desc.local_address.sin_addr.s_addr) {
-                               *us = sip_tls_desc.local_address;
-                       } else {
-                               us->sin_port = sip_tls_desc.local_address.sin_port;
-                       }
-                               break;
-               case SIP_TRANSPORT_UDP:
-                       /* fall through on purpose */
-               default:
-                       if (bindaddr.sin_addr.s_addr) {
-                               *us = bindaddr;
+                               ast_log(LOG_ERROR, "Unknown tcptls thread alert '%d'\n", alert);
                        }
                }
-       } else if (bindaddr.sin_addr.s_addr) {
-               *us = bindaddr;
        }
-       ast_debug(3, "Setting SIP_TRANSPORT_%s with address %s:%d\n", get_transport(p->socket.type), ast_inet_ntoa(us->sin_addr), ntohs(us->sin_port));
-}
 
-/*! \brief Append to SIP dialog history with arg list  */
-static __attribute__((format(printf, 2, 0))) void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
-{
-       char buf[80], *c = buf; /* max history length */
-       struct sip_history *hist;
-       int l;
+       ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
 
-       vsnprintf(buf, sizeof(buf), fmt, ap);
-       strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
-       l = strlen(buf) + 1;
-       if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
-               return;
-       if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
-               ast_free(hist);
-               return;
+cleanup:
+       if (me) {
+               ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing");
+               ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref");
        }
-       memcpy(hist->event, buf, l);
-       if (p->history_entries == MAX_HISTORY_ENTRIES) {
-               struct sip_history *oldest;
-               oldest = AST_LIST_REMOVE_HEAD(p->history, list);
-               p->history_entries--;
-               ast_free(oldest);
+       if (reqcpy.data) {
+               ast_free(reqcpy.data);
        }
-       AST_LIST_INSERT_TAIL(p->history, hist, list);
-       p->history_entries++;
-}
-
-/*! \brief Append to SIP dialog history with arg list  */
-static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
-{
-       va_list ap;
 
-       if (!p)
-               return;
+       if (req.data) {
+               ast_free(req.data);
+               req.data = NULL;
+       }
 
-       if (!p->do_history && !recordhistory && !dumphistory)
-               return;
+       /* if client, we own the parent session arguments and must decrement ref */
+       if (ca) {
+               ao2_t_ref(ca, -1, "closing tcptls thread, getting rid of client tcptls_session arguments");
+       }
 
-       va_start(ap, fmt);
-       append_history_va(p, fmt, ap);
-       va_end(ap);
+       if (tcptls_session) {
+               ast_mutex_lock(&tcptls_session->lock);
+               if (tcptls_session->f) {
+                       fclose(tcptls_session->f);
+                       tcptls_session->f = NULL;
+               }
+               if (tcptls_session->fd != -1) {
+                       close(tcptls_session->fd);
+                       tcptls_session->fd = -1;
+               }
+               tcptls_session->parent = NULL;
+               ast_mutex_unlock(&tcptls_session->lock);
 
-       return;
+               ao2_ref(tcptls_session, -1);
+               tcptls_session = NULL;
+       }
+       return NULL;
 }
 
-/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
-static int retrans_pkt(const void *data)
+
+/*!
+ * helper functions to unreference various types of objects.
+ * By handling them this way, we don't have to declare the
+ * destructor on each call, which removes the chance of errors.
+ */
+static void *unref_peer(struct sip_peer *peer, char *tag)
 {
-       struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
-       int reschedule = DEFAULT_RETRANS;
-       int xmitres = 0;
-       
-       /* Lock channel PVT */
-       sip_pvt_lock(pkt->owner);
+       ao2_t_ref(peer, -1, tag);
+       return NULL;
+}
 
-       if (pkt->retrans < MAX_RETRANS) {
-               pkt->retrans++;
-               if (!pkt->timer_t1) {   /* Re-schedule using timer_a and timer_t1 */
-                       if (sipdebug)
-                               ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
-               } else {
-                       int siptimer_a;
+static struct sip_peer *ref_peer(struct sip_peer *peer, char *tag)
+{
+       ao2_t_ref(peer, 1, tag);
+       return peer;
+}
 
-                       if (sipdebug)
-                               ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
-                       if (!pkt->timer_a)
-                               pkt->timer_a = 2 ;
-                       else
-                               pkt->timer_a = 2 * pkt->timer_a;
+/*! \brief maintain proper refcounts for a sip_pvt's outboundproxy
+ *
+ * This function sets pvt's outboundproxy pointer to the one referenced
+ * by the proxy parameter. Because proxy may be a refcounted object, and
+ * because pvt's old outboundproxy may also be a refcounted object, we need
+ * to maintain the proper refcounts.
+ *
+ * \param pvt The sip_pvt for which we wish to set the outboundproxy
+ * \param proxy The sip_proxy which we will point pvt towards.
+ * \return Returns void
+ */
+static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
+{
+       struct sip_proxy *old_obproxy = pvt->outboundproxy;
+       /* The sip_cfg.outboundproxy is statically allocated, and so
+        * we don't ever need to adjust refcounts for it
+        */
+       if (proxy && proxy != &sip_cfg.outboundproxy) {
+               ao2_ref(proxy, +1);
+       }
+       pvt->outboundproxy = proxy;
+       if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
+               ao2_ref(old_obproxy, -1);
+       }
+}
 
-                       /* For non-invites, a maximum of 4 secs */
-                       siptimer_a = pkt->timer_t1 * pkt->timer_a;      /* Double each time */
-                       if (pkt->method != SIP_INVITE && siptimer_a > 4000)
-                               siptimer_a = 4000;
-               
-                       /* Reschedule re-transmit */
-                       reschedule = siptimer_a;
-                       ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
-               }
+/*!
+ * \brief Unlink a dialog from the dialogs container, as well as any other places
+ * that it may be currently stored.
+ *
+ * \note A reference to the dialog must be held before calling this function, and this
+ * function does not release that reference.
+ */
+void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist)
+{
+       struct sip_pkt *cp;
 
-               if (sip_debug_test_pvt(pkt->owner)) {
-                       const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
-                       ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
-                               pkt->retrans, sip_nat_mode(pkt->owner),
-                               ast_inet_ntoa(dst->sin_addr),
-                               ntohs(dst->sin_port), pkt->data->str);
-               }
+       dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
 
-               append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data->str);
-               xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
-               sip_pvt_unlock(pkt->owner);
-               if (xmitres == XMIT_ERROR)
-                       ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
-               else
-                       return  reschedule;
-       }
-       /* Too many retries */
-       if (pkt->owner && pkt->method != SIP_OPTIONS && xmitres == 0) {
-               if (pkt->is_fatal || sipdebug)  /* Tell us if it's critical or if we're debugging */
-                       ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s) -- See doc/sip-retransmit.txt.\n",
-                               pkt->owner->callid, pkt->seqno,
-                               pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
-       } else if (pkt->method == SIP_OPTIONS && sipdebug) {
-                       ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s)  -- See doc/sip-retransmit.txt.\n", pkt->owner->callid);
+       ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
 
+       /* Unlink us from the owner (channel) if we have one */
+       if (dialog->owner) {
+               if (lockowner)
+                       ast_channel_lock(dialog->owner);
+               ast_debug(1, "Detaching from channel %s\n", dialog->owner->name);
+               dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
+               if (lockowner)
+                       ast_channel_unlock(dialog->owner);
        }
-       if (xmitres == XMIT_ERROR) {
-               ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
-               append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
-       } else
-               append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
-               
-       pkt->retransid = -1;
+       if (dialog->registry) {
+               if (dialog->registry->call == dialog)
+                       dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
+               dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
+       }
+       if (dialog->stateid > -1) {
+               ast_extension_state_del(dialog->stateid, NULL);
+               dialog_unref(dialog, "removing extension_state, should unref the associated dialog ptr that was stored there.");
+               dialog->stateid = -1; /* shouldn't we 'zero' this out? */
+       }
+       /* Remove link from peer to subscription of MWI */
+       if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog)
+               dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
+       if (dialog->relatedpeer && dialog->relatedpeer->call == dialog)
+               dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
 
-       if (pkt->is_fatal) {
-               while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
-                       sip_pvt_unlock(pkt->owner);     /* SIP_PVT, not channel */
-                       usleep(1);
-                       sip_pvt_lock(pkt->owner);
+       /* remove all current packets in this dialog */
+       while((cp = dialog->packets)) {
+               dialog->packets = dialog->packets->next;
+               AST_SCHED_DEL(sched, cp->retransid);
+               dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
+               if (cp->data) {
+                       ast_free(cp->data);
                }
+               ast_free(cp);
+       }
 
-               if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
-                       pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
-               
-               if (pkt->owner->owner) {
-                       sip_alreadygone(pkt->owner);
-                       ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see doc/sip-retransmit.txt).\n", pkt->owner->callid);
-                       ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_PROTOCOL_ERROR);
-                       ast_channel_unlock(pkt->owner->owner);
-               } else {
-                       /* If no channel owner, destroy now */
+       AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
 
-                       /* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
-                       if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
-                               pvt_set_needdestroy(pkt->owner, "no response to critical packet");
-                               sip_alreadygone(pkt->owner);
-                               append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
-                       }
-               }
-       }
+       AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr"));
+       
+       if (dialog->autokillid > -1)
+               AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr"));
 
-       if (pkt->method == SIP_BYE) {
-               /* We're not getting answers on SIP BYE's.  Tear down the call anyway. */
-               if (pkt->owner->owner)
-                       ast_channel_unlock(pkt->owner->owner);
-               append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
-               pvt_set_needdestroy(pkt->owner, "no response to BYE");
+       if (dialog->request_queue_sched_id > -1) {
+               AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr"));
        }
 
-       /* Remove the packet */
-       for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
-               if (cur == pkt) {
-                       UNLINK(cur, pkt->owner->packets, prev);
-                       sip_pvt_unlock(pkt->owner);
-                       if (pkt->owner)
-                               pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
-                       if (pkt->data)
-                               ast_free(pkt->data);
-                       pkt->data = NULL;
-                       ast_free(pkt);
-                       return 0;
-               }
+       AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+
+       if (dialog->t38id > -1) {
+               AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
        }
-       /* error case */
-       ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
-       sip_pvt_unlock(pkt->owner);
-       return 0;
+
+       dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
+       return NULL;
 }
 
-/*! \brief Transmit packet with retransmits
-       \return 0 on success, -1 on failure to allocate packet
-*/
-static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod)
+void *registry_unref(struct sip_registry *reg, char *tag)
 {
-       struct sip_pkt *pkt = NULL;
-       int siptimer_a = DEFAULT_RETRANS;
-       int xmitres = 0;
-       int respid;
+       ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1);
+       ASTOBJ_UNREF(reg, sip_registry_destroy);
+       return NULL;
+}
 
-       if (sipmethod == SIP_INVITE) {
-               /* Note this is a pending invite */
-               p->pendinginvite = seqno;
-       }
+/*! \brief Add object reference to SIP registry */
+static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag)
+{
+       ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
+       return ASTOBJ_REF(reg); /* Add pointer to registry in packet */
+}
 
-       /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */
-       /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */
-       /*! \todo According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */
-       if (!(p->socket.type & SIP_TRANSPORT_UDP)) {
-               xmitres = __sip_xmit(p, data, len);     /* Send packet */
-               if (xmitres == XMIT_ERROR) {    /* Serious network trouble, no need to try again */
-                       append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
-                       return AST_FAILURE;
-               } else {
-                       return AST_SUCCESS;
-               }
-       }
+/*! \brief Interface structure with callbacks used to connect to UDPTL module*/
+static struct ast_udptl_protocol sip_udptl = {
+       type: "SIP",
+       get_udptl_info: sip_get_udptl_peer,
+       set_udptl_peer: sip_set_udptl_peer,
+};
 
-       if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
-               return AST_FAILURE;
-       /* copy data, add a terminator and save length */
-       if (!(pkt->data = ast_str_create(len))) {
-               ast_free(pkt);
-               return AST_FAILURE;
-       }
-       ast_str_set(&pkt->data, 0, "%s%s", data->str, "\0");
-       pkt->packetlen = len;
-       /* copy other parameters from the caller */
-       pkt->method = sipmethod;
-       pkt->seqno = seqno;
-       pkt->is_resp = resp;
-       pkt->is_fatal = fatal;
-       pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
-       pkt->next = p->packets;
-       p->packets = pkt;       /* Add it to the queue */
-       if (resp) {
-               /* Parse out the response code */
-               if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) {
-                       pkt->response_code = respid;
-               }
-       }
-       pkt->timer_t1 = p->timer_t1;    /* Set SIP timer T1 */
-       pkt->retransid = -1;
-       if (pkt->timer_t1)
-               siptimer_a = pkt->timer_t1 * 2;
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
+       __attribute__((format(printf, 2, 3)));
 
-       /* Schedule retransmission */
-       AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
-       if (sipdebug)
-               ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id  #%d\n", pkt->retransid);
 
-       xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);    /* Send packet */
+/*! \brief Convert transfer status to string */
+static const char *referstatus2str(enum referstatus rstatus)
+{
+       return map_x_s(referstatusstrings, rstatus, "");
+}
 
-       if (xmitres == XMIT_ERROR) {    /* Serious network trouble, no need to try again */
-               append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
-               ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n");
-               AST_SCHED_DEL(sched, pkt->retransid);
-               p->packets = pkt->next;
-               pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
-               ast_free(pkt->data);
-               ast_free(pkt);
-               return AST_FAILURE;
-       } else {
-               return AST_SUCCESS;
-       }
+static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
+{
+       append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason);
+       pvt->needdestroy = 1;
 }
 
-/*! \brief Kill a SIP dialog (called only by the scheduler)
- * The scheduler has a reference to this dialog when p->autokillid != -1,
- * and we are called using that reference. So if the event is not
- * rescheduled, we need to call dialog_unref().
- */
-static int __sip_autodestruct(const void *data)
+/*! \brief Initialize the initital request packet in the pvt structure.
+       This packet is used for creating replies and future requests in
+       a dialog */
+static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
 {
-       struct sip_pvt *p = (struct sip_pvt *)data;
+       if (p->initreq.headers)
+               ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
+       else
+               ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+       /* Use this as the basis */
+       copy_request(&p->initreq, req);
+       parse_request(&p->initreq);
+       if (req->debug)
+               ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+}
 
-       /* If this is a subscription, tell the phone that we got a timeout */
-       if (p->subscribed) {
-               transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE);   /* Send last notification */
-               p->subscribed = NONE;
-               append_history(p, "Subscribestatus", "timeout");
-               ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
-               return 10000;   /* Reschedule this destruction so that we know that it's gone */
-       }
+/*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */
+static void sip_alreadygone(struct sip_pvt *dialog)
+{
+       ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
+       dialog->alreadygone = 1;
+}
 
-       /* If there are packets still waiting for delivery, delay the destruction */
-       if (p->packets) {
-               if (!p->needdestroy) {
-                       char method_str[31];
-                       ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
-                       append_history(p, "ReliableXmit", "timeout");
-                       if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) {
-                               if (method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) {
-                                       pvt_set_needdestroy(p, "autodestruct");
-                               }
-                       }
-                       return 10000;
-               } else {
-                       /* They've had their chance to respond. Time to bail */
-                       __sip_pretend_ack(p);
+/*! Resolve DNS srv name or host name in a sip_proxy structure */
+static int proxy_update(struct sip_proxy *proxy)
+{
+       /* if it's actually an IP address and not a name,
+           there's no need for a managed lookup */
+       if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
+               /* Ok, not an IP address, then let's check if it's a domain or host */
+               /* XXX Todo - if we have proxy port, don't do SRV */
+               if (ast_get_ip_or_srv(&proxy->ip, proxy->name, sip_cfg.srvlookup ? "_sip._udp" : NULL) < 0) {
+                       ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
+                       return FALSE;
                }
        }
+       proxy->last_dnsupdate = time(NULL);
+       return TRUE;
+}
 
-       if (p->subscribed == MWI_NOTIFICATION) {
-               if (p->relatedpeer) {
-                       p->relatedpeer = unref_peer(p->relatedpeer, "__sip_autodestruct: unref peer p->relatedpeer");   /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
-               }
+/*! \brief converts ascii port to int representation. If no
+ *  pt buffer is provided or the pt has errors when being converted
+ *  to an int value, the port provided as the standard is used.
+ */
+unsigned int port_str2int(const char *pt, unsigned int standard)
+{
+       int port = standard;
+       if (ast_strlen_zero(pt) || (sscanf(pt, "%30d", &port) != 1) || (port < 1) || (port > 65535)) {
+               port = standard;
        }
 
-       /* Reset schedule ID */
-       p->autokillid = -1;
-
-       if (p->owner) {
-               ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
-               ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
-       } else if (p->refer && !p->alreadygone) {
-               ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
-               transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
-               append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
-               sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
-       } else {
-               append_history(p, "AutoDestroy", "%s", p->callid);
-               ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
-               dialog_unlink_all(p, TRUE, TRUE); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
-               /* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
-               /* sip_destroy(p); */           /* Go ahead and destroy dialog. All attempts to recover is done */
-               /* sip_destroy also absorbs the reference */
-       }
-       dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
-       return 0;
+       return port;
 }
 
-/*! \brief Schedule destruction of SIP dialog */
-void sip_scheddestroy(struct sip_pvt *p, int ms)
+/*! \brief Allocate and initialize sip proxy */
+static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
 {
-       if (ms < 0) {
-               if (p->timer_t1 == 0) {
-                       p->timer_t1 = global_t1;        /* Set timer T1 if not set (RFC 3261) */
-               }
-               if (p->timer_b == 0) {
-                       p->timer_b = global_timer_b;  /* Set timer B if not set (RFC 3261) */
-               }
-               ms = p->timer_t1 * 64;
+       struct sip_proxy *proxy;
+
+       if (ast_strlen_zero(name)) {
+               return NULL;
        }
-       if (sip_debug_test_pvt(p))
-               ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
-       if (sip_cancel_destroy(p))
-               ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
 
-       if (p->do_history)
-               append_history(p, "SchedDestroy", "%d ms", ms);
-       p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct"));
+       proxy = ao2_alloc(sizeof(*proxy), NULL);
+       if (!proxy)
+               return NULL;
+       proxy->force = force;
+       ast_copy_string(proxy->name, name, sizeof(proxy->name));
+       proxy->ip.sin_port = htons(port_str2int(port, STANDARD_SIP_PORT));
+       proxy_update(proxy);
+       return proxy;
+}
 
-       if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
-               stop_session_timer(p);
+/*! \brief Get default outbound proxy or global proxy */
+static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
+{
+       if (peer && peer->outboundproxy) {
+               if (sipdebug)
+                       ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
+               append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
+               return peer->outboundproxy;
+       }
+       if (sip_cfg.outboundproxy.name[0]) {
+               if (sipdebug)
+                       ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
+               append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name);
+               return &sip_cfg.outboundproxy;
+       }
+       if (sipdebug)
+               ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
+       return NULL;
 }
 
-/*! \brief Cancel destruction of SIP dialog.
- * Be careful as this also absorbs the reference - if you call it
- * from within the scheduler, this might be the last reference.
+/*! \brief returns true if 'name' (with optional trailing whitespace)
+ * matches the sip method 'id'.
+ * Strictly speaking, SIP methods are case SENSITIVE, but we do
+ * a case-insensitive comparison to be more tolerant.
+ * following Jon Postel's rule: Be gentle in what you accept, strict with what you send
  */
-int sip_cancel_destroy(struct sip_pvt *p)
+static int method_match(enum sipmethod id, const char *name)
 {
-       int res = 0;
-       if (p->autokillid > -1) {
-               int res3;
+       int len = strlen(sip_methods[id].text);
+       int l_name = name ? strlen(name) : 0;
+       /* true if the string is long enough, and ends with whitespace, and matches */
+       return (l_name >= len && name[len] < 33 &&
+               !strncasecmp(sip_methods[id].text, name, len));
+}
 
-               if (!(res3 = ast_sched_del(sched, p->autokillid))) {
-                       append_history(p, "CancelDestroy", "");
-                       p->autokillid = -1;
-                       dialog_unref(p, "dialog unrefd because autokillid is de-sched'd");
-               }
+/*! \brief  find_sip_method: Find SIP method from header */
+static int find_sip_method(const char *msg)
+{
+       int i, res = 0;
+       
+       if (ast_strlen_zero(msg))
+               return 0;
+       for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) {
+               if (method_match(i, msg))
+                       res = sip_methods[i].id;
        }
        return res;
 }
 
-/*! \brief Acknowledges receipt of a packet and stops retransmission
- * called with p locked*/
-int __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+/*! \brief Parse supported header in incoming packet */
+static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
 {
-       struct sip_pkt *cur, *prev = NULL;
-       const char *msg = "Not Found";  /* used only for debugging */
-       int res = FALSE;
+       char *next, *sep;
+       char *temp;
+       unsigned int profile = 0;
+       int i, found;
 
-       /* If we have an outbound proxy for this dialog, then delete it now since
-         the rest of the requests in this dialog needs to follow the routing.
-         If obforcing is set, we will keep the outbound proxy during the whole
-         dialog, regardless of what the SIP rfc says
-       */
-       if (p->outboundproxy && !p->outboundproxy->force){
-               ref_proxy(p, NULL);
-       }
+       if (ast_strlen_zero(supported) )
+               return 0;
+       temp = ast_strdupa(supported);
 
-       for (cur = p->packets; cur; prev = cur, cur = cur->next) {
-               if (cur->seqno != seqno || cur->is_resp != resp)
-                       continue;
-               if (cur->is_resp || cur->method == sipmethod) {
-                       res = TRUE;
-                       msg = "Found";
-                       if (!resp && (seqno == p->pendinginvite)) {
-                               ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
-                               p->pendinginvite = 0;
-                       }
-                       if (cur->retransid > -1) {
-                               if (sipdebug)
-                                       ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
-                       }
-                       /* This odd section is designed to thwart a
-                        * race condition in the packet scheduler. There are
-                        * two conditions under which deleting the packet from the
-                        * scheduler can fail.
-                        *
-                        * 1. The packet has been removed from the scheduler because retransmission
-                        * is being attempted. The problem is that if the packet is currently attempting
-                        * retransmission and we are at this point in the code, then that MUST mean
-                        * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
-                        * lock temporarily to allow retransmission.
-                        *
-                        * 2. The packet has reached its maximum number of retransmissions and has
-                        * been permanently removed from the packet scheduler. If this is the case, then
-                        * the packet's retransid will be set to -1. The atomicity of the setting and checking
-                        * of the retransid to -1 is ensured since in both cases p's lock is held.
-                        */
-                       while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
-                               sip_pvt_unlock(p);
-                               usleep(1);
-                               sip_pvt_lock(p);
+       if (sipdebug)
+               ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
+
+       for (next = temp; next; next = sep) {
+               found = FALSE;
+               if ( (sep = strchr(next, ',')) != NULL)
+                       *sep++ = '\0';
+               next = ast_skip_blanks(next);
+               if (sipdebug)
+                       ast_debug(3, "Found SIP option: -%s-\n", next);
+               for (i = 0; i < ARRAY_LEN(sip_options); i++) {
+                       if (!strcasecmp(next, sip_options[i].text)) {
+                               profile |= sip_options[i].id;
+                               found = TRUE;
+                               if (sipdebug)
+                                       ast_debug(3, "Matched SIP option: %s\n", next);
+                               break;
                        }
-                       UNLINK(cur, p->packets, prev);
-                       dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
-                       if (cur->data)
-                               ast_free(cur->data);
-                       ast_free(cur);
-                       break;
                }
-       }
-       ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
-               p->callid, resp ? "Response" : "Request", seqno, msg);
-       return res;
-}
 
-/*! \brief Pretend to ack all packets
- * called with p locked */
-void __sip_pretend_ack(struct sip_pvt *p)
-{
-       struct sip_pkt *cur = NULL;
+               /* This function is used to parse both Suported: and Require: headers.
+               Let the caller of this function know that an unknown option tag was
+               encountered, so that if the UAC requires it then the request can be
+               rejected with a 420 response. */
+               if (!found)
+                       profile |= SIP_OPT_UNKNOWN;
 
-       while (p->packets) {
-               int method;
-               if (cur == p->packets) {
-                       ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
-                       return;
+               if (!found && sipdebug) {
+                       if (!strncasecmp(next, "x-", 2))
+                               ast_debug(3, "Found private SIP option, not supported: %s\n", next);
+                       else
+                               ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
                }
-               cur = p->packets;
-               method = (cur->method) ? cur->method : find_sip_method(cur->data->str);
-               __sip_ack(p, cur->seqno, cur->is_resp, method);
        }
+
+       if (pvt)
+               pvt->sipoptions = profile;
+       return profile;
 }
 
-/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
-int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+/*! \brief See if we pass debug IP filter */
+static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
 {
-       struct sip_pkt *cur;
-       int res = FALSE;
-
-       for (cur = p->packets; cur; cur = cur->next) {
-               if (cur->seqno == seqno && cur->is_resp == resp &&
-                       (cur->is_resp || method_match(sipmethod, cur->data->str))) {
-                       /* this is our baby */
-                       if (cur->retransid > -1) {
-                               if (sipdebug)
-                                       ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
-                       }
-                       AST_SCHED_DEL(sched, cur->retransid);
-                       res = TRUE;
-                       break;
-               }
+       if (!sipdebug)
+               return 0;
+       if (debugaddr.sin_addr.s_addr) {
+               if (((ntohs(debugaddr.sin_port) != 0)
+                       && (debugaddr.sin_port != addr->sin_port))
+                       || (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
+                       return 0;
        }
-       ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");
-       return res;
+       return 1;
 }
 
+/*! \brief The real destination address for a write */
+static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
+{
+       if (p->outboundproxy)
+               return &p->outboundproxy->ip;
+
+       return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa;
+}
 
-/*! \brief Copy SIP request, parse it */
-static void parse_copy(struct sip_request *dst, const struct sip_request *src)
+/*! \brief Display SIP nat mode */
+static const char *sip_nat_mode(const struct sip_pvt *p)
 {
-       copy_request(dst, src);
-       parse_request(dst);
+       return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT";
 }
 
-/*! \brief add a blank line if no body */
-static void add_blank(struct sip_request *req)
+/*! \brief Test PVT for debugging output */
+static inline int sip_debug_test_pvt(struct sip_pvt *p)
 {
-       if (!req->lines) {
-               /* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
-               ast_str_append(&req->data, 0, "\r\n");
-               req->len = ast_str_strlen(req->data);
-       }
+       if (!sipdebug)
+               return 0;
+       return sip_debug_test_addr(sip_real_dst(p));
 }
 
-static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
+/*! \brief Return int representing a bit field of transport types found in const char *transport */
+static int get_transport_str2enum(const char *transport)
 {
-       const char *msg = NULL;
+       int res = 0;
 
-       if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
-               msg = "183 Session Progress";
+       if (ast_strlen_zero(transport)) {
+               return res;
        }
 
-       if (pvt->invitestate < INV_COMPLETED) {
-               if (with_sdp) {
-                       transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE);
-               } else {
-                       transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
-               }
-               return PROVIS_KEEPALIVE_TIMEOUT;
+       if (!strcasecmp(transport, "udp")) {
+               res |= SIP_TRANSPORT_UDP;
+       }
+       if (!strcasecmp(transport, "tcp")) {
+               res |= SIP_TRANSPORT_TCP;
+       }
+       if (!strcasecmp(transport, "tls")) {
+               res |= SIP_TRANSPORT_TLS;
        }
 
-       return 0;
+       return res;
 }
 
-static int send_provisional_keepalive(const void *data) {
-       struct sip_pvt *pvt = (struct sip_pvt *) data;
-
-       return send_provisional_keepalive_full(pvt, 0);
+/*! \brief Return configuration of transports for a device */
+static inline const char *get_transport_list(unsigned int transports) {
+       switch (transports) {
+               case SIP_TRANSPORT_UDP:
+                       return "UDP";
+               case SIP_TRANSPORT_TCP:
+                       return "TCP";
+               case SIP_TRANSPORT_TLS:
+                       return "TLS";
+               case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
+                       return "TCP,UDP";
+               case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
+                       return "TLS,UDP";
+               case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
+                       return "TLS,TCP";
+               default:
+                       return transports ?
+                               "TLS,TCP,UDP" : "UNKNOWN";      
+       }
 }
 
-static int send_provisional_keepalive_with_sdp(const void *data) {
-       struct sip_pvt *pvt = (void *)data;
+/*! \brief Return transport as string */
+static inline const char *get_transport(enum sip_transport t)
+{
+       switch (t) {
+       case SIP_TRANSPORT_UDP:
+               return "UDP";
+       case SIP_TRANSPORT_TCP:
+               return "TCP";
+       case SIP_TRANSPORT_TLS:
+               return "TLS";
+       }
 
-       return send_provisional_keepalive_full(pvt, 1);
+       return "UNKNOWN";
 }
 
-static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
+/*! \brief Return transport of dialog.
+       \note this is based on a false assumption. We don't always use the
+       outbound proxy for all requests in a dialog. It depends on the
+       "force" parameter. The FIRST request is always sent to the ob proxy.
+       \todo Fix this function to work correctly
+*/
+static inline const char *get_transport_pvt(struct sip_pvt *p)
 {
-       AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+       if (p->outboundproxy && p->outboundproxy->transport) {
+               set_socket_transport(&p->socket, p->outboundproxy->transport);
+       }
 
-       pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
-               with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
+       return get_transport(p->socket.type);
 }
 
-/*! \brief Transmit response on SIP request*/
-static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
+/*! \brief Transmit SIP message
+       Sends a SIP request or response on a given socket (in the pvt)
+       Called by retrans_pkt, send_request, send_response and
+       __sip_reliable_xmit
+       \return length of transmitted message, XMIT_ERROR on known network failures -1 on other failures.
+*/
+static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len)
 {
-       int res;
+       int res = 0;
+       const struct sockaddr_in *dst = sip_real_dst(p);
 
-       add_blank(req);
-       if (sip_debug_test_pvt(p)) {
-               const struct sockaddr_in *dst = sip_real_dst(p);
+       ast_debug(2, "Trying to put '%.11s' onto %s socket destined for %s:%d\n", data->str, get_transport_pvt(p), ast_inet_ntoa(dst->sin_addr), htons(dst->sin_port));
 
-               ast_verbose("\n<--- %sTransmitting (%s) to %s:%d --->\n%s\n<------------>\n",
-                       reliable ? "Reliably " : "", sip_nat_mode(p),
-                       ast_inet_ntoa(dst->sin_addr),
-                       ntohs(dst->sin_port), req->data->str);
-       }
-       if (p->do_history) {
-               struct sip_request tmp = { .rlPart1 = 0, };
-               parse_copy(&tmp, req);
-               append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"),
-                       (tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlPart2) : sip_methods[tmp.method].text);
-               ast_free(tmp.data);
+       if (sip_prepare_socket(p) < 0)
+               return XMIT_ERROR;
+
+       if (p->socket.type == SIP_TRANSPORT_UDP) {
+               res = sendto(p->socket.fd, data->str, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
+       } else if (p->socket.tcptls_session) {
+               res = sip_tcptls_write(p->socket.tcptls_session, data->str, len);
+       } else {
+               ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
+               return XMIT_ERROR;
        }
 
-       /* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
-       if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
-               AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+       if (res == -1) {
+               switch (errno) {
+               case EBADF:             /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
+               case EHOSTUNREACH:      /* Host can't be reached */
+               case ENETDOWN:          /* Interface down */
+               case ENETUNREACH:       /* Network failure */
+               case ECONNREFUSED:      /* ICMP port unreachable */
+                       res = XMIT_ERROR;       /* Don't bother with trying to transmit again */
+               }
        }
+       if (res != len)
+               ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
 
-       res = (reliable) ?
-                __sip_reliable_xmit(p, seqno, 1, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
-               __sip_xmit(p, req->data, req->len);
-       ast_free(req->data);
-       req->data = NULL;
-       if (res > 0)
-               return 0;
        return res;
 }
 
-/*! \brief Send SIP Request to the other part of the dialogue 
-       \return see \ref __sip_xmit
-*/
-static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
+/*! \brief Build a Via header for a request */
+static void build_via(struct sip_pvt *p)
 {
-       int res;
-
-       /* If we have an outbound proxy, reset peer address
-               Only do this once.
-       */
-       if (p->outboundproxy) {
-               p->sa = p->outboundproxy->ip;
-       }
+       /* Work around buggy UNIDEN UIP200 firmware */
+       const char *rport = (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)) ? ";rport" : "";
 
-       add_blank(req);
-       if (sip_debug_test_pvt(p)) {
-               if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT))
-                       ast_verbose("%sTransmitting (NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port), req->data->str);
-               else
-                       ast_verbose("%sTransmitting (no NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->sa.sin_addr), ntohs(p->sa.sin_port), req->data->str);
-       }
-       if (p->do_history) {
-               struct sip_request tmp = { .rlPart1 = 0, };
-               parse_copy(&tmp, req);
-               append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
-               ast_free(tmp.data);
-       }
-       res = (reliable) ?
-               __sip_reliable_xmit(p, seqno, 0, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
-               __sip_xmit(p, req->data, req->len);
-       if (req->data) {
-               ast_free(req->data);
-               req->data = NULL;
-       }
-       return res;
+       /* z9hG4bK is a magic cookie.  See RFC 3261 section 8.1.1.7 */
+       snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
+                get_transport_pvt(p),
+                ast_inet_ntoa(p->ourip.sin_addr),
+                ntohs(p->ourip.sin_port), (int) p->branch, rport);
 }
 
-static void enable_dsp_detect(struct sip_pvt *p)
+/*! \brief NAT fix - decide which IP address to use for Asterisk server?
+ *
+ * Using the localaddr structure built up with localnet statements in sip.conf
+ * apply it to their address to see if we need to substitute our
+ * externip or can get away with our internal bindaddr
+ * 'us' is always overwritten.
+ */
+static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us, struct sip_pvt *p)
 {
-       int features = 0;
+       struct sockaddr_in theirs;
+       /* Set want_remap to non-zero if we want to remap 'us' to an externally
+        * reachable IP address and port. This is done if:
+        * 1. we have a localaddr list (containing 'internal' addresses marked
+        *    as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
+        *    and AST_SENSE_ALLOW on 'external' ones);
+        * 2. either stunaddr or externip is set, so we know what to use as the
+        *    externally visible address;
+        * 3. the remote address, 'them', is external;
+        * 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
+        *    when passed to ast_apply_ha() so it does need to be remapped.
+        *    This fourth condition is checked later.
+        */
+       int want_remap;
 
-       if (p->dsp) {
-               return;
-       }
+       *us = internip;         /* starting guess for the internal address */
+       /* now ask the system what would it use to talk to 'them' */
+       ast_ouraddrfor(them, &us->sin_addr);
+       theirs.sin_addr = *them;
 
-       if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
-            (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
-               if (!p->rtp || ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND)) {
-                       features |= DSP_FEATURE_DIGIT_DETECT;
-                }
-       }
+       want_remap = localaddr &&
+               (externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
+               ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
 
-       if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
-               features |= DSP_FEATURE_FAX_DETECT;
+       if (want_remap &&
+           (!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
+               /* if we used externhost or stun, see if it is time to refresh the info */
+               if (externexpire && time(NULL) >= externexpire) {
+                       if (stunaddr.sin_addr.s_addr) {
+                               ast_stun_request(sipsock, &stunaddr, NULL, &externip);
+                       } else {
+                               if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
+                                       ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
+                       }
+                       externexpire = time(NULL) + externrefresh;
+               }
+               if (externip.sin_addr.s_addr) {
+                       *us = externip;
+                       switch (p->socket.type) {
+                       case SIP_TRANSPORT_TCP:
+                               us->sin_port = htons(externtcpport);
+                               break;
+                       case SIP_TRANSPORT_TLS:
+                               us->sin_port = htons(externtlsport);
+                               break;
+                       case SIP_TRANSPORT_UDP:
+                               break; /* fall through */
+                       default:
+                               us->sin_port = htons(STANDARD_SIP_PORT); /* we should never get here */
+                       }
+               }
+               else
+                       ast_log(LOG_WARNING, "stun failed\n");
+               ast_debug(1, "Target address %s is not local, substituting externip\n",
+                       ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
+       } else if (p) {
+               /* no remapping, but we bind to a specific address, so use it. */
+               switch (p->socket.type) {
+               case SIP_TRANSPORT_TCP:
+                       if (sip_tcp_desc.local_address.sin_addr.s_addr) {
+                               *us = sip_tcp_desc.local_address;
+                       } else {
+                               us->sin_port = sip_tcp_desc.local_address.sin_port;
+                       }
+                       break;
+               case SIP_TRANSPORT_TLS:
+                       if (sip_tls_desc.local_address.sin_addr.s_addr) {
+                               *us = sip_tls_desc.local_address;
+                       } else {
+                               us->sin_port = sip_tls_desc.local_address.sin_port;
+                       }
+                               break;
+               case SIP_TRANSPORT_UDP:
+                       /* fall through on purpose */
+               default:
+                       if (bindaddr.sin_addr.s_addr) {
+                               *us = bindaddr;
+                       }
+               }
+       } else if (bindaddr.sin_addr.s_addr) {
+               *us = bindaddr;
        }
+       ast_debug(3, "Setting SIP_TRANSPORT_%s with address %s:%d\n", get_transport(p->socket.type), ast_inet_ntoa(us->sin_addr), ntohs(us->sin_port));
+}
 
-       if (!features) {
-               return;
-       }
+/*! \brief Append to SIP dialog history with arg list  */
+static __attribute__((format(printf, 2, 0))) void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
+{
+       char buf[80], *c = buf; /* max history length */
+       struct sip_history *hist;
+       int l;
 
-       if (!(p->dsp = ast_dsp_new())) {
+       vsnprintf(buf, sizeof(buf), fmt, ap);
+       strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
+       l = strlen(buf) + 1;
+       if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
+               return;
+       if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
+               ast_free(hist);
                return;
        }
-
-       ast_dsp_set_features(p->dsp, features);
-       if (global_relaxdtmf) {
-               ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
+       memcpy(hist->event, buf, l);
+       if (p->history_entries == MAX_HISTORY_ENTRIES) {
+               struct sip_history *oldest;
+               oldest = AST_LIST_REMOVE_HEAD(p->history, list);
+               p->history_entries--;
+               ast_free(oldest);
        }
+       AST_LIST_INSERT_TAIL(p->history, hist, list);
+       p->history_entries++;
 }
 
-static void disable_dsp_detect(struct sip_pvt *p)
+/*! \brief Append to SIP dialog history with arg list  */
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
 {
-       if (p->dsp) {
-               ast_dsp_free(p->dsp);
-               p->dsp = NULL;
-       }
-}
+       va_list ap;
 
-/*! \brief Set an option on a SIP dialog */
-static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen)
-{
-       int res = -1;
-       struct sip_pvt *p = chan->tech_pvt;
+       if (!p)
+               return;
 
-       switch (option) {
-       case AST_OPTION_FORMAT_READ:
-               res = ast_rtp_instance_set_read_format(p->rtp, *(int *) data);
-               break;
-       case AST_OPTION_FORMAT_WRITE:
-               res = ast_rtp_instance_set_write_format(p->rtp, *(int *) data);
-               break;
-       case AST_OPTION_MAKE_COMPATIBLE:
-               res = ast_rtp_instance_make_compatible(chan, p->rtp, (struct ast_channel *) data);
-               break;
-       case AST_OPTION_DIGIT_DETECT:
-               if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
-                   (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
-                       char *cp = (char *) data;
+       if (!p->do_history && !recordhistory && !dumphistory)
+               return;
 
-                       ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", chan->name);
-                       if (*cp) {
-                               enable_dsp_detect(p);
-                       } else {
-                               disable_dsp_detect(p);
-                       }
-                       res = 0;
-               }
-               break;
-       default:
-               break;
-       }
+       va_start(ap, fmt);
+       append_history_va(p, fmt, ap);
+       va_end(ap);
 
-       return res;
+       return;
 }
 
-/*! \brief Query an option on a SIP dialog */
-static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen)
+/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
+static int retrans_pkt(const void *data)
 {
-       int res = -1;
-       enum ast_t38_state state = T38_STATE_UNAVAILABLE;
-       struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt;
-       char *cp;
-
-       switch (option) {
-       case AST_OPTION_T38_STATE:
-               /* Make sure we got an ast_t38_state enum passed in */
-               if (*datalen != sizeof(enum ast_t38_state)) {
-                       ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen);
-                       return -1;
-               }
+       struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
+       int reschedule = DEFAULT_RETRANS;
+       int xmitres = 0;
+       
+       /* Lock channel PVT */
+       sip_pvt_lock(pkt->owner);
 
-               sip_pvt_lock(p);
+       if (pkt->retrans < MAX_RETRANS) {
+               pkt->retrans++;
+               if (!pkt->timer_t1) {   /* Re-schedule using timer_a and timer_t1 */
+                       if (sipdebug)
+                               ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
+               } else {
+                       int siptimer_a;
 
-               /* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */
-               if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) {
-                       switch (p->t38.state) {
-                       case T38_LOCAL_REINVITE:
-                       case T38_PEER_REINVITE:
-                               state = T38_STATE_NEGOTIATING;
-                               break;
-                       case T38_ENABLED:
-                               state = T38_STATE_NEGOTIATED;
-                               break;
-                       default:
-                               state = T38_STATE_UNKNOWN;
-                       }
-               }
+                       if (sipdebug)
+                               ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
+                       if (!pkt->timer_a)
+                               pkt->timer_a = 2 ;
+                       else
+                               pkt->timer_a = 2 * pkt->timer_a;
 
-               sip_pvt_unlock(p);
+                       /* For non-invites, a maximum of 4 secs */
+                       siptimer_a = pkt->timer_t1 * pkt->timer_a;      /* Double each time */
+                       if (pkt->method != SIP_INVITE && siptimer_a > 4000)
+                               siptimer_a = 4000;
+               
+                       /* Reschedule re-transmit */
+                       reschedule = siptimer_a;
+                       ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
+               }
 
-               *((enum ast_t38_state *) data) = state;
-               res = 0;
+               if (sip_debug_test_pvt(pkt->owner)) {
+                       const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
+                       ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
+                               pkt->retrans, sip_nat_mode(pkt->owner),
+                               ast_inet_ntoa(dst->sin_addr),
+                               ntohs(dst->sin_port), pkt->data->str);
+               }
 
-               break;
-       case AST_OPTION_DIGIT_DETECT:
-               cp = (char *) data;
-               *cp = p->dsp ? 1 : 0;
-               ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", chan->name);
-               break;
-       default:
-               break;
+               append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data->str);
+               xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
+               sip_pvt_unlock(pkt->owner);
+               if (xmitres == XMIT_ERROR)
+                       ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
+               else
+                       return  reschedule;
        }
+       /* Too many retries */
+       if (pkt->owner && pkt->method != SIP_OPTIONS && xmitres == 0) {
+               if (pkt->is_fatal || sipdebug)  /* Tell us if it's critical or if we're debugging */
+                       ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s) -- See doc/sip-retransmit.txt.\n",
+                               pkt->owner->callid, pkt->seqno,
+                               pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
+       } else if (pkt->method == SIP_OPTIONS && sipdebug) {
+                       ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s)  -- See doc/sip-retransmit.txt.\n", pkt->owner->callid);
 
-       return res;
-}
-
-/*! \brief Locate closing quote in a string, skipping escaped quotes.
- * optionally with a limit on the search.
- * start must be past the first quote.
- */
-const char *find_closing_quote(const char *start, const char *lim)
-{
-       char last_char = '\0';
-       const char *s;
-       for (s = start; *s && s != lim; last_char = *s++) {
-               if (*s == '"' && last_char != '\\')
-                       break;
        }
-       return s;
-}
-
-/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
-static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
-{
-       struct sip_pvt *p = chan->tech_pvt;
+       if (xmitres == XMIT_ERROR) {
+               ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
+               append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+       } else
+               append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+               
+       pkt->retransid = -1;
 
-       if (subclass != AST_HTML_URL)
-               return -1;
+       if (pkt->is_fatal) {
+               while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
+                       sip_pvt_unlock(pkt->owner);     /* SIP_PVT, not channel */
+                       usleep(1);
+                       sip_pvt_lock(pkt->owner);
+               }
 
-       ast_string_field_build(p, url, "<%s>;mode=active", data);
+               if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
+                       pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
+               
+               if (pkt->owner->owner) {
+                       sip_alreadygone(pkt->owner);
+                       ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see doc/sip-retransmit.txt).\n", pkt->owner->callid);
+                       ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_PROTOCOL_ERROR);
+                       ast_channel_unlock(pkt->owner->owner);
+               } else {
+                       /* If no channel owner, destroy now */
 
-       if (sip_debug_test_pvt(p))
-               ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
+                       /* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
+                       if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
+                               pvt_set_needdestroy(pkt->owner, "no response to critical packet");
+                               sip_alreadygone(pkt->owner);
+                               append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
+                       }
+               }
+       }
 
-       switch (chan->_state) {
-       case AST_STATE_RING:
-               transmit_response(p, "100 Trying", &p->initreq);
-               break;
-       case AST_STATE_RINGING:
-               transmit_response(p, "180 Ringing", &p->initreq);
-               break;
-       case AST_STATE_UP:
-               if (!p->pendinginvite) {                /* We are up, and have no outstanding invite */
-                       transmit_reinvite_with_sdp(p, FALSE, FALSE);
-               } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
-                       ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);   
-               }       
-               break;
-       default:
-               ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
+       if (pkt->method == SIP_BYE) {
+               /* We're not getting answers on SIP BYE's.  Tear down the call anyway. */
+               if (pkt->owner->owner)
+                       ast_channel_unlock(pkt->owner->owner);
+               append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
+               pvt_set_needdestroy(pkt->owner, "no response to BYE");
        }
 
+       /* Remove the packet */
+       for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
+               if (cur == pkt) {
+                       UNLINK(cur, pkt->owner->packets, prev);
+                       sip_pvt_unlock(pkt->owner);
+                       if (pkt->owner)
+                               pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
+                       if (pkt->data)
+                               ast_free(pkt->data);
+                       pkt->data = NULL;
+                       ast_free(pkt);
+                       return 0;
+               }
+       }
+       /* error case */
+       ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
+       sip_pvt_unlock(pkt->owner);
        return 0;
 }
 
-/*! \brief Deliver SIP call ID for the call */
-static const char *sip_get_callid(struct ast_channel *chan)
-{
-       return chan->tech_pvt ? ((struct sip_pvt *) chan->tech_pvt)->callid : "";
-}
-
-/*! \brief Send SIP MESSAGE text within a call
-       Called from PBX core sendtext() application */
-static int sip_sendtext(struct ast_channel *ast, const char *text)
+/*! \brief Transmit packet with retransmits
+       \return 0 on success, -1 on failure to allocate packet
+*/
+static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod)
 {
-       struct sip_pvt *dialog = ast->tech_pvt;
-       int debug = sip_debug_test_pvt(dialog);
+       struct sip_pkt *pkt = NULL;
+       int siptimer_a = DEFAULT_RETRANS;
+       int xmitres = 0;
+       int respid;
 
-       if (!dialog)
-               return -1;
-       /* NOT ast_strlen_zero, because a zero-length message is specifically
-        * allowed by RFC 3428 (See section 10, Examples) */
-       if (!text)
-               return 0;
-       if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) {
-               ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n");
-               return(0);
+       if (sipmethod == SIP_INVITE) {
+               /* Note this is a pending invite */
+               p->pendinginvite = seqno;
        }
-       if (debug)
-               ast_verbose("Sending text %s on %s\n", text, ast->name);
-       transmit_message_with_text(dialog, text);
-       return 0;       
-}
 
-/*! \brief Update peer object in realtime storage
-       If the Asterisk system name is set in asterisk.conf, we will use
-       that name and store that in the "regserver" field in the sippeers
-       table to facilitate multi-server setups.
-*/
-static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms)
-{
-       char port[10];
-       char ipaddr[INET_ADDRSTRLEN];
-       char regseconds[20];
-       char *tablename = NULL;
-       char str_lastms[20];
-
-       const char *sysname = ast_config_AST_SYSTEM_NAME;
-       char *syslabel = NULL;
+       /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */
+       /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */
+       /*! \todo According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */
+       if (!(p->socket.type & SIP_TRANSPORT_UDP)) {
+               xmitres = __sip_xmit(p, data, len);     /* Send packet */
+               if (xmitres == XMIT_ERROR) {    /* Serious network trouble, no need to try again */
+                       append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
+                       return AST_FAILURE;
+               } else {
+                       return AST_SUCCESS;
+               }
+       }
 
-       time_t nowtime = time(NULL) + expirey;
-       const char *fc = fullcontact ? "fullcontact" : NULL;
+       if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
+               return AST_FAILURE;
+       /* copy data, add a terminator and save length */
+       if (!(pkt->data = ast_str_create(len))) {
+               ast_free(pkt);
+               return AST_FAILURE;
+       }
+       ast_str_set(&pkt->data, 0, "%s%s", data->str, "\0");
+       pkt->packetlen = len;
+       /* copy other parameters from the caller */
+       pkt->method = sipmethod;
+       pkt->seqno = seqno;
+       pkt->is_resp = resp;
+       pkt->is_fatal = fatal;
+       pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
+       pkt->next = p->packets;
+       p->packets = pkt;       /* Add it to the queue */
+       if (resp) {
+               /* Parse out the response code */
+               if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) {
+                       pkt->response_code = respid;
+               }
+       }
+       pkt->timer_t1 = p->timer_t1;    /* Set SIP timer T1 */
+       pkt->retransid = -1;
+       if (pkt->timer_t1)
+               siptimer_a = pkt->timer_t1 * 2;
 
-       int realtimeregs = ast_check_realtime("sipregs");
+       /* Schedule retransmission */
+       AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
+       if (sipdebug)
+               ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id  #%d\n", pkt->retransid);
 
-       tablename = realtimeregs ? "sipregs" : "sippeers";
-       
+       xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);    /* Send packet */
 
-       snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
-       snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime);   /* Expiration time */
-       ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
-       snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
-       
-       if (ast_strlen_zero(sysname))   /* No system name, disable this */
-               sysname = NULL;
-       else if (sip_cfg.rtsave_sysname)
-               syslabel = "regserver";
-
-       if (fc) {
-               ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
-                       "port", port, "regseconds", regseconds,
-                       deprecated_username ? "username" : "defaultuser", defaultuser,
-                       "useragent", useragent, "lastms", str_lastms,
-                       fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+       if (xmitres == XMIT_ERROR) {    /* Serious network trouble, no need to try again */
+               append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+               ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n");
+               AST_SCHED_DEL(sched, pkt->retransid);
+               p->packets = pkt->next;
+               pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
+               ast_free(pkt->data);
+               ast_free(pkt);
+               return AST_FAILURE;
        } else {
-               ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
-                       "port", port, "regseconds", regseconds,
-                       "useragent", useragent, "lastms", str_lastms,
-                       deprecated_username ? "username" : "defaultuser", defaultuser,
-                       syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+               return AST_SUCCESS;
        }
 }
 
-/*! \brief Automatically add peer extension to dial plan */
-static void register_peer_exten(struct sip_peer *peer, int onoff)
+/*! \brief Kill a SIP dialog (called only by the scheduler)
+ * The scheduler has a reference to this dialog when p->autokillid != -1,
+ * and we are called using that reference. So if the event is not
+ * rescheduled, we need to call dialog_unref().
+ */
+static int __sip_autodestruct(const void *data)
 {
-       char multi[256];
-       char *stringp, *ext, *context;
-       struct pbx_find_info q = { .stacklen = 0 };
+       struct sip_pvt *p = (struct sip_pvt *)data;
 
-       /* XXX note that sip_cfg.regcontext is both a global 'enable' flag and
-        * the name of the global regexten context, if not specified
-        * individually.
-        */
-       if (ast_strlen_zero(sip_cfg.regcontext))
-               return;
+       /* If this is a subscription, tell the phone that we got a timeout */
+       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");
+               ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
+               return 10000;   /* Reschedule this destruction so that we know that it's gone */
+       }
 
-       ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi));
-       stringp = multi;
-       while ((ext = strsep(&stringp, "&"))) {
-               if ((context = strchr(ext, '@'))) {
-                       *context++ = '\0';      /* split ext@context */
-                       if (!ast_context_find(context)) {
-                               ast_log(LOG_WARNING, "Context %s must exist in regcontext= in sip.conf!\n", context);
-                               continue;
+       /* If there are packets still waiting for delivery, delay the destruction */
+       if (p->packets) {
+               if (!p->needdestroy) {
+                       char method_str[31];
+                       ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
+                       append_history(p, "ReliableXmit", "timeout");
+                       if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) {
+                               if (method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) {
+                                       pvt_set_needdestroy(p, "autodestruct");
+                               }
                        }
+                       return 10000;
                } else {
-                       context = sip_cfg.regcontext;
-               }
-               if (onoff) {
-                       if (!ast_exists_extension(NULL, context, ext, 1, NULL)) {
-                               ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
-                                        ast_strdup(peer->name), ast_free_ptr, "SIP");
-                       }
-               } else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1, NULL, "", E_MATCH)) {
-                       ast_context_remove_extension(context, ext, 1, NULL);
+                       /* They've had their chance to respond. Time to bail */
+                       __sip_pretend_ack(p);
                }
        }
-}
-
-/*! Destroy mailbox subscriptions */
-static void destroy_mailbox(struct sip_mailbox *mailbox)
-{
-       if (mailbox->mailbox)
-               ast_free(mailbox->mailbox);
-       if (mailbox->context)
-               ast_free(mailbox->context);
-       if (mailbox->event_sub)
-               ast_event_unsubscribe(mailbox->event_sub);
-       ast_free(mailbox);
-}
 
-/*! Destroy all peer-related mailbox subscriptions */
-static void clear_peer_mailboxes(struct sip_peer *peer)
-{
-       struct sip_mailbox *mailbox;
+       if (p->subscribed == MWI_NOTIFICATION) {
+               if (p->relatedpeer) {
+                       p->relatedpeer = unref_peer(p->relatedpeer, "__sip_autodestruct: unref peer p->relatedpeer");   /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
+               }
+       }
 
-       while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, entry)))
-               destroy_mailbox(mailbox);
-}
+       /* Reset schedule ID */
+       p->autokillid = -1;
 
-static void sip_destroy_peer_fn(void *peer)
-{
-       sip_destroy_peer(peer);
+       if (p->owner) {
+               ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
+               ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
+       } else if (p->refer && !p->alreadygone) {
+               ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
+               transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
+               append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
+               sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+       } else {
+               append_history(p, "AutoDestroy", "%s", p->callid);
+               ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
+               dialog_unlink_all(p, TRUE, TRUE); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
+               /* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
+               /* sip_destroy(p); */           /* Go ahead and destroy dialog. All attempts to recover is done */
+               /* sip_destroy also absorbs the reference */
+       }
+       dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
+       return 0;
 }
 
-/*! \brief Destroy peer object from memory */
-static void sip_destroy_peer(struct sip_peer *peer)
+/*! \brief Schedule destruction of SIP dialog */
+void sip_scheddestroy(struct sip_pvt *p, int ms)
 {
-       ast_debug(3, "Destroying SIP peer %s\n", peer->name);
-       if (peer->outboundproxy)
-               ao2_ref(peer->outboundproxy, -1);
-       peer->outboundproxy = NULL;
-
-       /* Delete it, it needs to disappear */
-       if (peer->call) {
-               dialog_unlink_all(peer->call, TRUE, TRUE);
-               peer->call = dialog_unref(peer->call, "peer->call is being unset");
-       }
-       
-
-       if (peer->mwipvt) {     /* We have an active subscription, delete it */
-               dialog_unlink_all(peer->mwipvt, TRUE, TRUE);
-               peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt");
-       }
-       
-       if (peer->chanvars) {
-               ast_variables_destroy(peer->chanvars);
-               peer->chanvars = NULL;
+       if (ms < 0) {
+               if (p->timer_t1 == 0) {
+                       p->timer_t1 = global_t1;        /* Set timer T1 if not set (RFC 3261) */
+               }
+               if (p->timer_b == 0) {
+                       p->timer_b = global_timer_b;  /* Set timer B if not set (RFC 3261) */
+               }
+               ms = p->timer_t1 * 64;
        }
-       
-       register_peer_exten(peer, FALSE);
-       ast_free_ha(peer->ha);
-       if (peer->selfdestruct)
-               ast_atomic_fetchadd_int(&apeerobjs, -1);
-       else if (peer->is_realtime) {
-               ast_atomic_fetchadd_int(&rpeerobjs, -1);
-               ast_debug(3, "-REALTIME- peer Destroyed. Name: %s. Realtime Peer objects: %d\n", peer->name, rpeerobjs);
-       } else
-               ast_atomic_fetchadd_int(&speerobjs, -1);
-       clear_realm_authentication(peer->auth);
-       peer->auth = NULL;
-       if (peer->dnsmgr)
-               ast_dnsmgr_release(peer->dnsmgr);
-       clear_peer_mailboxes(peer);
+       if (sip_debug_test_pvt(p))
+               ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
+       if (sip_cancel_destroy(p))
+               ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
 
-       if (peer->socket.tcptls_session) {
-               ao2_ref(peer->socket.tcptls_session, -1);
-               peer->socket.tcptls_session = NULL;
-       }
+       if (p->do_history)
+               append_history(p, "SchedDestroy", "%d ms", ms);
+       p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct"));
 
-       ast_string_field_free_memory(peer);
+       if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
+               stop_session_timer(p);
 }
 
-/*! \brief Update peer data in database (if used) */
-static void update_peer(struct sip_peer *p, int expire)
+/*! \brief Cancel destruction of SIP dialog.
+ * Be careful as this also absorbs the reference - if you call it
+ * from within the scheduler, this might be the last reference.
+ */
+int sip_cancel_destroy(struct sip_pvt *p)
 {
-       int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS);
-       if (sip_cfg.peer_rtupdate &&
-           (p->is_realtime || rtcachefriends)) {
-               realtime_update_peer(p->name, &p->addr, p->username, rtcachefriends ? p->fullcontact : NULL, p->useragent, expire, p->deprecated_username, p->lastms);
-       }
-}
+       int res = 0;
+       if (p->autokillid > -1) {
+               int res3;
 
-static struct ast_variable *get_insecure_variable_from_config(struct ast_config *cfg)
-{
-       struct ast_variable *var = NULL;
-       struct ast_flags flags = {0};
-       char *cat = NULL;
-       const char *insecure;
-       while ((cat = ast_category_browse(cfg, cat))) {
-               insecure = ast_variable_retrieve(cfg, cat, "insecure");
-               set_insecure_flags(&flags, insecure, -1);
-               if (ast_test_flag(&flags, SIP_INSECURE_PORT)) {
-                       var = ast_category_root(cfg, cat);
-                       break;
+               if (!(res3 = ast_sched_del(sched, p->autokillid))) {
+                       append_history(p, "CancelDestroy", "");
+                       p->autokillid = -1;
+                       dialog_unref(p, "dialog unrefd because autokillid is de-sched'd");
                }
        }
-       return var;
+       return res;
 }
 
-static const char *get_name_from_variable(struct ast_variable *var, const char *newpeername)
+/*! \brief Acknowledges receipt of a packet and stops retransmission
+ * called with p locked*/
+int __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
 {
-       struct ast_variable *tmp;
-       for (tmp = var; tmp; tmp = tmp->next) {
-               if (!newpeername && !strcasecmp(tmp->name, "name"))
-                       newpeername = tmp->value;
+       struct sip_pkt *cur, *prev = NULL;
+       const char *msg = "Not Found";  /* used only for debugging */
+       int res = FALSE;
+
+       /* If we have an outbound proxy for this dialog, then delete it now since
+         the rest of the requests in this dialog needs to follow the routing.
+         If obforcing is set, we will keep the outbound proxy during the whole
+         dialog, regardless of what the SIP rfc says
+       */
+       if (p->outboundproxy && !p->outboundproxy->force){
+               ref_proxy(p, NULL);
        }
-       return newpeername;
-}
 
-/*! \brief  realtime_peer: Get peer from realtime storage
- * Checks the "sippeers" realtime family from extconfig.conf
- * Checks the "sipregs" realtime family from extconfig.conf if it's configured.
- * This returns a pointer to a peer and because we use build_peer, we can rest
- * assured that the refcount is bumped.
-*/
-static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_in *sin, int devstate_only)
-{
-       struct sip_peer *peer;
-       struct ast_variable *var = NULL;
-       struct ast_variable *varregs = NULL;
-       struct ast_variable *tmp;
-       struct ast_config *peerlist = NULL;
-       char ipaddr[INET_ADDRSTRLEN];
-       char portstring[6]; /*up to 5 digits plus null terminator*/
-       char *cat = NULL;
-       unsigned short portnum;
-       int realtimeregs = ast_check_realtime("sipregs");
-
-       /* First check on peer name */
-       if (newpeername) {
-               if (realtimeregs)
-                       varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
-
-               var = ast_load_realtime("sippeers", "name", newpeername, "host", "dynamic", SENTINEL);
-               if (!var && sin)
-                       var = ast_load_realtime("sippeers", "name", newpeername, "host", ast_inet_ntoa(sin->sin_addr), SENTINEL);
-               if (!var) {
-                       var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
-                       /*!\note
-                        * If this one loaded something, then we need to ensure that the host
-                        * field matched.  The only reason why we can't have this as a criteria
-                        * is because we only have the IP address and the host field might be
-                        * set as a name (and the reverse PTR might not match).
+       for (cur = p->packets; cur; prev = cur, cur = cur->next) {
+               if (cur->seqno != seqno || cur->is_resp != resp)
+                       continue;
+               if (cur->is_resp || cur->method == sipmethod) {
+                       res = TRUE;
+                       msg = "Found";
+                       if (!resp && (seqno == p->pendinginvite)) {
+                               ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
+                               p->pendinginvite = 0;
+                       }
+                       if (cur->retransid > -1) {
+                               if (sipdebug)
+                                       ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
+                       }
+                       /* This odd section is designed to thwart a
+                        * race condition in the packet scheduler. There are
+                        * two conditions under which deleting the packet from the
+                        * scheduler can fail.
+                        *
+                        * 1. The packet has been removed from the scheduler because retransmission
+                        * is being attempted. The problem is that if the packet is currently attempting
+                        * retransmission and we are at this point in the code, then that MUST mean
+                        * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
+                        * lock temporarily to allow retransmission.
+                        *
+                        * 2. The packet has reached its maximum number of retransmissions and has
+                        * been permanently removed from the packet scheduler. If this is the case, then
+                        * the packet's retransid will be set to -1. The atomicity of the setting and checking
+                        * of the retransid to -1 is ensured since in both cases p's lock is held.
                         */
-                       if (var && sin) {
-                               for (tmp = var; tmp; tmp = tmp->next) {
-                                       if (!strcasecmp(tmp->name, "host")) {
-                                               struct hostent *hp;
-                                               struct ast_hostent ahp;
-                                               if (!(hp = ast_gethostbyname(tmp->value, &ahp)) || (memcmp(hp->h_addr, &sin->sin_addr, sizeof(hp->h_addr)))) {
-                                                       /* No match */
-                                                       ast_variables_destroy(var);
-                                                       var = NULL;
-                                               }
-                                               break;
-                                       }
-                               }
+                       while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
+                               sip_pvt_unlock(p);
+                               usleep(1);
+                               sip_pvt_lock(p);
                        }
+                       UNLINK(cur, p->packets, prev);
+                       dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
+                       if (cur->data)
+                               ast_free(cur->data);
+                       ast_free(cur);
+                       break;
                }
        }
+       ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
+               p->callid, resp ? "Response" : "Request", seqno, msg);
+       return res;
+}
 
-       if (!var && sin) {      /* Then check on IP address for dynamic peers */
-               ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
-               portnum = ntohs(sin->sin_port);
-               sprintf(portstring, "%u", portnum);
-               var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL);      /* First check for fixed IP hosts */
-               if (var) {
-                       if (realtimeregs) {
-                               newpeername = get_name_from_variable(var, newpeername);
-                               varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
-                       }
-               } else {
-                       if (realtimeregs)
-                               varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
-                       else
-                               var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
-                       if (varregs) {
-                               newpeername = get_name_from_variable(varregs, newpeername);
-                               var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
-                       }
-               }
-               if (!var) { /*We couldn't match on ipaddress and port, so we need to check if port is insecure*/
-                       peerlist = ast_load_realtime_multientry("sippeers", "host", ipaddr, SENTINEL);
-                       if (peerlist) {
-                               var = get_insecure_variable_from_config(peerlist);
-                               if(var) {
-                                       if (realtimeregs) {
-                                               newpeername = get_name_from_variable(var, newpeername);
-                                               varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
-                                       }
-                               } else { /*var wasn't found in the list of "hosts", so try "ipaddr"*/
-                                       peerlist = NULL;
-                                       cat = NULL;
-                                       peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
-                                       if(peerlist) {
-                                               var = get_insecure_variable_from_config(peerlist);
-                                               if(var) {
-                                                       if (realtimeregs) {
-                                                               newpeername = get_name_from_variable(var, newpeername);
-                                                               varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
-                                                       }
-                                               }
-                                       }
-                               }
-                       } else {
-                               if (realtimeregs) {
-                                       peerlist = ast_load_realtime_multientry("sipregs", "ipaddr", ipaddr, SENTINEL);
-                                       if (peerlist) {
-                                               varregs = get_insecure_variable_from_config(peerlist);
-                                               if (varregs) {
-                                                       newpeername = get_name_from_variable(varregs, newpeername);
-                                                       var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
-                                               }
-                                       }
-                               } else {
-                                       peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
-                                       if (peerlist) {
-                                               var = get_insecure_variable_from_config(peerlist);
-                                               if (var) {
-                                                       newpeername = get_name_from_variable(var, newpeername);
-                                                       varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
-                                               }
-                                       }
-                               }
-                       }
+/*! \brief Pretend to ack all packets
+ * called with p locked */
+void __sip_pretend_ack(struct sip_pvt *p)
+{
+       struct sip_pkt *cur = NULL;
+
+       while (p->packets) {
+               int method;
+               if (cur == p->packets) {
+                       ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
+                       return;
                }
+               cur = p->packets;
+               method = (cur->method) ? cur->method : find_sip_method(cur->data->str);
+               __sip_ack(p, cur->seqno, cur->is_resp, method);
        }
+}
 
-       if (!var) {
-               if (peerlist)
-                       ast_config_destroy(peerlist);
-               return NULL;
-       }
+/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
+int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+{
+       struct sip_pkt *cur;
+       int res = FALSE;
 
-       for (tmp = var; tmp; tmp = tmp->next) {
-               /* If this is type=user, then skip this object. */
-               if (!strcasecmp(tmp->name, "type") &&
-                   !strcasecmp(tmp->value, "user")) {
-                       if(peerlist)
-                               ast_config_destroy(peerlist);
-                       else {
-                               ast_variables_destroy(var);
-                               ast_variables_destroy(varregs);
+       for (cur = p->packets; cur; cur = cur->next) {
+               if (cur->seqno == seqno && cur->is_resp == resp &&
+                       (cur->is_resp || method_match(sipmethod, cur->data->str))) {
+                       /* this is our baby */
+                       if (cur->retransid > -1) {
+                               if (sipdebug)
+                                       ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
                        }
-                       return NULL;
-               } else if (!newpeername && !strcasecmp(tmp->name, "name")) {
-                       newpeername = tmp->value;
+                       AST_SCHED_DEL(sched, cur->retransid);
+                       res = TRUE;
+                       break;
                }
        }
-       
-       if (!newpeername) {     /* Did not find peer in realtime */
-               ast_log(LOG_WARNING, "Cannot Determine peer name ip=%s\n", ipaddr);
-               if(peerlist)
-                       ast_config_destroy(peerlist);
-               else
-                       ast_variables_destroy(var);
-               return NULL;
+       ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");
+       return res;
+}
+
+
+/*! \brief Copy SIP request, parse it */
+static void parse_copy(struct sip_request *dst, const struct sip_request *src)
+{
+       copy_request(dst, src);
+       parse_request(dst);
+}
+
+/*! \brief add a blank line if no body */
+static void add_blank(struct sip_request *req)
+{
+       if (!req->lines) {
+               /* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
+               ast_str_append(&req->data, 0, "\r\n");
+               req->len = ast_str_strlen(req->data);
        }
+}
+
+static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
+{
+       const char *msg = NULL;
 
+       if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
+               msg = "183 Session Progress";
+       }
 
-       /* Peer found in realtime, now build it in memory */
-       peer = build_peer(newpeername, var, varregs, TRUE, devstate_only);
-       if (!peer) {
-               if(peerlist)
-                       ast_config_destroy(peerlist);
-               else {
-                       ast_variables_destroy(var);
-                       ast_variables_destroy(varregs);
+       if (pvt->invitestate < INV_COMPLETED) {
+               if (with_sdp) {
+                       transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE);
+               } else {
+                       transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
                }
-               return NULL;
+               return PROVIS_KEEPALIVE_TIMEOUT;
        }
 
-       ast_debug(3, "-REALTIME- loading peer from database to memory. Name: %s. Peer objects: %d\n", peer->name, rpeerobjs);
+       return 0;
+}
 
-       if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && !devstate_only) {
-               /* Cache peer */
-               ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_RTAUTOCLEAR|SIP_PAGE2_RTCACHEFRIENDS);
-               if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
-                       AST_SCHED_REPLACE_UNREF(peer->expire, sched, sip_cfg.rtautoclear * 1000, expire_register, peer,
-                                       unref_peer(_data, "remove registration ref"),
-                                       unref_peer(peer, "remove registration ref"),
-                                       ref_peer(peer, "add registration ref"));
-               }
-               ao2_t_link(peers, peer, "link peer into peers table");
-               if (peer->addr.sin_addr.s_addr) {
-                       ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table");
-               }
-       }
-       peer->is_realtime = 1;
-       if (peerlist)
-               ast_config_destroy(peerlist);
-       else {
-               ast_variables_destroy(var);
-               ast_variables_destroy(varregs);
-       }
+static int send_provisional_keepalive(const void *data) {
+       struct sip_pvt *pvt = (struct sip_pvt *) data;
 
-       return peer;
+       return send_provisional_keepalive_full(pvt, 0);
 }
 
-/* Function to assist finding peers by name only */
-static int find_by_name(void *obj, void *arg, void *data, int flags)
-{
-       struct sip_peer *search = obj, *match = arg;
-       int *which_objects = data;
+static int send_provisional_keepalive_with_sdp(const void *data) {
+       struct sip_pvt *pvt = (void *)data;
 
-       /* Usernames in SIP uri's are case sensitive. Domains are not */
-       if (strcmp(search->name, match->name)) {
-               return 0;
-       }
+       return send_provisional_keepalive_full(pvt, 1);
+}
 
-       switch (*which_objects) {
-       case FINDUSERS:
-               if (!(search->type & SIP_TYPE_USER)) {
-                       return 0;
-               }
-               break;
-       case FINDPEERS:
-               if (!(search->type & SIP_TYPE_PEER)) {
-                       return 0;
-               }
-               break;
-       case FINDALLDEVICES:
-               break;
-       }
+static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
+{
+       AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
 
-       return CMP_MATCH | CMP_STOP;
+       pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
+               with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
 }
 
-/*!
- * \brief Locate device by name or ip address
- *
- * \param which_objects Define which objects should be matched when doing a lookup
- *        by name.  Valid options are FINDUSERS, FINDPEERS, or FINDALLDEVICES.
- *        Note that this option is not used at all when doing a lookup by IP.
- *
- *     This is used on find matching device on name or ip/port.
- * If the device was declared as type=peer, we don't match on peer name on incoming INVITEs.
- *
- * \note Avoid using this function in new functions if there is a way to avoid it,
- * since it might cause a database lookup.
- */
-static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime, int which_objects, int devstate_only, int transport)
+/*! \brief Transmit response on SIP request*/
+static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
 {
-       struct sip_peer *p = NULL;
-       struct sip_peer tmp_peer;
+       int res;
 
-       if (peer) {
-               ast_copy_string(tmp_peer.name, peer, sizeof(tmp_peer.name));
-               p = ao2_t_callback_data(peers, OBJ_POINTER, find_by_name, &tmp_peer, &which_objects, "ao2_find in peers table");
-       } else if (sin) { /* search by addr? */
-               tmp_peer.addr.sin_addr.s_addr = sin->sin_addr.s_addr;
-               tmp_peer.addr.sin_port = sin->sin_port;
-               tmp_peer.flags[0].flags = 0;
-               tmp_peer.transports = transport;
-               p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table"); /* WAS:  p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */
-               if (!p) {
-                       ast_set_flag(&tmp_peer.flags[0], SIP_INSECURE_PORT);
-                       p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table 2"); /* WAS:  p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */
-                       if (p) {
-                               return p;
-                       }
-               }
+       add_blank(req);
+       if (sip_debug_test_pvt(p)) {
+               const struct sockaddr_in *dst = sip_real_dst(p);
+
+               ast_verbose("\n<--- %sTransmitting (%s) to %s:%d --->\n%s\n<------------>\n",
+                       reliable ? "Reliably " : "", sip_nat_mode(p),
+                       ast_inet_ntoa(dst->sin_addr),
+                       ntohs(dst->sin_port), req->data->str);
+       }
+       if (p->do_history) {
+               struct sip_request tmp = { .rlPart1 = 0, };
+               parse_copy(&tmp, req);
+               append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"),
+                       (tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlPart2) : sip_methods[tmp.method].text);
+               ast_free(tmp.data);
        }
 
-       if (!p && (realtime || devstate_only)) {
-               p = realtime_peer(peer, sin, devstate_only);
+       /* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
+       if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
+               AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
        }
 
-       return p;
+       res = (reliable) ?
+                __sip_reliable_xmit(p, seqno, 1, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
+               __sip_xmit(p, req->data, req->len);
+       ast_free(req->data);
+       req->data = NULL;
+       if (res > 0)
+               return 0;
+       return res;
 }
 
-/*! \brief Set nat mode on the various data sockets */
-static void do_setnat(struct sip_pvt *p)
+/*! \brief Send SIP Request to the other part of the dialogue 
+       \return see \ref __sip_xmit
+*/
+static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
 {
-       const char *mode;
-       int natflags;
-
-       natflags = ast_test_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP);
-       mode = natflags ? "On" : "Off";
+       int res;
 
-       if (p->rtp) {
-               ast_debug(1, "Setting NAT on RTP to %s\n", mode);
-               ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_NAT, natflags);
+       /* If we have an outbound proxy, reset peer address
+               Only do this once.
+       */
+       if (p->outboundproxy) {
+               p->sa = p->outboundproxy->ip;
        }
-       if (p->vrtp) {
-               ast_debug(1, "Setting NAT on VRTP to %s\n", mode);
-               ast_rtp_instance_set_prop(p->vrtp, AST_RTP_PROPERTY_NAT, natflags);
+
+       add_blank(req);
+       if (sip_debug_test_pvt(p)) {
+               if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT))
+                       ast_verbose("%sTransmitting (NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port), req->data->str);
+               else
+                       ast_verbose("%sTransmitting (no NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->sa.sin_addr), ntohs(p->sa.sin_port), req->data->str);
        }
-       if (p->udptl) {
-               ast_debug(1, "Setting NAT on UDPTL to %s\n", mode);
-               ast_udptl_setnat(p->udptl, natflags);
+       if (p->do_history) {
+               struct sip_request tmp = { .rlPart1 = 0, };
+               parse_copy(&tmp, req);
+               append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
+               ast_free(tmp.data);
        }
-       if (p->trtp) {
-               ast_debug(1, "Setting NAT on TRTP to %s\n", mode);
-               ast_rtp_instance_set_prop(p->trtp, AST_RTP_PROPERTY_NAT, natflags);
+       res = (reliable) ?
+               __sip_reliable_xmit(p, seqno, 0, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
+               __sip_xmit(p, req->data, req->len);
+       if (req->data) {
+               ast_free(req->data);
+               req->data = NULL;
        }
+       return res;
 }
 
-/*! \brief Change the T38 state on a SIP dialog */
-static void change_t38_state(struct sip_pvt *p, int state)
+static void enable_dsp_detect(struct sip_pvt *p)
 {
-       int old = p->t38.state;
-       struct ast_channel *chan = p->owner;
-       struct ast_control_t38_parameters parameters = { .request_response = 0 };
-
-       /* Don't bother changing if we are already in the state wanted */
-       if (old == state)
-               return;
-
-       p->t38.state = state;
-       ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, chan ? chan->name : "<none>");
+       int features = 0;
 
-       /* If no channel was provided we can't send off a control frame */
-       if (!chan)
+       if (p->dsp) {
                return;
-
-       /* Given the state requested and old state determine what control frame we want to queue up */
-       switch (state) {
-       case T38_PEER_REINVITE:
-               parameters = p->t38.their_parms;
-               parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
-               parameters.request_response = AST_T38_REQUEST_NEGOTIATE;
-               ast_udptl_set_tag(p->udptl, "SIP/%s", p->username);
-               break;
-       case T38_ENABLED:
-               parameters = p->t38.their_parms;
-               parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
-               parameters.request_response = AST_T38_NEGOTIATED;
-               ast_udptl_set_tag(p->udptl, "SIP/%s", p->username);
-               break;
-       case T38_DISABLED:
-               if (old == T38_ENABLED) {
-                       parameters.request_response = AST_T38_TERMINATED;
-               } else if (old == T38_LOCAL_REINVITE) {
-                       parameters.request_response = AST_T38_REFUSED;
-               }
-               break;
-       case T38_LOCAL_REINVITE:
-               /* wait until we get a peer response before responding to local reinvite */
-               break;
        }
 
-       /* Woot we got a message, create a control frame and send it on! */
-       if (parameters.request_response)
-               ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, &parameters, sizeof(parameters));
-}
+       if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
+            (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
+               if (!p->rtp || ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND)) {
+                       features |= DSP_FEATURE_DIGIT_DETECT;
+                }
+       }
 
-/*! \brief Set the global T38 capabilities on a SIP dialog structure */
-static void set_t38_capabilities(struct sip_pvt *p)
-{
-       if (p->udptl) {
-               if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY) {
-                        ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY);
-               } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_FEC) {
-                       ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_FEC);
-               } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL) {
-                       ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_NONE);
-               }
+       if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
+               features |= DSP_FEATURE_FAX_DETECT;
        }
-}
 
-static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket *from_sock)
-{
-       if (to_sock->tcptls_session) {
-               ao2_ref(to_sock->tcptls_session, -1);
-               to_sock->tcptls_session = NULL;
+       if (!features) {
+               return;
        }
 
-       if (from_sock->tcptls_session) {
-               ao2_ref(from_sock->tcptls_session, +1);
+       if (!(p->dsp = ast_dsp_new())) {
+               return;
        }
 
-       *to_sock = *from_sock;
+       ast_dsp_set_features(p->dsp, features);
+       if (global_relaxdtmf) {
+               ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
+       }
 }
 
-/*! \brief Initialize RTP portion of a dialog
- * \return -1 on failure, 0 on success
- */
-static int dialog_initialize_rtp(struct sip_pvt *dialog)
+static void disable_dsp_detect(struct sip_pvt *p)
 {
-       if (!sip_methods[dialog->method].need_rtp) {
-               return 0;
+       if (p->dsp) {
+               ast_dsp_free(p->dsp);
+               p->dsp = NULL;
        }
+}
 
-       if (!(dialog->rtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
-               return -1;
-       }
+/*! \brief Set an option on a SIP dialog */
+static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen)
+{
+       int res = -1;
+       struct sip_pvt *p = chan->tech_pvt;
 
-       if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT) && (dialog->capability & AST_FORMAT_VIDEO_MASK)) {
-               if (!(dialog->vrtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
-                       return -1;
-               }
-               ast_rtp_instance_set_timeout(dialog->vrtp, global_rtptimeout);
-               ast_rtp_instance_set_hold_timeout(dialog->vrtp, global_rtpholdtimeout);
+       switch (option) {
+       case AST_OPTION_FORMAT_READ:
+               res = ast_rtp_instance_set_read_format(p->rtp, *(int *) data);
+               break;
+       case AST_OPTION_FORMAT_WRITE:
+               res = ast_rtp_instance_set_write_format(p->rtp, *(int *) data);
+               break;
+       case AST_OPTION_MAKE_COMPATIBLE:
+               res = ast_rtp_instance_make_compatible(chan, p->rtp, (struct ast_channel *) data);
+               break;
+       case AST_OPTION_DIGIT_DETECT:
+               if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
+                   (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
+                       char *cp = (char *) data;
 
-               ast_rtp_instance_set_prop(dialog->vrtp, AST_RTP_PROPERTY_RTCP, 1);
+                       ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", chan->name);
+                       if (*cp) {
+                               enable_dsp_detect(p);
+                       } else {
+                               disable_dsp_detect(p);
+                       }
+                       res = 0;
+               }
+               break;
+       default:
+               break;
        }
 
-       if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_TEXTSUPPORT)) {
-               if (!(dialog->trtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
+       return res;
+}
+
+/*! \brief Query an option on a SIP dialog */
+static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen)
+{
+       int res = -1;
+       enum ast_t38_state state = T38_STATE_UNAVAILABLE;
+       struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt;
+       char *cp;
+
+       switch (option) {
+       case AST_OPTION_T38_STATE:
+               /* Make sure we got an ast_t38_state enum passed in */
+               if (*datalen != sizeof(enum ast_t38_state)) {
+                       ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen);
                        return -1;
                }
-               ast_rtp_instance_set_timeout(dialog->trtp, global_rtptimeout);
-               ast_rtp_instance_set_hold_timeout(dialog->trtp, global_rtpholdtimeout);
 
-               ast_rtp_instance_set_prop(dialog->trtp, AST_RTP_PROPERTY_RTCP, 1);
-       }
+               sip_pvt_lock(p);
 
-       ast_rtp_instance_set_timeout(dialog->rtp, global_rtptimeout);
-       ast_rtp_instance_set_hold_timeout(dialog->rtp, global_rtpholdtimeout);
+               /* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */
+               if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) {
+                       switch (p->t38.state) {
+                       case T38_LOCAL_REINVITE:
+                       case T38_PEER_REINVITE:
+                               state = T38_STATE_NEGOTIATING;
+                               break;
+                       case T38_ENABLED:
+                               state = T38_STATE_NEGOTIATED;
+                               break;
+                       default:
+                               state = T38_STATE_UNKNOWN;
+                       }
+               }
 
-       ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_RTCP, 1);
-       ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
-       ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+               sip_pvt_unlock(p);
 
-       ast_rtp_instance_set_qos(dialog->rtp, global_tos_audio, 0, "SIP RTP");
+               *((enum ast_t38_state *) data) = state;
+               res = 0;
 
-       do_setnat(dialog);
+               break;
+       case AST_OPTION_DIGIT_DETECT:
+               cp = (char *) data;
+               *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;
+       }
 
-       return 0;
+       return res;
 }
 
-/*! \brief Create address structure from peer reference.
- *     This function copies data from peer to the dialog, so we don't have to look up the peer
- *     again from memory or database during the life time of the dialog.
- *
- * \return -1 on error, 0 on success.
- *
+/*! \brief Locate closing quote in a string, skipping escaped quotes.
+ * optionally with a limit on the search.
+ * start must be past the first quote.
  */
-static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
+const char *find_closing_quote(const char *start, const char *lim)
 {
+       char last_char = '\0';
+       const char *s;
+       for (s = start; *s && s != lim; last_char = *s++) {
+               if (*s == '"' && last_char != '\\')
+                       break;
+       }
+       return s;
+}
 
-       /* this checks that the dialog is contacting the peer on a valid
-        * transport type based on the peers transport configuration,
-        * otherwise, this function bails out */
-       if (dialog->socket.type && check_request_transport(peer, dialog))
-               return -1;
-       copy_socket_data(&dialog->socket, &peer->socket);
+/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
+static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
+{
+       struct sip_pvt *p = chan->tech_pvt;
 
-       if ((peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr) &&
-           (!peer->maxms || ((peer->lastms >= 0)  && (peer->lastms <= peer->maxms)))) {
-               dialog->sa = (peer->addr.sin_addr.s_addr) ? peer->addr : peer->defaddr;
-               dialog->recv = dialog->sa;
-       } else
+       if (subclass != AST_HTML_URL)
                return -1;
 
-       ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
-       ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
-       dialog->capability = peer->capability;
-       dialog->prefs = peer->prefs;
-       if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) {
-               if (!dialog->udptl) {
-                       /* t38pt_udptl was enabled in the peer and not in [general] */
-                       dialog->udptl = ast_udptl_new_with_bindaddr(sched, io, 0, bindaddr.sin_addr);
-               }
-               dialog->t38_maxdatagram = peer->t38_maxdatagram;
-               set_t38_capabilities(dialog);
-       } else if (dialog->udptl) {
-               ast_udptl_destroy(dialog->udptl);
-               dialog->udptl = NULL;
+       ast_string_field_build(p, url, "<%s>;mode=active", data);
+
+       if (sip_debug_test_pvt(p))
+               ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
+
+       switch (chan->_state) {
+       case AST_STATE_RING:
+               transmit_response(p, "100 Trying", &p->initreq);
+               break;
+       case AST_STATE_RINGING:
+               transmit_response(p, "180 Ringing", &p->initreq);
+               break;
+       case AST_STATE_UP:
+               if (!p->pendinginvite) {                /* We are up, and have no outstanding invite */
+                       transmit_reinvite_with_sdp(p, FALSE, FALSE);
+               } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+                       ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);   
+               }       
+               break;
+       default:
+               ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
        }
 
-       ast_string_field_set(dialog, engine, peer->engine);
+       return 0;
+}
 
-       if (dialog_initialize_rtp(dialog)) {
+/*! \brief Deliver SIP call ID for the call */
+static const char *sip_get_callid(struct ast_channel *chan)
+{
+       return chan->tech_pvt ? ((struct sip_pvt *) chan->tech_pvt)->callid : "";
+}
+
+/*! \brief Send SIP MESSAGE text within a call
+       Called from PBX core sendtext() application */
+static int sip_sendtext(struct ast_channel *ast, const char *text)
+{
+       struct sip_pvt *dialog = ast->tech_pvt;
+       int debug = sip_debug_test_pvt(dialog);
+
+       if (!dialog)
                return -1;
+       /* NOT ast_strlen_zero, because a zero-length message is specifically
+        * allowed by RFC 3428 (See section 10, Examples) */
+       if (!text)
+               return 0;
+       if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) {
+               ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n");
+               return(0);
        }
+       if (debug)
+               ast_verbose("Sending text %s on %s\n", text, ast->name);
+       transmit_message_with_text(dialog, text);
+       return 0;       
+}
 
-       if (dialog->rtp) { /* Audio */
-               ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
-               ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
-               ast_rtp_instance_set_timeout(dialog->rtp, peer->rtptimeout);
-               ast_rtp_instance_set_hold_timeout(dialog->rtp, peer->rtpholdtimeout);
-               /* Set Frame packetization */
-               ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(dialog->rtp), dialog->rtp, &dialog->prefs);
-               dialog->autoframing = peer->autoframing;
-       }
-       if (dialog->vrtp) { /* Video */
-               ast_rtp_instance_set_timeout(dialog->vrtp, peer->rtptimeout);
-               ast_rtp_instance_set_hold_timeout(dialog->vrtp, peer->rtpholdtimeout);
-       }
-       if (dialog->trtp) { /* Realtime text */
-               ast_rtp_instance_set_timeout(dialog->trtp, peer->rtptimeout);
-               ast_rtp_instance_set_hold_timeout(dialog->trtp, peer->rtpholdtimeout);
-       }
+/*! \brief Update peer object in realtime storage
+       If the Asterisk system name is set in asterisk.conf, we will use
+       that name and store that in the "regserver" field in the sippeers
+       table to facilitate multi-server setups.
+*/
+static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms)
+{
+       char port[10];
+       char ipaddr[INET_ADDRSTRLEN];
+       char regseconds[20];
+       char *tablename = NULL;
+       char str_lastms[20];
 
-       ast_string_field_set(dialog, peername, peer->name);
-       ast_string_field_set(dialog, authname, peer->username);
-       ast_string_field_set(dialog, username, peer->username);
-       ast_string_field_set(dialog, peersecret, peer->secret);
-       ast_string_field_set(dialog, peermd5secret, peer->md5secret);
-       ast_string_field_set(dialog, mohsuggest, peer->mohsuggest);
-       ast_string_field_set(dialog, mohinterpret, peer->mohinterpret);
-       ast_string_field_set(dialog, tohost, peer->tohost);
-       ast_string_field_set(dialog, fullcontact, peer->fullcontact);
-       ast_string_field_set(dialog, accountcode, peer->accountcode);
-       ast_string_field_set(dialog, context, peer->context);
-       ast_string_field_set(dialog, cid_num, peer->cid_num);
-       ast_string_field_set(dialog, cid_name, peer->cid_name);
-       ast_string_field_set(dialog, mwi_from, peer->mwi_from);
-       ast_string_field_set(dialog, parkinglot, peer->parkinglot);
-       ast_string_field_set(dialog, engine, peer->engine);
-       ref_proxy(dialog, obproxy_get(dialog, peer));
-       dialog->callgroup = peer->callgroup;
-       dialog->pickupgroup = peer->pickupgroup;
-       dialog->allowtransfer = peer->allowtransfer;
-       dialog->jointnoncodeccapability = dialog->noncodeccapability;
-       dialog->rtptimeout = peer->rtptimeout;
-       dialog->peerauth = peer->auth;
-       dialog->maxcallbitrate = peer->maxcallbitrate;
-       dialog->disallowed_methods = peer->disallowed_methods;
-       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)) {
-               ast_string_field_set(dialog, fromdomain, peer->fromdomain);
-               if (!dialog->initreq.headers) {
-                       char *c;
-                       char *tmpcall = ast_strdupa(dialog->callid);
-                       /* this sure looks to me like we are going to change the callid on this dialog!! */
-                       c = strchr(tmpcall, '@');
-                       if (c) {
-                               *c = '\0';
-                               ao2_t_unlink(dialogs, dialog, "About to change the callid -- remove the old name");
-                               ast_string_field_build(dialog, callid, "%s@%s", tmpcall, peer->fromdomain);
-                               ao2_t_link(dialogs, dialog, "New dialog callid -- inserted back into table");
-                       }
-               }
-       }
-       if (!ast_strlen_zero(peer->fromuser))
-               ast_string_field_set(dialog, fromuser, peer->fromuser);
-       if (!ast_strlen_zero(peer->language))
-               ast_string_field_set(dialog, language, peer->language);
-       /* Set timer T1 to RTT for this peer (if known by qualify=) */
-       /* Minimum is settable or default to 100 ms */
-       /* If there is a maxms and lastms from a qualify use that over a manual T1
-          value. Otherwise, use the peer's T1 value. */
-       if (peer->maxms && peer->lastms)
-               dialog->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms;
-       else
-               dialog->timer_t1 = peer->timer_t1;
+       const char *sysname = ast_config_AST_SYSTEM_NAME;
+       char *syslabel = NULL;
 
-       /* Set timer B to control transaction timeouts, the peer setting is the default and overrides
-          the known timer */
-       if (peer->timer_b)
-               dialog->timer_b = peer->timer_b;
-       else
-               dialog->timer_b = 64 * dialog->timer_t1;
+       time_t nowtime = time(NULL) + expirey;
+       const char *fc = fullcontact ? "fullcontact" : NULL;
 
-       if ((ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
-           (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
-               dialog->noncodeccapability |= AST_RTP_DTMF;
-       else
-               dialog->noncodeccapability &= ~AST_RTP_DTMF;
-       if (peer->call_limit)
-               ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT);
-       if (!dialog->portinuri)
-               dialog->portinuri = peer->portinuri;
+       int realtimeregs = ast_check_realtime("sipregs");
+
+       tablename = realtimeregs ? "sipregs" : "sippeers";
        
-       dialog->chanvars = copy_vars(peer->chanvars);
 
-       return 0;
+       snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
+       snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime);   /* Expiration time */
+       ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
+       snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
+       
+       if (ast_strlen_zero(sysname))   /* No system name, disable this */
+               sysname = NULL;
+       else if (sip_cfg.rtsave_sysname)
+               syslabel = "regserver";
+
+       if (fc) {
+               ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+                       "port", port, "regseconds", regseconds,
+                       deprecated_username ? "username" : "defaultuser", defaultuser,
+                       "useragent", useragent, "lastms", str_lastms,
+                       fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+       } else {
+               ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+                       "port", port, "regseconds", regseconds,
+                       "useragent", useragent, "lastms", str_lastms,
+                       deprecated_username ? "username" : "defaultuser", defaultuser,
+                       syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+       }
 }
 
-/*! \brief create address structure from device name
- *      Or, if peer not found, find it in the global DNS
- *      returns TRUE (-1) on failure, FALSE on success */
-static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockaddr_in *sin, int newdialog, struct sockaddr_in *remote_address)
+/*! \brief Automatically add peer extension to dial plan */
+static void register_peer_exten(struct sip_peer *peer, int onoff)
 {
-      &nbs