Add support for call pickup on Snom phones. Asterisk now includes a magic
authorRussell Bryant <russell@russellbryant.com>
Tue, 30 Sep 2008 21:32:53 +0000 (21:32 +0000)
committerRussell Bryant <russell@russellbryant.com>
Tue, 30 Sep 2008 21:32:53 +0000 (21:32 +0000)
call-id in the dialog-info event package used with extension state subscriptions
on Snom phones.  Then, when the phone sends an INVITE with Replaces for the
special callid, Asterisk will perform a pickup on the extension that was
subscribed to.

The original code on this issue was submitted by xylome.  However, contributions
have been made by (at least) mgernoth and pkempgen.  The final patch was written
by seanbright, and includes the necessary logic to allow this work in a
technology independent way.

(closes issue #5014)
Reported by: xylome
Patches:
      issue5014-trunk.diff uploaded by seanbright (license 71)

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

CHANGES
channels/chan_sip.c

diff --git a/CHANGES b/CHANGES
index 0591e98..08fbb8d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,12 @@
 --- Functionality changes from Asterisk 1.6.1 to Asterisk 1.6.2  -------------
 ------------------------------------------------------------------------------
 
+SIP Changes
+-----------
+ * Added support for SUBSCRIBE/NOTIFY with dialog-info based call pickups.
+    Snom phones use this for call pickup of extensions that the phone is
+       subscribed to.
+
 Dialplan Functions
 ------------------
  * Added a new dialplan function, CURLOPT, which permits setting various
index e5d911e..3bba054 100644 (file)
@@ -1957,6 +1957,7 @@ static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer
 static int sip_refer_allocate(struct sip_pvt *p);
 static void ast_quiet_chan(struct ast_channel *chan);
 static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target);
+static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context);
 /*!
  * \brief generic function for determining if a correct transport is being 
  * used to contact a peer
@@ -9367,11 +9368,28 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim
                break;
        case DIALOG_INFO_XML: /* SNOM subscribes in this format */
                ast_str_append(&tmp, 0, "<?xml version=\"1.0\"?>\n");
-               ast_str_append(&tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%d\" state=\"%s\" entity=\"%s\">\n", p->dialogver++, full ? "full":"partial", mto);
-               if ((state & AST_EXTENSION_RINGING) && global_notifyringing)
-                       ast_str_append(&tmp, 0, "<dialog id=\"%s\" direction=\"recipient\">\n", p->exten);
-               else
+               ast_str_append(&tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%d\" state=\"%s\" entity=\"%s\">\n", p->dialogver++, full ? "full" : "partial", mto);
+               if ((state & AST_EXTENSION_RINGING) && global_notifyringing) {
+                       /* We create a fake call-id which the phone will send back in an INVITE
+                          Replaces header which we can grab and do some magic with. */
+                       ast_str_append(&tmp, 0, 
+                                       "<dialog id=\"%s\" call-id=\"pickup-%s\" direction=\"recipient\">\n"
+                                       "<remote>\n"
+                                       /* Note that the identity and target elements for the local participant are currently
+                                          (and may forever be) incorrect since we have no reliable way to get at that information 
+                                          at the moment.  Luckily the phone seems to still live happily without it being correct */
+                                       "<identity>%s</identity>\n"
+                                       "<target uri=\"%s\"/>\n"
+                                       "</remote>\n"
+                                       "<local>\n"
+                                       "<identity>%s</identity>\n"
+                                       "<target uri=\"%s\"/>\n"
+                                       "</local>\n",
+                                       p->exten, p->callid,
+                                       mto, mto, mto, mto);
+               } else {
                        ast_str_append(&tmp, 0, "<dialog id=\"%s\">\n", p->exten);
+               }
                ast_str_append(&tmp, 0, "<state>%s</state>\n", statestring);
                if (state == AST_EXTENSION_ONHOLD) {
                        ast_str_append(&tmp, 0, "<local>\n<target uri=\"%s\">\n"
@@ -17116,6 +17134,27 @@ static int sip_uri_cmp(const char *input1, const char *input2)
        return sip_uri_params_cmp(params1, params2);
 }
 
+static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context)
+{
+       struct ast_str *str = ast_str_alloca(AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2);
+       struct ast_app *pickup = pbx_findapp("Pickup");
+
+       if (!pickup) {
+               ast_log(LOG_ERROR, "Unable to perform pickup: Application 'Pickup' not loaded (app_directed_pickup.so).\n");
+               return -1;
+       }
+
+       ast_str_set(&str, 0, "%s@%s", extension, context);
+
+       ast_debug(2, "About to call Pickup(%s)\n", str->str);
+
+       /* There is no point in capturing the return value since pickup_exec
+          doesn't return anything meaningful unless the passed data is an empty
+          string (which in our case it will not be) */
+       pbx_exec(channel, pickup, str->str);
+
+       return 0;
+}
 
 /*! \brief Handle incoming INVITE request
 \note  If the INVITE has a Replaces header, it is part of an
@@ -17143,6 +17182,12 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int
        int st_interval = 0;            /* Session-Timer negotiated refresh interval                */
        enum st_refresher st_ref;       /* Session-Timer session refresher                          */
        int dlg_min_se = -1;
+       struct {
+               char exten[AST_MAX_EXTENSION];
+               char context[AST_MAX_CONTEXT];
+       } pickup = {
+               .exten = "",    
+       };
        st_ref = SESSION_TIMER_REFRESHER_AUTO;
 
        /* Find out what they support */
@@ -17279,14 +17324,36 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int
                        }
                }
 
-               if (sipdebug) 
-                       ast_debug(4, "Invite/replaces: Will use Replace-Call-ID : %s Fromtag: %s Totag: %s\n", replace_id, fromtag ? fromtag : "<no from tag>", totag ? totag : "<no to tag>");
-
+               if (sipdebug)
+                       ast_debug(4, "Invite/replaces: Will use Replace-Call-ID : %s Fromtag: %s Totag: %s\n",
+                                         replace_id,
+                                         fromtag ? fromtag : "<no from tag>",
+                                         totag ? totag : "<no to tag>");
+
+               /* Try to find call that we are replacing.
+                  If we have a Replaces header, we need to cancel that call if we succeed with this call.
+                  First we cheat a little and look for a magic call-id from phones that support
+                  dialog-info+xml so we can do technology independent pickup... */
+               if (strncmp(replace_id, "pickup-", 7) == 0) {
+                       struct sip_pvt *subscription = NULL;
+                       replace_id += 7; /* Worst case we are looking at \0 */
+
+                       if ((subscription = get_sip_pvt_byid_locked(replace_id, NULL, NULL)) == NULL) {
+                               ast_log(LOG_NOTICE, "Unable to find subscription with call-id: %s\n", replace_id);
+                               transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req);
+                               error = 1;
+                       } else {
+                               ast_log(LOG_NOTICE, "Trying to pick up %s@%s\n", subscription->exten, subscription->context);
+                               ast_copy_string(pickup.exten, subscription->exten, sizeof(pickup.exten));
+                               ast_copy_string(pickup.context, subscription->context, sizeof(pickup.context));
+                               sip_pvt_unlock(subscription);
+                               if (subscription->owner) {
+                                       ast_channel_unlock(subscription->owner);
+                               }
+                       }
+               }
 
-               /* Try to find call that we are replacing 
-                       If we have a Replaces  header, we need to cancel that call if we succeed with this call 
-               */
-               if ((p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag)) == NULL) {
+               if (!error && ast_strlen_zero(pickup.exten) && (p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag)) == NULL) {
                        ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existent call id (%s)!\n", replace_id);
                        transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req);
                        error = 1;
@@ -17305,7 +17372,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int
                        error = 1;
                }
 
-               if (!error && !p->refer->refer_call->owner) {
+               if (!error && ast_strlen_zero(pickup.exten) && !p->refer->refer_call->owner) {
                        /* Oops, someting wrong anyway, no owner, no call */
                        ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existing call id (%s)!\n", replace_id);
                        /* Check for better return code */
@@ -17313,7 +17380,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int
                        error = 1;
                }
 
-               if (!error && p->refer->refer_call->owner->_state != AST_STATE_RINGING && p->refer->refer_call->owner->_state != AST_STATE_RING && p->refer->refer_call->owner->_state != AST_STATE_UP ) {
+               if (!error && ast_strlen_zero(pickup.exten) && p->refer->refer_call->owner->_state != AST_STATE_RINGING && p->refer->refer_call->owner->_state != AST_STATE_RING && p->refer->refer_call->owner->_state != AST_STATE_UP) {
                        ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active call id (%s)!\n", replace_id);
                        transmit_response_reliable(p, "603 Declined (Replaces)", req);
                        error = 1;
@@ -17626,10 +17693,28 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int
                p->lastinvite = seqno;
 
        if (replace_id) {       /* Attended transfer or call pickup - we're the target */
-               /* Go and take over the target call */
-               if (sipdebug)
-                       ast_debug(4, "Sending this call to the invite/replcaes handler %s\n", p->callid);
-               return handle_invite_replaces(p, req, debug, seqno, sin);
+               if (!ast_strlen_zero(pickup.exten)) {
+                       append_history(p, "Xfer", "INVITE/Replace received");
+
+                       /* Let the caller know we're giving it a shot */
+                       transmit_response(p, "100 Trying", req);
+                       ast_setstate(c, AST_STATE_RING);
+
+                       /* Do the pickup itself */
+                       ast_channel_unlock(c);
+                       *nounlock = 1;
+                       do_magic_pickup(c, pickup.exten, pickup.context);
+
+                       /* Now we're either masqueraded or we failed to pickup, in either case we... */
+                       ast_hangup(c);
+
+                       return 0;
+               } else {
+                       /* Go and take over the target call */
+                       if (sipdebug)
+                               ast_debug(4, "Sending this call to the invite/replcaes handler %s\n", p->callid);
+                       return handle_invite_replaces(p, req, debug, seqno, sin);
+               }
        }