<p>George Joseph has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/8792">View Change</a></p><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>---<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, 651 insertions(+), 45 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/92/8792/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/CHANGES b/CHANGES<br>index 2f8bbc0..fdee7ab 100644<br>--- a/CHANGES<br>+++ b/CHANGES<br>@@ -9,6 +9,19 @@<br> ==============================================================================<br> <br> ------------------------------------------------------------------------------<br>+--- Functionality changes from Asterisk 15.4.0 to Asterisk 15.5.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 15.3.0 to Asterisk 15.4.0 ------------<br> ------------------------------------------------------------------------------<br> <br>diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c<br>index 16e1fb8..d7d3483 100644<br>--- a/bridges/bridge_softmix.c<br>+++ b/bridges/bridge_softmix.c<br>@@ -36,6 +36,7 @@<br> #include "asterisk/stream.h"<br> #include "asterisk/test.h"<br> #include "asterisk/vector.h"<br>+#include "asterisk/message.h"<br> #include "bridge_softmix/include/bridge_softmix_internal.h"<br> <br> /*! The minimum sample rate of the bridge. */<br>@@ -1108,6 +1109,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>@@ -1201,6 +1238,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 0752084..a7f3b14 100644<br>--- a/channels/chan_pjsip.c<br>+++ b/channels/chan_pjsip.c<br>@@ -61,6 +61,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>@@ -86,6 +87,7 @@<br> static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type,<br> struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids,<br> 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>@@ -109,6 +111,7 @@<br> .requester = chan_pjsip_request,<br> .requester_with_stream_topology = chan_pjsip_request_with_stream_topology,<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>@@ -125,7 +128,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>@@ -2539,50 +2542,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>@@ -2590,14 +2642,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>@@ -2621,6 +2681,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 59c8a14..b62bae9 100644<br>--- a/funcs/func_frame_trace.c<br>+++ b/funcs/func_frame_trace.c<br>@@ -59,6 +59,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>@@ -88,6 +89,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>@@ -391,6 +393,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 8cb0380..295fcbe 100644<br>--- a/include/asterisk/channel.h<br>+++ b/include/asterisk/channel.h<br>@@ -593,6 +593,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>@@ -807,6 +812,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>@@ -934,6 +942,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>@@ -2182,6 +2194,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 c3c0f88..542407e 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>@@ -129,6 +130,8 @@<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 3aac5eb..eb4b9ad 100644<br>--- a/main/bridge_channel.c<br>+++ b/main/bridge_channel.c<br>@@ -56,6 +56,7 @@<br> #include "asterisk/test.h"<br> #include "asterisk/sem.h"<br> #include "asterisk/stream.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>@@ -1053,6 +1054,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>@@ -2349,6 +2364,7 @@<br> struct ast_frame *fr;<br> struct sync_payload *sync_payload;<br> int num;<br>+ struct ast_msg_data *msg;<br> <br> ast_bridge_channel_lock(bridge_channel);<br> <br>@@ -2381,6 +2397,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>@@ -2404,6 +2421,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> /* Assume that there is no mapped stream for this */<br> num = -1;<br>diff --git a/main/channel.c b/main/channel.c<br>index 0794020..861166f 100644<br>--- a/main/channel.c<br>+++ b/main/channel.c<br>@@ -73,6 +73,7 @@<br> #include "asterisk/stasis_channels.h"<br> #include "asterisk/max_forwards.h"<br> #include "asterisk/stream.h"<br>+#include "asterisk/message.h"<br> <br> /*** DOCUMENTATION<br> ***/<br>@@ -1504,6 +1505,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>@@ -2789,6 +2791,7 @@<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>@@ -4674,9 +4677,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>@@ -4685,35 +4690,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 dd47f42..383571f 100644<br>--- a/main/frame.c<br>+++ b/main/frame.c<br>@@ -593,6 +593,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 ac7965e..b7d14f1 100644<br>--- a/main/message.c<br>+++ b/main/message.c<br>@@ -1348,6 +1348,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 b6e7a64..38d0813 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/8792">change 8792</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/8792"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 15 </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: Idacf5900bfd5f22ab8cd235aa56dfad090d18489 </div>
<div style="display:none"> Gerrit-Change-Number: 8792 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>