<p>Friendly Automation <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/17989">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Joshua Colp: Looks good to me, but someone else must approve
  Kevin Harwell: Looks good to me, but someone else must approve
  Benjamin Keith Ford: Looks good to me, approved
  Friendly Automation: Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">app_queue: Add QueueWithdrawCaller AMI action<br><br>This adds a new AMI action called QueueWithdrawCaller.<br>This AMI action makes it possible to withdraw a caller from a queue,<br>in a safe and a generic manner.<br>This can be useful for retrieving a specific call and<br>dispatching it to a specific extension.<br>It works by signaling the caller to exit the queue application<br>whenever it can. Therefore, it is not guaranteed<br>that the call will leave the queue.<br><br>ASTERISK-29909 #close<br><br>Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec<br>---<br>M apps/app_queue.c<br>A doc/CHANGES-staging/queue_withdraw_caller.txt<br>2 files changed, 159 insertions(+), 1 deletion(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/apps/app_queue.c b/apps/app_queue.c</span><br><span>index 8ec98e2..3505413 100644</span><br><span>--- a/apps/app_queue.c</span><br><span>+++ b/apps/app_queue.c</span><br><span>@@ -290,6 +290,7 @@</span><br><span>                                      <value name="JOINUNAVAIL" /></span><br><span>                                         <value name="LEAVEUNAVAIL" /></span><br><span>                                        <value name="CONTINUE" /></span><br><span style="color: hsl(120, 100%, 40%);">+                                     <value name="WITHDRAW" /></span><br><span>                            </variable></span><br><span>                            <variable name="ABANDONED"></span><br><span>                                  <para>If the call was not answered by an agent this variable will be TRUE.</para></span><br><span>@@ -298,6 +299,9 @@</span><br><span>                          <variable name="DIALEDPEERNUMBER"></span><br><span>                                   <para>Resource of the agent that was dialed set on the outbound channel.</para></span><br><span>                          </variable></span><br><span style="color: hsl(120, 100%, 40%);">+                             <variable name="QUEUE_WITHDRAW_INFO"></span><br><span style="color: hsl(120, 100%, 40%);">+                                 <para>If the call was successfully withdrawn from the queue, and the withdraw request was provided with optional withdraw info, the withdraw info will be stored in this variable.</para></span><br><span style="color: hsl(120, 100%, 40%);">+                         </variable></span><br><span>                    </variablelist></span><br><span>                </description></span><br><span>                 <see-also></span><br><span>@@ -1057,6 +1061,25 @@</span><br><span>            <description></span><br><span>          </description></span><br><span>         </manager></span><br><span style="color: hsl(120, 100%, 40%);">+      <manager name="QueueWithdrawCaller" language="en_US"></span><br><span style="color: hsl(120, 100%, 40%);">+               <synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                      Request to withdraw a caller from the queue back to the dialplan.</span><br><span style="color: hsl(120, 100%, 40%);">+             </synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+             <syntax></span><br><span style="color: hsl(120, 100%, 40%);">+                        <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /></span><br><span style="color: hsl(120, 100%, 40%);">+                   <parameter name="Queue" required="true"></span><br><span style="color: hsl(120, 100%, 40%);">+                            <para>The name of the queue to take action on.</para></span><br><span style="color: hsl(120, 100%, 40%);">+                     </parameter></span><br><span style="color: hsl(120, 100%, 40%);">+                    <parameter name="Caller" required="true"></span><br><span style="color: hsl(120, 100%, 40%);">+                           <para>The caller (channel) to withdraw from the queue.</para></span><br><span style="color: hsl(120, 100%, 40%);">+                     </parameter></span><br><span style="color: hsl(120, 100%, 40%);">+                    <parameter name="WithdrawInfo" required="false"></span><br><span style="color: hsl(120, 100%, 40%);">+                            <para>Optional info to store. If the call is successfully withdrawn from the queue, this information will be available in the QUEUE_WITHDRAW_INFO variable.</para></span><br><span style="color: hsl(120, 100%, 40%);">+                        </parameter></span><br><span style="color: hsl(120, 100%, 40%);">+            </syntax></span><br><span style="color: hsl(120, 100%, 40%);">+               <description></span><br><span style="color: hsl(120, 100%, 40%);">+           </description></span><br><span style="color: hsl(120, 100%, 40%);">+  </manager></span><br><span> </span><br><span>         <managerEvent language="en_US" name="QueueParams"></span><br><span>                 <managerEventInstance class="EVENT_FLAG_AGENT"></span><br><span>@@ -1602,6 +1625,7 @@</span><br><span>      QUEUE_LEAVEUNAVAIL = 5,</span><br><span>      QUEUE_FULL = 6,</span><br><span>      QUEUE_CONTINUE = 7,</span><br><span style="color: hsl(120, 100%, 40%);">+   QUEUE_WITHDRAW = 8,</span><br><span> };</span><br><span> </span><br><span> static const struct {</span><br><span>@@ -1616,6 +1640,7 @@</span><br><span>       { QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },</span><br><span>    { QUEUE_FULL, "FULL" },</span><br><span>    { QUEUE_CONTINUE, "CONTINUE" },</span><br><span style="color: hsl(120, 100%, 40%);">+     { QUEUE_WITHDRAW, "WITHDRAW" },</span><br><span> };</span><br><span> </span><br><span> enum queue_timeout_priority {</span><br><span>@@ -1684,6 +1709,8 @@</span><br><span>         time_t start;                          /*!< When we started holding */</span><br><span>    time_t expire;                         /*!< When this entry should expire (time out of queue) */</span><br><span>  int cancel_answered_elsewhere;         /*!< Whether we should force the CAE flag on this call (C) option*/</span><br><span style="color: hsl(120, 100%, 40%);">+ unsigned int withdraw:1;               /*!< Should this call exit the queue at its next iteration? Used for QueueWithdrawCaller */</span><br><span style="color: hsl(120, 100%, 40%);">+ char *withdraw_info;                   /*!< Optional info passed by the caller of QueueWithdrawCaller */</span><br><span>  struct ast_channel *chan;              /*!< Our channel */</span><br><span>        AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */</span><br><span>         struct penalty_rule *pr;               /*!< Pointer to the next penalty rule to implement */</span><br><span>@@ -5802,6 +5829,13 @@</span><br><span>     /* This is the holding pen for callers 2 through maxlen */</span><br><span>   for (;;) {</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+                /* A request to withdraw this call from the queue arrived */</span><br><span style="color: hsl(120, 100%, 40%);">+          if (qe->withdraw) {</span><br><span style="color: hsl(120, 100%, 40%);">+                        *reason = QUEUE_WITHDRAW;</span><br><span style="color: hsl(120, 100%, 40%);">+                     res = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+                      break;</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>          if (is_our_turn(qe)) {</span><br><span>                       break;</span><br><span>               }</span><br><span>@@ -7622,6 +7656,51 @@</span><br><span> }</span><br><span> </span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*! \brief Request to withdraw a caller from a queue</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval RES_NOSUCHQUEUE queue does not exist</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval RES_OKAY withdraw request sent</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval RES_NOT_CALLER queue exists but no caller</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval RES_EXISTS a withdraw request was already sent for this caller (channel) and queue</span><br><span style="color: hsl(120, 100%, 40%);">+*/</span><br><span style="color: hsl(120, 100%, 40%);">+static int request_withdraw_caller_from_queue(const char *queuename, const char *caller, const char *withdraw_info)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    struct call_queue *q;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct queue_ent *qe;</span><br><span style="color: hsl(120, 100%, 40%);">+ int res = RES_NOSUCHQUEUE;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  /*! \note Ensure the appropriate realtime queue is loaded.  Note that this</span><br><span style="color: hsl(120, 100%, 40%);">+     * short-circuits if the queue is already in memory. */</span><br><span style="color: hsl(120, 100%, 40%);">+       if (!(q = find_load_queue_rt_friendly(queuename))) {</span><br><span style="color: hsl(120, 100%, 40%);">+          return res;</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ao2_lock(q);</span><br><span style="color: hsl(120, 100%, 40%);">+  res = RES_NOT_CALLER;</span><br><span style="color: hsl(120, 100%, 40%);">+ for (qe = q->head; qe; qe = qe->next) {</span><br><span style="color: hsl(120, 100%, 40%);">+         if (!strcmp(ast_channel_name(qe->chan), caller)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                 if (qe->withdraw) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                ast_debug(1, "Ignoring duplicate withdraw request of caller %s from queue %s\n", caller, queuename);</span><br><span style="color: hsl(120, 100%, 40%);">+                                res = RES_EXISTS;</span><br><span style="color: hsl(120, 100%, 40%);">+                     } else {</span><br><span style="color: hsl(120, 100%, 40%);">+                              ast_debug(1, "Requested withdraw of caller %s from queue %s\n", caller, queuename);</span><br><span style="color: hsl(120, 100%, 40%);">+                         /* It is not possible to change the withdraw info by further withdraw requests for this caller (channel)</span><br><span style="color: hsl(120, 100%, 40%);">+                                 in this queue, so we do not need to worry about a memory leak here. */</span><br><span style="color: hsl(120, 100%, 40%);">+                             if (withdraw_info) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                  qe->withdraw_info = ast_strdup(withdraw_info);</span><br><span style="color: hsl(120, 100%, 40%);">+                             }</span><br><span style="color: hsl(120, 100%, 40%);">+                             qe->withdraw = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+                          res = RES_OKAY;</span><br><span style="color: hsl(120, 100%, 40%);">+                       }</span><br><span style="color: hsl(120, 100%, 40%);">+                     break;</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+     ao2_unlock(q);</span><br><span style="color: hsl(120, 100%, 40%);">+        queue_unref(q);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     return res;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static int publish_queue_member_pause(struct call_queue *q, struct member *member)</span><br><span> {</span><br><span>        struct ast_json *json_blob = queue_member_blob_create(q, member);</span><br><span>@@ -8569,6 +8648,13 @@</span><br><span>           /* they may dial a digit from the queue context; */</span><br><span>          /* or, they may timeout. */</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+               /* A request to withdraw this call from the queue arrived */</span><br><span style="color: hsl(120, 100%, 40%);">+          if (qe.withdraw) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    reason = QUEUE_WITHDRAW;</span><br><span style="color: hsl(120, 100%, 40%);">+                      res = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+                      break;</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>          /* Leave if we have exceeded our queuetimeout */</span><br><span>             if (qe.expire && (time(NULL) >= qe.expire)) {</span><br><span>                     record_abandoned(&qe);</span><br><span>@@ -8596,6 +8682,13 @@</span><br><span>                  }</span><br><span>            }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+         /* A request to withdraw this call from the queue arrived */</span><br><span style="color: hsl(120, 100%, 40%);">+          if (qe.withdraw) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    reason = QUEUE_WITHDRAW;</span><br><span style="color: hsl(120, 100%, 40%);">+                      res = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+                      break;</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>          /* Leave if we have exceeded our queuetimeout */</span><br><span>             if (qe.expire && (time(NULL) >= qe.expire)) {</span><br><span>                     record_abandoned(&qe);</span><br><span>@@ -8670,7 +8763,14 @@</span><br><span> </span><br><span> stop:</span><br><span>     if (res) {</span><br><span style="color: hsl(0, 100%, 40%);">-              if (res < 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+             if (reason == QUEUE_WITHDRAW) {</span><br><span style="color: hsl(120, 100%, 40%);">+                       record_abandoned(&qe);</span><br><span style="color: hsl(120, 100%, 40%);">+                    ast_queue_log(qe.parent->name, ast_channel_uniqueid(qe.chan), "NONE", "WITHDRAW", "%d|%d|%ld|%.40s", qe.pos, qe.opos, (long) (time(NULL) - qe.start), qe.withdraw_info ? qe.withdraw_info : "");</span><br><span style="color: hsl(120, 100%, 40%);">+                   if (qe.withdraw_info) {</span><br><span style="color: hsl(120, 100%, 40%);">+                               pbx_builtin_setvar_helper(qe.chan, "QUEUE_WITHDRAW_INFO", qe.withdraw_info);</span><br><span style="color: hsl(120, 100%, 40%);">+                        }</span><br><span style="color: hsl(120, 100%, 40%);">+                     res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+              } else if (res < 0) {</span><br><span>                     if (!qe.handled) {</span><br><span>                           record_abandoned(&qe);</span><br><span>                           ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "ABANDON",</span><br><span>@@ -8690,6 +8790,13 @@</span><br><span>            }</span><br><span>    }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* Free the optional withdraw info if present */</span><br><span style="color: hsl(120, 100%, 40%);">+      /* This is done here to catch all cases. e.g. if the call eventually wasn't withdrawn, e.g. answered */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (qe.withdraw_info) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_free(qe.withdraw_info);</span><br><span style="color: hsl(120, 100%, 40%);">+           qe.withdraw_info = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>  /* Don't allow return code > 0 */</span><br><span>     if (res >= 0) {</span><br><span>           res = 0;</span><br><span>@@ -10743,6 +10850,41 @@</span><br><span>  return 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static int manager_request_withdraw_caller_from_queue(struct mansession *s, const struct message *m)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   const char *queuename, *caller, *withdraw_info;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     queuename = astman_get_header(m, "Queue");</span><br><span style="color: hsl(120, 100%, 40%);">+  caller = astman_get_header(m, "Caller");</span><br><span style="color: hsl(120, 100%, 40%);">+    withdraw_info = astman_get_header(m, "WithdrawInfo");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     if (ast_strlen_zero(queuename)) {</span><br><span style="color: hsl(120, 100%, 40%);">+             astman_send_error(s, m, "'Queue' not specified.");</span><br><span style="color: hsl(120, 100%, 40%);">+          return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(caller)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                astman_send_error(s, m, "'Caller' not specified.");</span><br><span style="color: hsl(120, 100%, 40%);">+         return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   switch (request_withdraw_caller_from_queue(queuename, caller, withdraw_info)) {</span><br><span style="color: hsl(120, 100%, 40%);">+       case RES_OKAY:</span><br><span style="color: hsl(120, 100%, 40%);">+                astman_send_ack(s, m, "Withdraw requested successfully");</span><br><span style="color: hsl(120, 100%, 40%);">+           break;</span><br><span style="color: hsl(120, 100%, 40%);">+        case RES_NOSUCHQUEUE:</span><br><span style="color: hsl(120, 100%, 40%);">+         astman_send_error(s, m, "Unable to request withdraw from queue: No such queue");</span><br><span style="color: hsl(120, 100%, 40%);">+            break;</span><br><span style="color: hsl(120, 100%, 40%);">+        case RES_NOT_CALLER:</span><br><span style="color: hsl(120, 100%, 40%);">+          astman_send_error(s, m, "Unable to request withdraw from queue: No such caller");</span><br><span style="color: hsl(120, 100%, 40%);">+           break;</span><br><span style="color: hsl(120, 100%, 40%);">+        case RES_EXISTS:</span><br><span style="color: hsl(120, 100%, 40%);">+              astman_send_error(s, m, "Unable to request withdraw from queue: Already requested");</span><br><span style="color: hsl(120, 100%, 40%);">+                break;</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span> </span><br><span> </span><br><span> static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)</span><br><span>@@ -11472,6 +11614,7 @@</span><br><span>     ast_manager_unregister("QueueReset");</span><br><span>      ast_manager_unregister("QueueMemberRingInUse");</span><br><span>    ast_manager_unregister("QueueChangePriorityCaller");</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_manager_unregister("QueueWithdrawCaller");</span><br><span>     ast_unregister_application(app_aqm);</span><br><span>         ast_unregister_application(app_rqm);</span><br><span>         ast_unregister_application(app_pqm);</span><br><span>@@ -11585,6 +11728,7 @@</span><br><span>       err |= ast_manager_register_xml("QueueReload", 0, manager_queue_reload);</span><br><span>   err |= ast_manager_register_xml("QueueReset", 0, manager_queue_reset);</span><br><span>     err |= ast_manager_register_xml("QueueChangePriorityCaller", 0,  manager_change_priority_caller_on_queue);</span><br><span style="color: hsl(120, 100%, 40%);">+  err |= ast_manager_register_xml("QueueWithdrawCaller", 0,  manager_request_withdraw_caller_from_queue);</span><br><span>    err |= ast_custom_function_register(&queuevar_function);</span><br><span>         err |= ast_custom_function_register(&queueexists_function);</span><br><span>      err |= ast_custom_function_register(&queuemembercount_function);</span><br><span>diff --git a/doc/CHANGES-staging/queue_withdraw_caller.txt b/doc/CHANGES-staging/queue_withdraw_caller.txt</span><br><span>new file mode 100644</span><br><span>index 0000000..04e43d0</span><br><span>--- /dev/null</span><br><span>+++ b/doc/CHANGES-staging/queue_withdraw_caller.txt</span><br><span>@@ -0,0 +1,14 @@</span><br><span style="color: hsl(120, 100%, 40%);">+Subject: app_queue</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Added a new AMI action: QueueWithdrawCaller</span><br><span style="color: hsl(120, 100%, 40%);">+This AMI action makes it possible to withdraw a caller from a queue</span><br><span style="color: hsl(120, 100%, 40%);">+back to the dialplan. The call will be signaled to leave the queue</span><br><span style="color: hsl(120, 100%, 40%);">+whenever it can, hence, it not guaranteed that the call will leave</span><br><span style="color: hsl(120, 100%, 40%);">+the queue.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Optional custom data can be passed in the request, in the WithdrawInfo</span><br><span style="color: hsl(120, 100%, 40%);">+parameter. If the call successfully withdrawn the queue,</span><br><span style="color: hsl(120, 100%, 40%);">+it can be retrieved using the QUEUE_WITHDRAW_INFO variable.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+This can be useful for certain uses, such as dispatching the call</span><br><span style="color: hsl(120, 100%, 40%);">+to a specific extension.</span><br><span></span><br></pre><div style="white-space:pre-wrap"></div><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/17989">change 17989</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/c/asterisk/+/17989"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 18 </div>
<div style="display:none"> Gerrit-Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec </div>
<div style="display:none"> Gerrit-Change-Number: 17989 </div>
<div style="display:none"> Gerrit-PatchSet: 13 </div>
<div style="display:none"> Gerrit-Owner: Kfir Itzhak <mastertheknife@gmail.com> </div>
<div style="display:none"> Gerrit-Reviewer: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@sangoma.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>