<p>George Joseph <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/9022">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins2: Verified
  George Joseph: Looks good to me, approved; Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">bridge_softmix:  Forward TEXT frames<br><br>Core bridging and, more specifically, bridge_softmix have been<br>enhanced to relay received frames of type TEXT or TEXT_DATA to all<br>participants in a softmix bridge.  res_pjsip_messaging and<br>chan_pjsip have been enhanced to take advantage of this so when<br>res_pjsip_messaging receives an in-dialog MESSAGE message from a<br>user in a conference call, it's relayed to all other participants<br>in the call.<br><br>res_pjsip_messaging already queues TEXT frames to the channel when<br>it receives an in-dialog MESSAGE from an endpoint and chan_pjsip<br>will send an MESSAGE when it gets a TEXT frame.  On a normal<br>point-to-point call, the frames are forwarded between the two<br>correctly.  bridge_softmix was not though so messages weren't<br>getting forwarded to conference bridge participants.  Even if they<br>were, the bridging code had no way to tell the participants who<br>sent the message so it would look like it came from the bridge<br>itself.<br><br>* The TEXT frame type doesn't allow storage of any meta data, such<br>as sender, on the frame so a new TEXT_DATA frame type was added that<br>uses the new ast_msg_data structure as its payload.  A channel<br>driver can queue a frame of that type when it receives a message<br>from outside.  A channel driver can use it for sending messages<br>by implementing the new send_text_data channel tech callback and<br>setting the new AST_CHAN_TP_SEND_TEXT_DATA flag in its tech<br>properties.  If set, the bridging/channel core will use it instead<br>of the original send_text callback and it will get the ast_msg_data<br>structure. Channel drivers aren't required to implement this.  Even<br>if a TEXT_DATA enabled driver uses it for incoming messages, an<br>outgoing channel driver that doesn't will still have it's send_text<br>callback called with only the message text just as before.<br><br>* res_pjsip_messaging now creates a TEXT_DATA frame for incoming<br>in-dialog messages and sets the "from" to the display name in the<br>"From" header, or if that's empty, the caller id name from the<br>channel.  This allows the chat client user to set a friendly name<br>for the chat.<br><br>* bridge_softmix now forwards TEXT and TEXT_DATA frames to all<br>participants (except the sender).<br><br>* A new function "ast_sendtext_data" was added to channel which<br>takes an ast_msg_data structure and calls a channel's<br>send_text_data callback, or if that's not defined, the original<br>send_text callback.<br><br>* bridge_channel now calls ast_sendtext_data for TEXT_DATA frame<br>types and ast_sendtext for TEXT frame types.<br><br>* chan_pjsip now uses the "from" name in the ast_msg_data structure<br>(if it exists) to set the "From" header display name on outgoing text<br>messages.<br><br>Change-Id: Idacf5900bfd5f22ab8cd235aa56dfad090d18489<br>(cherry picked from commit be7d4faed5fb3684e9d68454ae2a97167e1ebb51)<br>---<br>M CHANGES<br>M bridges/bridge_softmix.c<br>M channels/chan_pjsip.c<br>M funcs/func_frame_trace.c<br>M include/asterisk/channel.h<br>M include/asterisk/frame.h<br>M include/asterisk/message.h<br>M main/bridge_channel.c<br>M main/channel.c<br>M main/frame.c<br>M main/message.c<br>M res/res_pjsip_messaging.c<br>12 files changed, 658 insertions(+), 45 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/CHANGES b/CHANGES<br>index 6c25cb2..040224b 100644<br>--- a/CHANGES<br>+++ b/CHANGES<br>@@ -9,6 +9,19 @@<br> ==============================================================================<br> <br> ------------------------------------------------------------------------------<br>+--- Functionality changes from Asterisk 13.21.0 to Asterisk 13.22.0 ----------<br>+------------------------------------------------------------------------------<br>+<br>+Core<br>+------------------<br>+ * Core bridging and, more specifically, bridge_softmix have been enhanced to<br>+   relay received frames of type TEXT or TEXT_DATA to all participants in a<br>+   softmix bridge.  res_pjsip_messaging and chan_pjsip have been enhanced to<br>+   take advantage of this so when res_pjsip_messaging receives an in-dialog<br>+   MESSAGE message from a user in a conference call, it's relayed to all<br>+   other participants in the call.<br>+<br>+------------------------------------------------------------------------------<br> --- Functionality changes from Asterisk 13.20.0 to Asterisk 13.21.0 ----------<br> ------------------------------------------------------------------------------<br> <br>diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c<br>index 22c1fce..fbe1587 100644<br>--- a/bridges/bridge_softmix.c<br>+++ b/bridges/bridge_softmix.c<br>@@ -54,6 +54,7 @@<br> #include "asterisk/astobj2.h"<br> #include "asterisk/timing.h"<br> #include "asterisk/translate.h"<br>+#include "asterisk/message.h"<br> <br> #define MAX_DATALEN 8096<br> <br>@@ -721,6 +722,42 @@<br> <br> /*!<br>  * \internal<br>+ * \brief Determine what to do with a text frame.<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ *<br>+ * \param bridge Which bridge is getting the frame<br>+ * \param bridge_channel Which channel is writing the frame.<br>+ * \param frame What is being written.<br>+ *<br>+ * \return Nothing<br>+ */<br>+static void softmix_bridge_write_text(struct ast_bridge *bridge,<br>+      struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)<br>+{<br>+      if (DEBUG_ATLEAST(1)) {<br>+              struct ast_msg_data *msg = frame->data.ptr;<br>+               char frame_type[64];<br>+<br>+              ast_frame_type2str(frame->frametype, frame_type, sizeof(frame_type));<br>+<br>+          if (frame->frametype == AST_FRAME_TEXT_DATA) {<br>+                    ast_log(LOG_DEBUG, "Received %s frame from '%s:%s': %s\n", frame_type,<br>+                             ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),<br>+                             ast_channel_name(bridge_channel->chan),<br>+                           ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));<br>+            } else {<br>+                     ast_log(LOG_DEBUG, "Received %s frame from '%s': %.*s\n", frame_type,<br>+                              ast_channel_name(bridge_channel->chan), frame->datalen,<br>+                                (char *)frame->data.ptr);<br>+         }<br>+    }<br>+<br>+ ast_bridge_queue_everyone_else(bridge, bridge_channel, frame);<br>+}<br>+<br>+/*!<br>+ * \internal<br>  * \brief Determine what to do with a control frame.<br>  * \since 12.0.0<br>  *<br>@@ -803,6 +840,10 @@<br>       case AST_FRAME_VIDEO:<br>                 softmix_bridge_write_video(bridge, bridge_channel, frame);<br>            break;<br>+       case AST_FRAME_TEXT:<br>+ case AST_FRAME_TEXT_DATA:<br>+            softmix_bridge_write_text(bridge, bridge_channel, frame);<br>+            break;<br>        case AST_FRAME_CONTROL:<br>               res = softmix_bridge_write_control(bridge, bridge_channel, frame);<br>            break;<br>diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c<br>index 184ea80..c7344d2 100644<br>--- a/channels/chan_pjsip.c<br>+++ b/channels/chan_pjsip.c<br>@@ -63,6 +63,7 @@<br> #include "asterisk/features_config.h"<br> #include "asterisk/pickup.h"<br> #include "asterisk/test.h"<br>+#include "asterisk/message.h"<br> <br> #include "asterisk/res_pjsip.h"<br> #include "asterisk/res_pjsip_session.h"<br>@@ -91,6 +92,7 @@<br> <br> /* \brief Asterisk core interaction functions */<br> static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);<br>+static int chan_pjsip_sendtext_data(struct ast_channel *ast, struct ast_msg_data *msg);<br> static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text);<br> static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit);<br> static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration);<br>@@ -112,6 +114,7 @@<br>     .description = "PJSIP Channel Driver",<br>      .requester = chan_pjsip_request,<br>      .send_text = chan_pjsip_sendtext,<br>+    .send_text_data = chan_pjsip_sendtext_data,<br>   .send_digit_begin = chan_pjsip_digit_begin,<br>   .send_digit_end = chan_pjsip_digit_end,<br>       .call = chan_pjsip_call,<br>@@ -128,7 +131,7 @@<br>         .queryoption = chan_pjsip_queryoption,<br>        .func_channel_read = pjsip_acf_channel_read,<br>  .get_pvt_uniqueid = chan_pjsip_get_uniqueid,<br>- .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER<br>+    .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER | AST_CHAN_TP_SEND_TEXT_DATA<br> };<br> <br> /*! \brief SIP session interaction functions */<br>@@ -2261,50 +2264,99 @@<br> <br> struct sendtext_data {<br>         struct ast_sip_session *session;<br>-     char text[0];<br>+        struct ast_msg_data *msg;<br> };<br> <br> static void sendtext_data_destroy(void *obj)<br> {<br>  struct sendtext_data *data = obj;<br>-    ao2_ref(data->session, -1);<br>+       ao2_cleanup(data->session);<br>+       ast_free(data->msg);<br> }<br> <br>-static struct sendtext_data* sendtext_data_create(struct ast_sip_session *session, const char *text)<br>+static struct sendtext_data* sendtext_data_create(struct ast_channel *chan,<br>+  struct ast_msg_data *msg)<br> {<br>-        int size = strlen(text) + 1;<br>- struct sendtext_data *data = ao2_alloc(sizeof(*data)+size, sendtext_data_destroy);<br>+   struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);<br>+    struct sendtext_data *data = ao2_alloc(sizeof(*data), sendtext_data_destroy);<br> <br>      if (!data) {<br>          return NULL;<br>  }<br> <br>- data->session = session;<br>+  data->msg = ast_msg_data_dup(msg);<br>+        if (!data->msg) {<br>+         ao2_cleanup(data);<br>+           return NULL;<br>+ }<br>+    data->session = channel->session;<br>       ao2_ref(data->session, +1);<br>-       ast_copy_string(data->text, text, size);<br>+<br>        return data;<br> }<br> <br> static int sendtext(void *obj)<br> {<br>-     RAII_VAR(struct sendtext_data *, data, obj, ao2_cleanup);<br>+    struct sendtext_data *data = obj;<br>     pjsip_tx_data *tdata;<br>-<br>-     const struct ast_sip_body body = {<br>+   const char *body_text = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_BODY);<br>+    const char *content_type = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_CONTENT_TYPE);<br>+ char *sep;<br>+   struct ast_sip_body body = {<br>          .type = "text",<br>             .subtype = "plain",<br>-                .body_text = data->text<br>+           .body_text = body_text,<br>       };<br>+<br>+        if (!ast_strlen_zero(content_type)) {<br>+                sep = strchr(content_type, '/');<br>+             if (sep) {<br>+                   *sep = '\0';<br>+                 body.type = content_type;<br>+                    body.subtype = ++sep;<br>+                }<br>+    }<br> <br>  if (data->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {<br>             ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n",<br>                       data->session->inv_session->cause,<br>                   pjsip_get_status_text(data->session->inv_session->cause)->ptr);<br>   } else {<br>-             ast_debug(3, "Sending in dialog SIP message\n");<br>+           pjsip_from_hdr *hdr;<br>+         pjsip_name_addr *name_addr;<br>+          const char *from = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_FROM);<br>+         const char *to = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_TO);<br>+             int invalidate_tdata = 0;<br> <br>          ast_sip_create_request("MESSAGE", data->session->inv_session->dlg, data->session->endpoint, NULL, NULL, &tdata);<br>                 ast_sip_add_body(tdata, &body);<br>+<br>+               /*<br>+            * If we have a 'from' in the msg, set the display name in the From<br>+           * header to it.<br>+              */<br>+          if (!ast_strlen_zero(from)) {<br>+                        hdr = PJSIP_MSG_FROM_HDR(tdata->msg);<br>+                     name_addr = (pjsip_name_addr *) hdr->uri;<br>+                 pj_strdup2(tdata->pool, &name_addr->display, from);<br>+                        invalidate_tdata = 1;<br>+                }<br>+<br>+         /*<br>+            * If we have a 'to' in the msg, set the display name in the To<br>+               * header to it.<br>+              */<br>+          if (!ast_strlen_zero(to)) {<br>+                  hdr = PJSIP_MSG_TO_HDR(tdata->msg);<br>+                       name_addr = (pjsip_name_addr *) hdr->uri;<br>+                 pj_strdup2(tdata->pool, &name_addr->display, to);<br>+                  invalidate_tdata = 1;<br>+                }<br>+<br>+         if (invalidate_tdata) {<br>+                      pjsip_tx_data_invalidate_msg(tdata);<br>+         }<br>+<br>          ast_sip_send_request(tdata, data->session->inv_session->dlg, data->session->endpoint, NULL, NULL);<br>     }<br> <br>@@ -2312,14 +2364,22 @@<br>         pjsip_inv_dec_ref(data->session->inv_session);<br> #endif<br> <br>+     ao2_cleanup(data);<br>+<br>         return 0;<br> }<br> <br> /*! \brief Function called by core to send text on PJSIP session */<br>-static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text)<br>+static int chan_pjsip_sendtext_data(struct ast_channel *ast, struct ast_msg_data *msg)<br> {<br>       struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);<br>-     struct sendtext_data *data = sendtext_data_create(channel->session, text);<br>+        struct sendtext_data *data = sendtext_data_create(ast, msg);<br>+<br>+      ast_debug(1, "Sending MESSAGE from '%s' to '%s:%s': %s\n",<br>+         ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),<br>+             ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),<br>+               ast_channel_name(ast),<br>+               ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));<br> <br>  if (!data) {<br>          return -1;<br>@@ -2343,6 +2403,28 @@<br>    return 0;<br> }<br> <br>+static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text)<br>+{<br>+     struct ast_msg_data *msg;<br>+    int rc;<br>+      struct ast_msg_data_attribute attrs[] =<br>+      {<br>+            {<br>+                    .type = AST_MSG_DATA_ATTR_BODY,<br>+                      .value = (char *)text,<br>+               }<br>+    };<br>+<br>+        msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_UNKNOWN, attrs, ARRAY_LEN(attrs));<br>+ if (!msg) {<br>+          return -1;<br>+   }<br>+    rc = chan_pjsip_sendtext_data(ast, msg);<br>+     ast_free(msg);<br>+<br>+    return rc;<br>+}<br>+<br> /*! \brief Convert SIP hangup causes to Asterisk hangup causes */<br> static int hangup_sip2cause(int cause)<br> {<br>diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c<br>index efb55e5..f76ba3e 100644<br>--- a/funcs/func_frame_trace.c<br>+++ b/funcs/func_frame_trace.c<br>@@ -62,6 +62,7 @@<br>                                        <enum name = "NULL" /><br>                                        <enum name = "IAX" /><br>                                         <enum name = "TEXT" /><br>+                                       <enum name = "TEXT_DATA" /><br>                                   <enum name = "IMAGE" /><br>                                       <enum name = "HTML" /><br>                                        <enum name = "CNG" /><br>@@ -91,6 +92,7 @@<br>      { AST_FRAME_NULL,   "NULL" },<br>       { AST_FRAME_IAX,   "IAX" },<br>         { AST_FRAME_TEXT,   "TEXT" },<br>+      { AST_FRAME_TEXT_DATA,   "TEXT_DATA" },<br>     { AST_FRAME_IMAGE,   "IMAGE" },<br>     { AST_FRAME_HTML,   "HTML" },<br>       { AST_FRAME_CNG,   "CNG" },<br>@@ -373,6 +375,9 @@<br>            }<br>             ast_verbose("Bytes: %d\n", frame->datalen);<br>              break;<br>+       case AST_FRAME_RTCP:<br>+         ast_verbose("FrameType: RTCP\n");<br>+          break;<br>        case AST_FRAME_NULL:<br>          ast_verbose("FrameType: NULL\n");<br>           break;<br>@@ -382,6 +387,9 @@<br>   case AST_FRAME_TEXT:<br>          ast_verbose("FrameType: TXT\n");<br>            break;<br>+       case AST_FRAME_TEXT_DATA:<br>+            ast_verbose("FrameType: TXT_DATA\n");<br>+              break;<br>        case AST_FRAME_IMAGE:<br>                 ast_verbose("FrameType: IMAGE\n");<br>          break;<br>diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h<br>index 7b406e5..8ec74e6 100644<br>--- a/include/asterisk/channel.h<br>+++ b/include/asterisk/channel.h<br>@@ -590,6 +590,11 @@<br> };<br> <br> /*!<br>+ * \brief Forward declaration<br>+ */<br>+struct ast_msg_data;<br>+<br>+/*!<br>  * \brief<br>  * Structure to describe a channel "technology", ie a channel driver<br>  * See for examples:<br>@@ -756,6 +761,9 @@<br>     * \retval -1 on error.<br>        */<br>   int (*pre_call)(struct ast_channel *chan, const char *sub_args);<br>+<br>+  /*! \brief Display or transmit text with data*/<br>+      int (* const send_text_data)(struct ast_channel *chan, struct ast_msg_data *data);<br> };<br> <br> /*! Kill the channel channel driver technology descriptor. */<br>@@ -883,6 +891,10 @@<br>       * world<br>       */<br>   AST_CHAN_TP_INTERNAL = (1 << 2),<br>+       /*!<br>+   * \brief Channels have this property if they implement send_text_data<br>+        */<br>+  AST_CHAN_TP_SEND_TEXT_DATA = (1 << 3),<br> };<br> <br> /*! \brief ast_channel flags */<br>@@ -2068,6 +2080,26 @@<br> int ast_sendtext(struct ast_channel *chan, const char *text);<br> <br> /*!<br>+ * \brief Sends text to a channel in an ast_msg_data structure wrapper with ast_sendtext as fallback<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ *<br>+ * \param chan channel to act upon<br>+ * \param msg ast_msg_data structure<br>+ *<br>+ * \details<br>+ * Write text to a display on a channel.  If the channel driver doesn't support the<br>+ * send_text_data callback. then the original send_text callback will be used if<br>+ * available.<br>+ *<br>+ * \note The channel does not need to be locked before calling this function.<br>+ *<br>+ * \retval 0 on success<br>+ * \retval -1 on failure<br>+ */<br>+int ast_sendtext_data(struct ast_channel *chan, struct ast_msg_data *msg);<br>+<br>+/*!<br>  * \brief Receives a text character from a channel<br>  * \param chan channel to act upon<br>  * \param timeout timeout in milliseconds (0 for infinite wait)<br>diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h<br>index 108dcaf..7193086 100644<br>--- a/include/asterisk/frame.h<br>+++ b/include/asterisk/frame.h<br>@@ -48,6 +48,7 @@<br>  * \arg \b DTMF:   A DTMF digit, subclass is the digit<br>  * \arg \b IMAGE:  Image transport, mostly used in IAX<br>  * \arg \b TEXT:   Text messages and character by character (real time text)<br>+ * \arg \b TEXT_DATA:   Text messages in an ast_msg_data structure<br>  * \arg \b HTML:   URL's and web pages<br>  * \arg \b MODEM:  Modulated data encodings, such as T.38 and V.150<br>  * \arg \b IAX:    Private frame type for the IAX protocol<br>@@ -127,6 +128,10 @@<br>          * directly into bridges.<br>      */<br>   AST_FRAME_BRIDGE_ACTION_SYNC,<br>+        /*! RTCP feedback (the subclass will contain the payload type) */<br>+    AST_FRAME_RTCP,<br>+      /*! Text message in an ast_msg_data structure */<br>+     AST_FRAME_TEXT_DATA,<br> };<br> #define AST_FRAME_DTMF AST_FRAME_DTMF_END<br> <br>diff --git a/include/asterisk/message.h b/include/asterisk/message.h<br>index ae6533c..826fa0a 100644<br>--- a/include/asterisk/message.h<br>+++ b/include/asterisk/message.h<br>@@ -407,6 +407,129 @@<br>  */<br> void ast_msg_var_unref_current(struct ast_msg_var_iterator *iter);<br> <br>+<br>+/*! \defgroup ast_msg_data Enhanced Messaging<br>+ * @{<br>+ * \page Messaging Enhanced Messaging<br>+ *<br>+ * The basic messaging framework has a basic drawback... It can only pass<br>+ * a text string through the core.  This causes several issues:<br>+ * \li Only a content type of text/plain can be passed.<br>+ * \li If a softmix bridge is used, the original sender identity is lost.<br>+ *<br>+ * The Enhanced Messaging framework allows attributes, such as "From", "To"<br>+ * and "Content-Type" to be attached to the message by the incoming channel<br>+ * tech which can then be used by the outgoing channel tech to construct<br>+ * the appropriate technology-specific outgoing message.<br>+ */<br>+<br>+/*!<br>+ * \brief Structure used to transport an enhanced message through the frame core<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ */<br>+struct ast_msg_data;<br>+<br>+enum ast_msg_data_source_type {<br>+   AST_MSG_DATA_SOURCE_TYPE_UNKNOWN = 0,<br>+        AST_MSG_DATA_SOURCE_TYPE_T140,<br>+       AST_MSG_DATA_SOURCE_TYPE_IN_DIALOG,<br>+  AST_MSG_DATA_SOURCE_TYPE_OUT_OF_DIALOG,<br>+      __AST_MSG_DATA_SOURCE_TYPE_LAST,<br>+};<br>+<br>+enum ast_msg_data_attribute_type {<br>+        AST_MSG_DATA_ATTR_TO = 0,<br>+    AST_MSG_DATA_ATTR_FROM,<br>+      AST_MSG_DATA_ATTR_CONTENT_TYPE,<br>+      AST_MSG_DATA_ATTR_BODY,<br>+      __AST_MSG_DATA_ATTR_LAST,<br>+};<br>+<br>+struct ast_msg_data_attribute {<br>+  enum ast_msg_data_attribute_type type;<br>+       char *value;<br>+};<br>+<br>+/*!<br>+ * \brief Allocates an ast_msg_data structure.<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ *<br>+ * \param source The source type of the message<br>+ * \param attributes A pointer to an array of ast_msg_data_attribute structures<br>+ * \param count The number of elements in the array<br>+ *<br>+ * \return Pointer to msg structure or NULL on allocation failure.<br>+ *         Caller must call ast_free when done.<br>+ */<br>+struct ast_msg_data *ast_msg_data_alloc(enum ast_msg_data_source_type source,<br>+  struct ast_msg_data_attribute attributes[], size_t count);<br>+<br>+/*!<br>+ * \brief Clone an ast_msg_data structure<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ *<br>+ * \param msg The message to clone<br>+ *<br>+ * \return New message structure or NULL if there was an allocation failure.<br>+ *         Caller must call ast_free when done.<br>+ */<br>+struct ast_msg_data *ast_msg_data_dup(struct ast_msg_data *msg);<br>+<br>+/*!<br>+ * \brief Get length of the structure<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ *<br>+ * \param msg Pointer to ast_msg_data structure<br>+ *<br>+ * \return The length of the structure itself plus the dynamically allocated attribute buffer.<br>+ */<br>+size_t ast_msg_data_get_length(struct ast_msg_data *msg);<br>+<br>+/*!<br>+ * \brief Get "source type" from ast_msg_data<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ *<br>+ * \param msg Pointer to ast_msg_data structure<br>+ *<br>+ * \return The source type field.<br>+ */<br>+enum ast_msg_data_source_type ast_msg_data_get_source_type(struct ast_msg_data *msg);<br>+<br>+/*!<br>+ * \brief Get attribute from ast_msg_data<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ *<br>+ * \param msg Pointer to ast_msg_data structure<br>+ * \param attribute_type One of ast_msg_data_attribute_type<br>+ *<br>+ * \return The attribute or an empty string ("") if the attribute wasn't set.<br>+ */<br>+const char *ast_msg_data_get_attribute(struct ast_msg_data *msg,<br>+  enum ast_msg_data_attribute_type attribute_type);<br>+<br>+/*!<br>+ * \brief Queue an AST_FRAME_TEXT_DATA frame containing an ast_msg_data structure<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ *<br>+ * \param channel  The channel on which to queue the frame<br>+ * \param msg Pointer to ast_msg_data structure<br>+ *<br>+ * \retval -1 Error<br>+ * \retval  0 Success<br>+ */<br>+int ast_msg_data_queue_frame(struct ast_channel *channel, struct ast_msg_data *msg);<br>+<br>+/*!<br>+ *  @}<br>+ */<br>+<br> #if defined(__cplusplus) || defined(c_plusplus)<br> }<br> #endif<br>diff --git a/main/bridge_channel.c b/main/bridge_channel.c<br>index 08aea5b..3eefd44 100644<br>--- a/main/bridge_channel.c<br>+++ b/main/bridge_channel.c<br>@@ -57,6 +57,7 @@<br> #include "asterisk/causes.h"<br> #include "asterisk/test.h"<br> #include "asterisk/sem.h"<br>+#include "asterisk/message.h"<br> <br> /*!<br>  * \brief Used to queue an action frame onto a bridge channel and write an action frame into a bridge.<br>@@ -997,6 +998,20 @@<br>           ast_bridge_channel_unlock(bridge_channel);<br>            bridge_frame_free(dup);<br>               return 0;<br>+    }<br>+<br>+ if (DEBUG_ATLEAST(1)) {<br>+              if (fr->frametype == AST_FRAME_TEXT) {<br>+                    ast_log(LOG_DEBUG, "Queuing TEXT frame to '%s': %*.s\n", ast_channel_name(bridge_channel->chan),<br>+                                fr->datalen, (char *)fr->data.ptr);<br>+            } else if (fr->frametype == AST_FRAME_TEXT_DATA) {<br>+                        struct ast_msg_data *msg = fr->data.ptr;<br>+                  ast_log(LOG_DEBUG, "Queueing TEXT_DATA frame from '%s' to '%s:%s': %s\n",<br>+                          ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),<br>+                             ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),<br>+                               ast_channel_name(bridge_channel->chan),<br>+                           ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));<br>+            }<br>     }<br> <br>  AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list);<br>@@ -2289,6 +2304,7 @@<br> {<br>   struct ast_frame *fr;<br>         struct sync_payload *sync_payload;<br>+   struct ast_msg_data *msg;<br> <br>  ast_bridge_channel_lock(bridge_channel);<br> <br>@@ -2321,6 +2337,7 @@<br>    AST_LIST_TRAVERSE_SAFE_END;<br> <br>        ast_bridge_channel_unlock(bridge_channel);<br>+<br>         if (!fr) {<br>            /*<br>             * Wait some to reduce CPU usage from a tight loop<br>@@ -2344,6 +2361,20 @@<br>            break;<br>        case AST_FRAME_NULL:<br>          break;<br>+       case AST_FRAME_TEXT:<br>+         ast_debug(1, "Sending TEXT frame to '%s': %*.s\n",<br>+                 ast_channel_name(bridge_channel->chan), fr->datalen, (char *)fr->data.ptr);<br>+         ast_sendtext(bridge_channel->chan, fr->data.ptr);<br>+              break;<br>+       case AST_FRAME_TEXT_DATA:<br>+            msg = (struct ast_msg_data *)fr->data.ptr;<br>+                ast_debug(1, "Sending TEXT_DATA frame from '%s' to '%s:%s': %s\n",<br>+                 ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),<br>+                     ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),<br>+                       ast_channel_name(bridge_channel->chan),<br>+                   ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));<br>+            ast_sendtext_data(bridge_channel->chan, msg);<br>+             break;<br>        default:<br>              /* Write the frame to the channel. */<br>                 bridge_channel->activity = BRIDGE_CHANNEL_THREAD_SIMPLE;<br>diff --git a/main/channel.c b/main/channel.c<br>index dc9c72d..cbcf134 100644<br>--- a/main/channel.c<br>+++ b/main/channel.c<br>@@ -75,6 +75,7 @@<br> #include "asterisk/test.h"<br> #include "asterisk/stasis_channels.h"<br> #include "asterisk/max_forwards.h"<br>+#include "asterisk/message.h"<br> <br> /*** DOCUMENTATION<br>  ***/<br>@@ -1513,6 +1514,7 @@<br>      case AST_FRAME_BRIDGE_ACTION_SYNC:<br>    case AST_FRAME_CONTROL:<br>       case AST_FRAME_TEXT:<br>+ case AST_FRAME_TEXT_DATA:<br>     case AST_FRAME_IMAGE:<br>         case AST_FRAME_HTML:<br>          return 1;<br>@@ -1525,6 +1527,7 @@<br>      case AST_FRAME_IAX:<br>   case AST_FRAME_CNG:<br>   case AST_FRAME_MODEM:<br>+        case AST_FRAME_RTCP:<br>          return 0;<br>     }<br>     return 0;<br>@@ -2883,11 +2886,13 @@<br>                            case AST_FRAME_VOICE:<br>                                 case AST_FRAME_VIDEO:<br>                                 case AST_FRAME_TEXT:<br>+                         case AST_FRAME_TEXT_DATA:<br>                             case AST_FRAME_DTMF_BEGIN:<br>                            case AST_FRAME_DTMF_END:<br>                              case AST_FRAME_IMAGE:<br>                                 case AST_FRAME_HTML:<br>                          case AST_FRAME_MODEM:<br>+                                case AST_FRAME_RTCP:<br>                                  done = 1;<br>                                     break;<br>                                case AST_FRAME_CONTROL:<br>@@ -4869,9 +4874,11 @@<br>       return buf;<br> }<br> <br>-int ast_sendtext(struct ast_channel *chan, const char *text)<br>+int ast_sendtext_data(struct ast_channel *chan, struct ast_msg_data *msg)<br> {<br>     int res = 0;<br>+ const char *body = ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY);<br>+  const char *content_type = ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_CONTENT_TYPE);<br> <br>        ast_channel_lock(chan);<br>       /* Stop if we're a zombie or need a soft hangup */<br>@@ -4880,35 +4887,76 @@<br>               return -1;<br>    }<br> <br>- if (ast_strlen_zero(text)) {<br>-         ast_channel_unlock(chan);<br>-            return 0;<br>-    }<br>-<br>  CHECK_BLOCKING(chan);<br>-        if (ast_channel_tech(chan)->write_text && (ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_TEXT))) {<br>+      if (ast_channel_tech(chan)->write_text<br>+            && (ast_strlen_zero(content_type) || strcasecmp(content_type, "text/plain") == 0)<br>+          && (ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_TEXT))) {<br>                 struct ast_frame f;<br>+          size_t body_len = strlen(body) + 1;<br> <br>+               /* Process as T.140 text (moved here from ast_sendtext() */<br>           memset(&f, 0, sizeof(f));<br>-                f.frametype = AST_FRAME_TEXT;<br>                 f.src = "DIALPLAN";<br>-                f.mallocd = AST_MALLOCD_DATA;<br>-                f.datalen = strlen(text);<br>-            f.data.ptr = ast_strdup(text);<br>                f.subclass.format = ast_format_t140;<br>-<br>+              f.frametype = AST_FRAME_TEXT;<br>+                f.datalen = body_len;<br>+                f.mallocd = AST_MALLOCD_DATA;<br>+                f.data.ptr = ast_strdup(body);<br>                if (f.data.ptr) {<br>                     res = ast_channel_tech(chan)->write_text(chan, &f);<br>-                   ast_frfree(&f);<br>+          } else {<br>+                     res = -1;<br>             }<br>-    } else if (ast_channel_tech(chan)->send_text) {<br>-           res = ast_channel_tech(chan)->send_text(chan, text);<br>+              ast_frfree(&f);<br>+  } else if ((ast_channel_tech(chan)->properties & AST_CHAN_TP_SEND_TEXT_DATA)<br>+          && ast_channel_tech(chan)->send_text_data) {<br>+              /* Send enhanced message to a channel driver that supports it */<br>+             ast_debug(1, "Sending TEXT_DATA from '%s' to %s:%s %s\n",<br>+                  ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),<br>+                     ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),<br>+                       ast_channel_name(chan), body);<br>+               res = ast_channel_tech(chan)->send_text_data(chan, msg);<br>+  } else if (ast_channel_tech(chan)->send_text<br>+              && (ast_strlen_zero(content_type) || strcasecmp(content_type, "text/plain") == 0)) {<br>+               /* Send the body of an enhanced message to a channel driver that supports only a char str */<br>+         ast_debug(1, "Sending TEXT to %s: %s\n", ast_channel_name(chan), body);<br>+            res = ast_channel_tech(chan)->send_text(chan, body);<br>+      } else {<br>+             ast_debug(1, "Channel technology does not support sending text on channel '%s'\n",<br>+                 ast_channel_name(chan));<br>+             res = -1;<br>     }<br>     ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BLOCKING);<br>   ast_channel_unlock(chan);<br>     return res;<br> }<br> <br>+int ast_sendtext(struct ast_channel *chan, const char *text)<br>+{<br>+        struct ast_msg_data *msg;<br>+    int rc;<br>+      struct ast_msg_data_attribute attrs[] =<br>+      {<br>+            {<br>+                    .type = AST_MSG_DATA_ATTR_BODY,<br>+                      .value = (char *)text,<br>+               }<br>+    };<br>+<br>+        if (ast_strlen_zero(text)) {<br>+         return 0;<br>+    }<br>+<br>+ msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_UNKNOWN, attrs, ARRAY_LEN(attrs));<br>+ if (!msg) {<br>+          return -1;<br>+   }<br>+    rc = ast_sendtext_data(chan, msg);<br>+   ast_free(msg);<br>+<br>+    return rc;<br>+}<br>+<br> int ast_senddigit_begin(struct ast_channel *chan, char digit)<br> {<br>         /* Device does not support DTMF tones, lets fake<br>diff --git a/main/frame.c b/main/frame.c<br>index 2140480..19f3654 100644<br>--- a/main/frame.c<br>+++ b/main/frame.c<br>@@ -591,6 +591,9 @@<br>        case AST_FRAME_TEXT:<br>          ast_copy_string(ftype, "Text", len);<br>                break;<br>+       case AST_FRAME_TEXT_DATA:<br>+            ast_copy_string(ftype, "Text Data", len);<br>+          break;<br>        case AST_FRAME_IMAGE:<br>                 ast_copy_string(ftype, "Image", len);<br>               break;<br>diff --git a/main/message.c b/main/message.c<br>index caee37c..7ba2e26 100644<br>--- a/main/message.c<br>+++ b/main/message.c<br>@@ -1350,6 +1350,148 @@<br>      return res;<br> }<br> <br>+/*!<br>+ * \brief Structure used to transport a message through the frame core<br>+ * \since 13.22.0<br>+ * \since 15.5.0<br>+ */<br>+struct ast_msg_data {<br>+       /*! The length of this structure plus the actual length of the allocated buffer */<br>+   size_t length;<br>+       enum ast_msg_data_source_type source;<br>+        /*! These are indices into the buffer where teh attribute starts */<br>+  int attribute_value_offsets[__AST_MSG_DATA_ATTR_LAST];<br>+       /*! The buffer containing the NULL separated attributes */<br>+   char buf[0];<br>+};<br>+<br>+#define ATTRIBUTE_UNSET -1<br>+<br>+struct ast_msg_data *ast_msg_data_alloc(enum ast_msg_data_source_type source,<br>+ struct ast_msg_data_attribute attributes[], size_t count)<br>+{<br>+        struct ast_msg_data *msg;<br>+    size_t len = sizeof(*msg);<br>+   size_t i;<br>+    size_t current_offset = 0;<br>+   enum ast_msg_data_attribute_type attr_type;<br>+<br>+       if (!attributes) {<br>+           ast_assert(attributes != NULL);<br>+              return NULL;<br>+ }<br>+<br>+ if (!count) {<br>+                ast_assert(count > 0);<br>+            return NULL;<br>+ }<br>+<br>+ /* Calculate the length required for the buffer */<br>+   for (i=0; i < count; i++) {<br>+               if (!attributes[i].value) {<br>+                  ast_assert(attributes[i].value != NULL);<br>+                     return NULL;<br>+         }<br>+            len += (strlen(attributes[i].value) + 1);<br>+    }<br>+<br>+ msg = ast_calloc(1, len);<br>+    if (!msg) {<br>+          return NULL;<br>+ }<br>+    msg->source = source;<br>+     msg->length = len;<br>+<br>+     /* Mark all of the attributes as unset */<br>+    for (attr_type = 0; attr_type < __AST_MSG_DATA_ATTR_LAST; attr_type++) {<br>+          msg->attribute_value_offsets[attr_type] = ATTRIBUTE_UNSET;<br>+        }<br>+<br>+ /* Set the ones we have and increment the offset */<br>+  for (i=0; i < count; i++) {<br>+               len = (strlen(attributes[i].value) + 1);<br>+             strcpy(msg->buf + current_offset, attributes[i].value); /* Safe */<br>+                msg->attribute_value_offsets[attributes[i].type] = current_offset;<br>+                current_offset += len;<br>+       }<br>+<br>+ return msg;<br>+}<br>+<br>+struct ast_msg_data *ast_msg_data_dup(struct ast_msg_data *msg)<br>+{<br>+     struct ast_msg_data *dest;<br>+<br>+        if (!msg) {<br>+          ast_assert(msg != NULL);<br>+             return NULL;<br>+ }<br>+<br>+ dest = ast_malloc(msg->length);<br>+   if (!dest) {<br>+         return NULL;<br>+ }<br>+    memcpy(dest, msg, msg->length);<br>+<br>+        return dest;<br>+}<br>+<br>+size_t ast_msg_data_get_length(struct ast_msg_data *msg)<br>+{<br>+   if (!msg) {<br>+          ast_assert(msg != NULL);<br>+             return 0;<br>+    }<br>+<br>+ return msg->length;<br>+}<br>+<br>+enum ast_msg_data_source_type ast_msg_data_get_source_type(struct ast_msg_data *msg)<br>+{<br>+     if (!msg) {<br>+          ast_assert(msg != NULL);<br>+             return AST_MSG_DATA_SOURCE_TYPE_UNKNOWN;<br>+     }<br>+<br>+ return msg->source;<br>+}<br>+<br>+const char *ast_msg_data_get_attribute(struct ast_msg_data *msg,<br>+     enum ast_msg_data_attribute_type attribute_type)<br>+{<br>+ if (!msg) {<br>+          ast_assert(msg != NULL);<br>+             return "";<br>+ }<br>+<br>+ if (msg->attribute_value_offsets[attribute_type] > ATTRIBUTE_UNSET) {<br>+          return msg->buf + msg->attribute_value_offsets[attribute_type];<br>+        }<br>+<br>+ return "";<br>+}<br>+<br>+int ast_msg_data_queue_frame(struct ast_channel *channel, struct ast_msg_data *msg)<br>+{<br>+        struct ast_frame f;<br>+<br>+       if (!channel) {<br>+              ast_assert(channel != NULL);<br>+         return -1;<br>+   }<br>+<br>+ if (!msg) {<br>+          ast_assert(msg != NULL);<br>+             return -1;<br>+   }<br>+<br>+ memset(&f, 0, sizeof(f));<br>+        f.frametype = AST_FRAME_TEXT_DATA;<br>+   f.data.ptr = msg;<br>+    f.datalen = msg->length;<br>+  return ast_queue_frame(channel, &f);<br>+}<br>+<br> int ast_msg_tech_register(const struct ast_msg_tech *tech)<br> {<br>      const struct ast_msg_tech *match;<br>diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c<br>index 3209141..cbc6ea5 100644<br>--- a/res/res_pjsip_messaging.c<br>+++ b/res/res_pjsip_messaging.c<br>@@ -78,6 +78,32 @@<br> <br> /*!<br>  * \internal<br>+ * \brief Checks to make sure the request has the correct content type.<br>+ *<br>+ * \details This module supports the following media types: "text/\*".<br>+ * Return unsupported otherwise.<br>+ *<br>+ * \param rdata The SIP request<br>+ */<br>+static enum pjsip_status_code check_content_type_any_text(const pjsip_rx_data *rdata)<br>+{<br>+     int res = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE;<br>+   pj_str_t text = { "text", 4};<br>+<br>+   /* We'll accept any text/ content type */<br>+        if (rdata->msg_info.msg->body && rdata->msg_info.msg->body->len<br>+               && pj_stricmp(&rdata->msg_info.msg->body->content_type.type, &text) == 0) {<br>+         res = PJSIP_SC_OK;<br>+   } else if (rdata->msg_info.ctype<br>+          && pj_stricmp(&rdata->msg_info.ctype->media.type, &text) == 0) {<br>+               res = PJSIP_SC_OK;<br>+   }<br>+<br>+ return res;<br>+}<br>+<br>+/*!<br>+ * \internal<br>  * \brief Puts pointer past 'sip[s]:' string that should be at the<br>  * front of the given 'fromto' parameter<br>  *<br>@@ -756,39 +782,98 @@<br> <br> static int incoming_in_dialog_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)<br> {<br>-     char buf[MAX_BODY_SIZE];<br>      enum pjsip_status_code code;<br>- struct ast_frame f;<br>+  int rc;<br>       pjsip_dialog *dlg = session->inv_session->dlg;<br>  pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);<br>+ struct ast_msg_data *msg;<br>+    struct ast_party_caller *caller;<br>+     pjsip_name_addr *name_addr;<br>+  size_t from_len;<br>+     size_t to_len;<br>+       struct ast_msg_data_attribute attrs[4];<br>+      int pos = 0;<br>+ int body_pos;<br> <br>      if (!session->channel) {<br>           send_response(rdata, PJSIP_SC_NOT_FOUND, dlg, tsx);<br>           return 0;<br>     }<br> <br>- if ((code = check_content_type(rdata)) != PJSIP_SC_OK) {<br>+     code = check_content_type_any_text(rdata);<br>+   if (code != PJSIP_SC_OK) {<br>            send_response(rdata, code, dlg, tsx);<br>                 return 0;<br>     }<br> <br>- if (print_body(rdata, buf, sizeof(buf)-1) < 1) {<br>-          /* invalid body size */<br>-              send_response(rdata, PJSIP_SC_REQUEST_ENTITY_TOO_LARGE, dlg, tsx);<br>+   caller = ast_channel_caller(session->channel);<br>+<br>+ name_addr = (pjsip_name_addr *) rdata->msg_info.from->uri;<br>+     from_len = pj_strlen(&name_addr->display);<br>+    if (from_len) {<br>+              attrs[pos].type = AST_MSG_DATA_ATTR_FROM;<br>+            from_len++;<br>+          attrs[pos].value = ast_alloca(from_len);<br>+             ast_copy_pj_str(attrs[pos].value, &name_addr->display, from_len);<br>+             pos++;<br>+       } else if (caller->id.name.valid && !ast_strlen_zero(caller->id.name.str)) {<br>+           attrs[pos].type = AST_MSG_DATA_ATTR_FROM;<br>+            attrs[pos].value = caller->id.name.str;<br>+           pos++;<br>+       }<br>+<br>+ name_addr = (pjsip_name_addr *) rdata->msg_info.to->uri;<br>+       to_len = pj_strlen(&name_addr->display);<br>+      if (to_len) {<br>+                attrs[pos].type = AST_MSG_DATA_ATTR_TO;<br>+              to_len++;<br>+            attrs[pos].value = ast_alloca(to_len);<br>+               ast_copy_pj_str(attrs[pos].value, &name_addr->display, to_len);<br>+               pos++;<br>+       }<br>+<br>+ attrs[pos].type = AST_MSG_DATA_ATTR_CONTENT_TYPE;<br>+    attrs[pos].value = ast_alloca(rdata->msg_info.msg->body->content_type.type.slen<br>+             + rdata->msg_info.msg->body->content_type.subtype.slen + 2);<br>+        sprintf(attrs[pos].value, "%.*s/%.*s",<br>+             (int)rdata->msg_info.msg->body->content_type.type.slen,<br>+             rdata->msg_info.msg->body->content_type.type.ptr,<br>+           (int)rdata->msg_info.msg->body->content_type.subtype.slen,<br>+          rdata->msg_info.msg->body->content_type.subtype.ptr);<br>+       pos++;<br>+<br>+    body_pos = pos;<br>+      attrs[pos].type = AST_MSG_DATA_ATTR_BODY;<br>+    attrs[pos].value = ast_malloc(rdata->msg_info.msg->body->len + 1);<br>+  if (!attrs[pos].value) {<br>+             send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx);<br>+              return 0;<br>+    }<br>+    ast_copy_string(attrs[pos].value, rdata->msg_info.msg->body->data, rdata->msg_info.msg->body->len);<br>+        pos++;<br>+<br>+    msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_IN_DIALOG, attrs, pos);<br>+    if (!msg) {<br>+          ast_free(attrs[body_pos].value);<br>+             send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx);<br>               return 0;<br>     }<br> <br>- ast_debug(3, "Received in dialog SIP message\n");<br>+  ast_debug(1, "Received in-dialog MESSAGE from '%s:%s': %s %s\n",<br>+           ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM),<br>+             ast_channel_name(session->channel),<br>+               ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO),<br>+               ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY));<br> <br>- memset(&f, 0, sizeof(f));<br>-        f.frametype = AST_FRAME_TEXT;<br>-        f.subclass.integer = 0;<br>-      f.offset = 0;<br>-        f.data.ptr = buf;<br>-    f.datalen = strlen(buf) + 1;<br>- ast_queue_frame(session->channel, &f);<br>+        rc = ast_msg_data_queue_frame(session->channel, msg);<br>+     ast_free(attrs[body_pos].value);<br>+     ast_free(msg);<br>+       if (rc != 0) {<br>+               send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx);<br>+      } else {<br>+             send_response(rdata, PJSIP_SC_ACCEPTED, dlg, tsx);<br>+   }<br> <br>- send_response(rdata, PJSIP_SC_ACCEPTED, dlg, tsx);<br>    return 0;<br> }<br> <br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/9022">change 9022</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/9022"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: certified/13.21 </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: Idacf5900bfd5f22ab8cd235aa56dfad090d18489 </div>
<div style="display:none"> Gerrit-Change-Number: 9022 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>