<p>Jenkins2 <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/6483">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, approved
  Jenkins2: Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">res_pjsip:  Add handling for incoming unsolicited MWI NOTIFY<br><br>A new endpoint parameter "incoming_mwi_mailbox" allows Asterisk to<br>receive unsolicited MWI NOTIFY requests and make them available to<br>other modules via the stasis message bus.<br><br>res_pjsip_pubsub has a new handler "pubsub_on_rx_mwi_notify_request"<br>that parses a simple-message-summary body and, if<br>endpoint->incoming_mwi_account is set, calls ast_publish_mwi_state<br>with the voice-message counts from the message.<br><br>Change-Id: I08bae3d16e77af48fcccc2c936acce8fc0ef0f3c<br>---<br>M CHANGES<br>M configs/samples/pjsip.conf.sample<br>A contrib/ast-db-manage/config/versions/a1698e8bb9c5_add_incoming_mwi_mailbox.py<br>M include/asterisk/res_pjsip.h<br>M include/asterisk/strings.h<br>M main/strings.c<br>M res/res_pjsip.c<br>M res/res_pjsip/pjsip_configuration.c<br>M res/res_pjsip_pubsub.c<br>9 files changed, 217 insertions(+), 0 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/CHANGES b/CHANGES<br>index 67454bc..dde24e2 100644<br>--- a/CHANGES<br>+++ b/CHANGES<br>@@ -54,6 +54,10 @@<br>    when dnsmgr refreshes are enabled will be automatically updated with the new<br>    IP address of a given hostname.<br> <br>+ * A new endpoint parameter "incoming_mwi_mailbox" allows Asterisk to receive<br>+   unsolicited MWI NOTIFY requests and make them available to other modules via<br>+   the stasis message bus.<br>+<br> res_musiconhold<br> ------------------<br>  * By default, when res_musiconhold reloads or unloads, it sends a HUP signal<br>diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample<br>index 3c3e52a..1e1029b 100644<br>--- a/configs/samples/pjsip.conf.sample<br>+++ b/configs/samples/pjsip.conf.sample<br>@@ -798,6 +798,12 @@<br>          ;     dtls_setup=actpass<br>          ; A dtls_cert_file and a dtls_ca_file still need to be specified.<br>          ; Default for this option is "no"<br>+;incoming_mwi_mailbox = ; Mailbox name to use when incoming MWI NOTIFYs are<br>+                        ; received.<br>+                        ; If an MWI NOTIFY is received FROM this endpoint,<br>+                        ; this mailbox will be used when notifying other modules<br>+                        ; of MWI status changes.  If not set, incoming MWI<br>+                        ; NOTIFYs are ignored.<br> <br> ;==========================AUTH SECTION OPTIONS=========================<br> ;[auth]<br>diff --git a/contrib/ast-db-manage/config/versions/a1698e8bb9c5_add_incoming_mwi_mailbox.py b/contrib/ast-db-manage/config/versions/a1698e8bb9c5_add_incoming_mwi_mailbox.py<br>new file mode 100644<br>index 0000000..86c307e<br>--- /dev/null<br>+++ b/contrib/ast-db-manage/config/versions/a1698e8bb9c5_add_incoming_mwi_mailbox.py<br>@@ -0,0 +1,21 @@<br>+"""add_incoming_mwi_mailbox<br>+<br>+Revision ID: a1698e8bb9c5<br>+Revises: b83645976fdd<br>+Create Date: 2017-09-08 13:45:18.937571<br>+<br>+"""<br>+<br>+# revision identifiers, used by Alembic.<br>+revision = 'a1698e8bb9c5'<br>+down_revision = 'b83645976fdd'<br>+<br>+from alembic import op<br>+import sqlalchemy as sa<br>+<br>+<br>+def upgrade():<br>+    op.add_column('ps_endpoints', sa.Column('incoming_mwi_mailbox', sa.String(40)))<br>+<br>+def downgrade():<br>+    op.drop_column('ps_endpoints', 'incoming_mwi_mailbox')<br>diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h<br>index ad88138..b6403d6 100644<br>--- a/include/asterisk/res_pjsip.h<br>+++ b/include/asterisk/res_pjsip.h<br>@@ -804,6 +804,8 @@<br>      unsigned int refer_blind_progress;<br>    /*! Whether to notifies dialog-info 'early' on INUSE && RINGING state */<br>      unsigned int notify_early_inuse_ringing;<br>+     /*! If set, we'll push incoming MWI NOTIFYs to stasis using this mailbox */<br>+      AST_STRING_FIELD_EXTENDED(incoming_mwi_mailbox);<br> };<br> <br> /*! URI parameter for symmetric transport */<br>diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h<br>index 1200eb9..963519d 100644<br>--- a/include/asterisk/strings.h<br>+++ b/include/asterisk/strings.h<br>@@ -1383,4 +1383,24 @@<br>  */<br> int ast_strings_match(const char *left, const char *op, const char *right);<br> <br>+/*!<br>+ * \brief Read lines from a string buffer<br>+ * \since 13.18.0<br>+ *<br>+ * \param buffer [IN/OUT] A pointer to a char * string with either Unix or Windows line endings<br>+ *<br>+ * \return The "next" line<br>+ *<br>+ * \warning The original string and *buffer will be modified.<br>+ *<br>+ * \details<br>+ * Both '\n' and '\r\n' are treated as single delimiters but consecutive occurrances of<br>+ * the delimiters are NOT considered to be a single delimiter.  This preserves blank<br>+ * lines in the input.<br>+ *<br>+ * MacOS line endings ('\r') are not supported at this time.<br>+ *<br>+ */<br>+char *ast_read_line_from_buffer(char **buffer);<br>+<br> #endif /* _ASTERISK_STRINGS_H */<br>diff --git a/main/strings.c b/main/strings.c<br>index d29da67..3207fa1 100644<br>--- a/main/strings.c<br>+++ b/main/strings.c<br>@@ -369,4 +369,23 @@<br>      return 0;<br> }<br> <br>+char *ast_read_line_from_buffer(char **buffer)<br>+{<br>+        char *start = *buffer;<br> <br>+    if (!buffer || !*buffer || *(*buffer) == '\0') {<br>+             return NULL;<br>+ }<br>+<br>+ while (*(*buffer) && *(*buffer) != '\n' ) {<br>+          (*buffer)++;<br>+ }<br>+<br>+ *(*buffer) = '\0';<br>+   if (*(*buffer - 1) == '\r') {<br>+                *(*buffer - 1) = '\0';<br>+       }<br>+    (*buffer)++;<br>+<br>+      return start;<br>+}<br>diff --git a/res/res_pjsip.c b/res/res_pjsip.c<br>index ca0c301..0ff6ab8 100644<br>--- a/res/res_pjsip.c<br>+++ b/res/res_pjsip.c<br>@@ -1025,6 +1025,14 @@<br>                                                <para>dtls_setup=actpass</para><br>                                   </description><br>                          </configOption><br>+                                <configOption name="incoming_mwi_mailbox"><br>+                                   <synopsis>Mailbox name to use when incoming MWI NOTIFYs are received</synopsis><br>+                                  <description><para><br>+                                              If an MWI NOTIFY is received <emphasis>from</emphasis> this endpoint,<br>+                                            this mailbox will be used when notifying other modules of MWI status<br>+                                         changes.  If not set, incoming MWI NOTIFYs are ignored.<br>+                                      </para></description><br>+                            </configOption><br>                         </configObject><br>                         <configObject name="auth"><br>                            <synopsis>Authentication type</synopsis><br>diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c<br>index 715ffe8..cba8a7e 100644<br>--- a/res/res_pjsip/pjsip_configuration.c<br>+++ b/res/res_pjsip/pjsip_configuration.c<br>@@ -1995,6 +1995,7 @@<br>      ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_video_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_video_streams));<br>  ast_sorcery_object_field_register(sip_sorcery, "endpoint", "bundle", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.bundle));<br>       ast_sorcery_object_field_register(sip_sorcery, "endpoint", "webrtc", "no", OPT_YESNO_T, 1, FLDSET(struct ast_sip_endpoint, media.webrtc));<br>+     ast_sorcery_object_field_register(sip_sorcery, "endpoint", "incoming_mwi_mailbox", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, incoming_mwi_mailbox));<br> <br>      if (ast_sip_initialize_sorcery_transport()) {<br>                 ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");<br>@@ -2159,6 +2160,9 @@<br>               ao2_cleanup(endpoint);<br>                return NULL;<br>  }<br>+<br>+ ast_string_field_init_extended(endpoint, incoming_mwi_mailbox);<br>+<br>    if (!(endpoint->media.codecs = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {<br>               ao2_cleanup(endpoint);<br>                return NULL;<br>diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c<br>index 9f0eae2..81b25ac 100644<br>--- a/res/res_pjsip_pubsub.c<br>+++ b/res/res_pjsip_pubsub.c<br>@@ -31,6 +31,7 @@<br> #include <pjsip_simple.h><br> #include <pjlib.h><br> <br>+#include "asterisk/app.h"<br> #include "asterisk/res_pjsip_pubsub.h"<br> #include "asterisk/module.h"<br> #include "asterisk/linkedlists.h"<br>@@ -3402,12 +3403,144 @@<br>    return res;<br> }<br> <br>+struct simple_message_summary {<br>+ int messages_waiting;<br>+        int voice_messages_new;<br>+      int voice_messages_old;<br>+      int voice_messages_urgent_new;<br>+       int voice_messages_urgent_old;<br>+       char message_account[PJSIP_MAX_URL_SIZE];<br>+};<br>+<br>+static int parse_simple_message_summary(char *body,<br>+      struct simple_message_summary *summary)<br>+{<br>+  char *line;<br>+  char *buffer;<br>+        int found_counts = 0;<br>+<br>+     if (ast_strlen_zero(body) || !summary) {<br>+             return -1;<br>+   }<br>+<br>+ buffer = ast_strdupa(body);<br>+  memset(summary, 0, sizeof(*summary));<br>+<br>+     while ((line = ast_read_line_from_buffer(&buffer))) {<br>+            line = ast_str_to_lower(line);<br>+<br>+            if (sscanf(line, "voice-message: %d/%d (%d/%d)",<br>+                   &summary->voice_messages_new, &summary->voice_messages_old,<br>+                    &summary->voice_messages_urgent_new, &summary->voice_messages_urgent_old)) {<br>+                   found_counts = 1;<br>+            } else {<br>+                     sscanf(line, "message-account: %s", summary->message_account);<br>+          }<br>+    }<br>+<br>+ return !found_counts;<br>+}<br>+<br>+static pj_bool_t pubsub_on_rx_mwi_notify_request(pjsip_rx_data *rdata)<br>+{<br>+    RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);<br>+    struct simple_message_summary summary;<br>+       const char *endpoint_name;<br>+   char *atsign;<br>+        char *context;<br>+       char *body;<br>+  char *mailbox;<br>+       int rc;<br>+<br>+   endpoint = ast_pjsip_rdata_get_endpoint(rdata);<br>+      if (!endpoint) {<br>+             ast_debug(1, "Incoming MWI: Endpoint not found in rdata (%p)\n", rdata);<br>+           rc = 404;<br>+            goto error;<br>+  }<br>+<br>+ endpoint_name = ast_sorcery_object_get_id(endpoint);<br>+ ast_debug(1, "Incoming MWI: Found endpoint: %s\n", endpoint_name);<br>+ if (ast_strlen_zero(endpoint->incoming_mwi_mailbox)) {<br>+            ast_debug(1, "Incoming MWI: No incoming mailbox specified for endpoint '%s'\n", endpoint_name);<br>+            ast_test_suite_event_notify("PUBSUB_NO_INCOMING_MWI_MAILBOX",<br>+                      "Endpoint: %s", endpoint_name);<br>+            rc = 404;<br>+            goto error;<br>+  }<br>+<br>+ mailbox = ast_strdupa(endpoint->incoming_mwi_mailbox);<br>+    atsign = strchr(mailbox, '@');<br>+       if (!atsign) {<br>+               ast_debug(1, "Incoming MWI: No '@' found in endpoint %s's incoming mailbox '%s'.  Can't parse context\n",<br>+                  endpoint_name, endpoint->incoming_mwi_mailbox);<br>+           rc = 404;<br>+            goto error;<br>+  }<br>+<br>+ *atsign = '\0';<br>+      context = atsign + 1;<br>+<br>+     body = ast_alloca(rdata->msg_info.msg->body->len + 1);<br>+      rdata->msg_info.msg->body->print_body(rdata->msg_info.msg->body, body,<br>+                rdata->msg_info.msg->body->len + 1);<br>+<br>+     if (parse_simple_message_summary(body, &summary) != 0) {<br>+         ast_debug(1, "Incoming MWI: Endpoint: '%s' There was an issue getting message info from body '%s'\n",<br>+                      ast_sorcery_object_get_id(endpoint), body);<br>+          rc = 404;<br>+            goto error;<br>+  }<br>+<br>+ if (ast_publish_mwi_state(mailbox, context,<br>+          summary.voice_messages_new, summary.voice_messages_old)) {<br>+           ast_log(LOG_ERROR, "Incoming MWI: Endpoint: '%s' Could not publish MWI to stasis.  "<br>+                       "Mailbox: %s Message-Account: %s Voice-Messages: %d/%d (%d/%d)\n",<br>+                 endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,<br>+                   summary.voice_messages_new, summary.voice_messages_old,<br>+                      summary.voice_messages_urgent_new, summary.voice_messages_urgent_old);<br>+               rc = 404;<br>+    } else {<br>+             ast_debug(1, "Incoming MWI: Endpoint: '%s' Mailbox: %s Message-Account: %s Voice-Messages: %d/%d (%d/%d)\n",<br>+                       endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,<br>+                   summary.voice_messages_new, summary.voice_messages_old,<br>+                      summary.voice_messages_urgent_new, summary.voice_messages_urgent_old);<br>+               ast_test_suite_event_notify("PUBSUB_INCOMING_MWI_PUBLISH",<br>+                 "Endpoint: %s\r\n"<br>+                 "Mailbox: %s\r\n"<br>+                  "MessageAccount: %s\r\n"<br>+                   "VoiceMessagesNew: %d\r\n"<br>+                 "VoiceMessagesOld: %d\r\n"<br>+                 "VoiceMessagesUrgentNew: %d\r\n"<br>+                   "VoiceMessagesUrgentOld: %d",<br>+                              endpoint_name, endpoint->incoming_mwi_mailbox, summary.message_account,<br>+                           summary.voice_messages_new, summary.voice_messages_old,<br>+                              summary.voice_messages_urgent_new, summary.voice_messages_urgent_old);<br>+               rc = 200;<br>+    }<br>+<br>+error:<br>+        pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, rc, NULL, NULL, NULL);<br>+    return PJ_TRUE;<br>+}<br>+<br>+static pj_bool_t pubsub_on_rx_notify_request(pjsip_rx_data *rdata)<br>+{<br>+      if (pj_stricmp2(&rdata->msg_info.msg->body->content_type.type, "application") == 0 &&<br>+         pj_stricmp2(&rdata->msg_info.msg->body->content_type.subtype, "simple-message-summary") == 0) {<br>+               return pubsub_on_rx_mwi_notify_request(rdata);<br>+       }<br>+    return PJ_FALSE;<br>+}<br>+<br> static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata)<br> {<br>     if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method())) {<br>               return pubsub_on_rx_subscribe_request(rdata);<br>         } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_publish_method)) {<br>           return pubsub_on_rx_publish_request(rdata);<br>+  } else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_notify_method)) {<br>+           return pubsub_on_rx_notify_request(rdata);<br>    }<br> <br>  return PJ_FALSE;<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/6483">change 6483</a>. To unsubscribe, 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/6483"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I08bae3d16e77af48fcccc2c936acce8fc0ef0f3c </div>
<div style="display:none"> Gerrit-Change-Number: 6483 </div>
<div style="display:none"> Gerrit-PatchSet: 3 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>