diff --git a/CHANGES b/CHANGES index 3a81ca7bae4c1c4311f5c8f455ba2101a8787f17..7fdae7066b24eea8084d27dcbce9c80358b0fec7 100644 --- a/CHANGES +++ b/CHANGES @@ -104,6 +104,19 @@ Core: * libedit is no longer available as an embedded library and must be provided by the system. +------------------------------------------------------------------------------ +--- Functionality changes from Asterisk 15.4.0 to Asterisk 15.5.0 ------------ +------------------------------------------------------------------------------ + +Core +------------------ + * Core bridging and, more specifically, bridge_softmix have been enhanced to + relay received frames of type TEXT or TEXT_DATA to all participants in a + softmix bridge. res_pjsip_messaging and chan_pjsip have been enhanced to + take advantage of this so when res_pjsip_messaging receives an in-dialog + MESSAGE message from a user in a conference call, it's relayed to all + other participants in the call. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 15.3.0 to Asterisk 15.4.0 ------------ ------------------------------------------------------------------------------ diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index 16e1fb897c19b3baa4e532eca49141f72118c805..d7d34831ec0b9c296bc3f9c67a5ad11e2a48e8ac 100644 --- a/bridges/bridge_softmix.c +++ b/bridges/bridge_softmix.c @@ -36,6 +36,7 @@ #include "asterisk/stream.h" #include "asterisk/test.h" #include "asterisk/vector.h" +#include "asterisk/message.h" #include "bridge_softmix/include/bridge_softmix_internal.h" /*! The minimum sample rate of the bridge. */ @@ -1106,6 +1107,42 @@ cleanup: ast_stream_topology_free(source_video); } +/*! + * \internal + * \brief Determine what to do with a text frame. + * \since 13.22.0 + * \since 15.5.0 + * + * \param bridge Which bridge is getting the frame + * \param bridge_channel Which channel is writing the frame. + * \param frame What is being written. + * + * \return Nothing + */ +static void softmix_bridge_write_text(struct ast_bridge *bridge, + struct ast_bridge_channel *bridge_channel, struct ast_frame *frame) +{ + if (DEBUG_ATLEAST(1)) { + struct ast_msg_data *msg = frame->data.ptr; + char frame_type[64]; + + ast_frame_type2str(frame->frametype, frame_type, sizeof(frame_type)); + + if (frame->frametype == AST_FRAME_TEXT_DATA) { + ast_log(LOG_DEBUG, "Received %s frame from '%s:%s': %s\n", frame_type, + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM), + ast_channel_name(bridge_channel->chan), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY)); + } else { + ast_log(LOG_DEBUG, "Received %s frame from '%s': %.*s\n", frame_type, + ast_channel_name(bridge_channel->chan), frame->datalen, + (char *)frame->data.ptr); + } + } + + ast_bridge_queue_everyone_else(bridge, bridge_channel, frame); +} + /*! * \internal * \brief Determine what to do with a control frame. @@ -1201,6 +1238,10 @@ static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_cha case AST_FRAME_VIDEO: softmix_bridge_write_video(bridge, bridge_channel, frame); break; + case AST_FRAME_TEXT: + case AST_FRAME_TEXT_DATA: + softmix_bridge_write_text(bridge, bridge_channel, frame); + break; case AST_FRAME_CONTROL: res = softmix_bridge_write_control(bridge, bridge_channel, frame); break; diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index dde7416c3180f4a0ed6671b5a91d9410f8c88e0a..b4eb711f796339a698ed21ce4311ac010436c269 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -61,6 +61,7 @@ #include "asterisk/features_config.h" #include "asterisk/pickup.h" #include "asterisk/test.h" +#include "asterisk/message.h" #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_session.h" @@ -86,6 +87,7 @@ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_forma static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type, struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); +static int chan_pjsip_sendtext_data(struct ast_channel *ast, struct ast_msg_data *msg); static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text); static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit); static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration); @@ -109,6 +111,7 @@ struct ast_channel_tech chan_pjsip_tech = { .requester = chan_pjsip_request, .requester_with_stream_topology = chan_pjsip_request_with_stream_topology, .send_text = chan_pjsip_sendtext, + .send_text_data = chan_pjsip_sendtext_data, .send_digit_begin = chan_pjsip_digit_begin, .send_digit_end = chan_pjsip_digit_end, .call = chan_pjsip_call, @@ -125,7 +128,7 @@ struct ast_channel_tech chan_pjsip_tech = { .queryoption = chan_pjsip_queryoption, .func_channel_read = pjsip_acf_channel_read, .get_pvt_uniqueid = chan_pjsip_get_uniqueid, - .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER + .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER | AST_CHAN_TP_SEND_TEXT_DATA }; /*! \brief SIP session interaction functions */ @@ -2539,50 +2542,99 @@ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_forma struct sendtext_data { struct ast_sip_session *session; - char text[0]; + struct ast_msg_data *msg; }; static void sendtext_data_destroy(void *obj) { struct sendtext_data *data = obj; - ao2_ref(data->session, -1); + ao2_cleanup(data->session); + ast_free(data->msg); } -static struct sendtext_data* sendtext_data_create(struct ast_sip_session *session, const char *text) +static struct sendtext_data* sendtext_data_create(struct ast_channel *chan, + struct ast_msg_data *msg) { - int size = strlen(text) + 1; - struct sendtext_data *data = ao2_alloc(sizeof(*data)+size, sendtext_data_destroy); + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + struct sendtext_data *data = ao2_alloc(sizeof(*data), sendtext_data_destroy); if (!data) { return NULL; } - data->session = session; + data->msg = ast_msg_data_dup(msg); + if (!data->msg) { + ao2_cleanup(data); + return NULL; + } + data->session = channel->session; ao2_ref(data->session, +1); - ast_copy_string(data->text, text, size); + return data; } static int sendtext(void *obj) { - RAII_VAR(struct sendtext_data *, data, obj, ao2_cleanup); + struct sendtext_data *data = obj; pjsip_tx_data *tdata; - - const struct ast_sip_body body = { + const char *body_text = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_BODY); + const char *content_type = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_CONTENT_TYPE); + char *sep; + struct ast_sip_body body = { .type = "text", .subtype = "plain", - .body_text = data->text + .body_text = body_text, }; + if (!ast_strlen_zero(content_type)) { + sep = strchr(content_type, '/'); + if (sep) { + *sep = '\0'; + body.type = content_type; + body.subtype = ++sep; + } + } + if (data->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n", data->session->inv_session->cause, pjsip_get_status_text(data->session->inv_session->cause)->ptr); } else { - ast_debug(3, "Sending in dialog SIP message\n"); + pjsip_from_hdr *hdr; + pjsip_name_addr *name_addr; + const char *from = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_FROM); + const char *to = ast_msg_data_get_attribute(data->msg, AST_MSG_DATA_ATTR_TO); + int invalidate_tdata = 0; ast_sip_create_request("MESSAGE", data->session->inv_session->dlg, data->session->endpoint, NULL, NULL, &tdata); ast_sip_add_body(tdata, &body); + + /* + * If we have a 'from' in the msg, set the display name in the From + * header to it. + */ + if (!ast_strlen_zero(from)) { + hdr = PJSIP_MSG_FROM_HDR(tdata->msg); + name_addr = (pjsip_name_addr *) hdr->uri; + pj_strdup2(tdata->pool, &name_addr->display, from); + invalidate_tdata = 1; + } + + /* + * If we have a 'to' in the msg, set the display name in the To + * header to it. + */ + if (!ast_strlen_zero(to)) { + hdr = PJSIP_MSG_TO_HDR(tdata->msg); + name_addr = (pjsip_name_addr *) hdr->uri; + pj_strdup2(tdata->pool, &name_addr->display, to); + invalidate_tdata = 1; + } + + if (invalidate_tdata) { + pjsip_tx_data_invalidate_msg(tdata); + } + ast_sip_send_request(tdata, data->session->inv_session->dlg, data->session->endpoint, NULL, NULL); } @@ -2590,14 +2642,22 @@ static int sendtext(void *obj) pjsip_inv_dec_ref(data->session->inv_session); #endif + ao2_cleanup(data); + return 0; } /*! \brief Function called by core to send text on PJSIP session */ -static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text) +static int chan_pjsip_sendtext_data(struct ast_channel *ast, struct ast_msg_data *msg) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); - struct sendtext_data *data = sendtext_data_create(channel->session, text); + struct sendtext_data *data = sendtext_data_create(ast, msg); + + ast_debug(1, "Sending MESSAGE from '%s' to '%s:%s': %s\n", + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO), + ast_channel_name(ast), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY)); if (!data) { return -1; @@ -2621,6 +2681,28 @@ static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text) return 0; } +static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text) +{ + struct ast_msg_data *msg; + int rc; + struct ast_msg_data_attribute attrs[] = + { + { + .type = AST_MSG_DATA_ATTR_BODY, + .value = (char *)text, + } + }; + + msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_UNKNOWN, attrs, ARRAY_LEN(attrs)); + if (!msg) { + return -1; + } + rc = chan_pjsip_sendtext_data(ast, msg); + ast_free(msg); + + return rc; +} + /*! \brief Convert SIP hangup causes to Asterisk hangup causes */ static int hangup_sip2cause(int cause) { diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c index 59c8a1461ce5a64a59b147707360dcae36ab5a70..b62bae9deeb4421d5bf1a0f83a7da4ba3ca994a1 100644 --- a/funcs/func_frame_trace.c +++ b/funcs/func_frame_trace.c @@ -59,6 +59,7 @@ <enum name = "NULL" /> <enum name = "IAX" /> <enum name = "TEXT" /> + <enum name = "TEXT_DATA" /> <enum name = "IMAGE" /> <enum name = "HTML" /> <enum name = "CNG" /> @@ -88,6 +89,7 @@ static struct { { AST_FRAME_NULL, "NULL" }, { AST_FRAME_IAX, "IAX" }, { AST_FRAME_TEXT, "TEXT" }, + { AST_FRAME_TEXT_DATA, "TEXT_DATA" }, { AST_FRAME_IMAGE, "IMAGE" }, { AST_FRAME_HTML, "HTML" }, { AST_FRAME_CNG, "CNG" }, @@ -391,6 +393,9 @@ static void print_frame(struct ast_frame *frame) case AST_FRAME_TEXT: ast_verbose("FrameType: TXT\n"); break; + case AST_FRAME_TEXT_DATA: + ast_verbose("FrameType: TXT_DATA\n"); + break; case AST_FRAME_IMAGE: ast_verbose("FrameType: IMAGE\n"); break; diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 16f9aa8aef930b466e80fe221f326187a84bcd46..c865a8a32b2e51e336a47fcd21da19088e3963d1 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -592,6 +592,11 @@ struct ast_assigned_ids { const char *uniqueid2; }; +/*! + * \brief Forward declaration + */ +struct ast_msg_data; + /*! * \brief * Structure to describe a channel "technology", ie a channel driver @@ -807,6 +812,9 @@ struct ast_channel_tech { * \retval -1 on error. */ int (*pre_call)(struct ast_channel *chan, const char *sub_args); + + /*! \brief Display or transmit text with data*/ + int (* const send_text_data)(struct ast_channel *chan, struct ast_msg_data *data); }; /*! Kill the channel channel driver technology descriptor. */ @@ -934,6 +942,10 @@ enum { * world */ AST_CHAN_TP_INTERNAL = (1 << 2), + /*! + * \brief Channels have this property if they implement send_text_data + */ + AST_CHAN_TP_SEND_TEXT_DATA = (1 << 3), }; /*! \brief ast_channel flags */ @@ -2133,6 +2145,26 @@ int ast_set_write_format_interleaved_stereo(struct ast_channel *chan, struct ast */ int ast_sendtext(struct ast_channel *chan, const char *text); +/*! + * \brief Sends text to a channel in an ast_msg_data structure wrapper with ast_sendtext as fallback + * \since 13.22.0 + * \since 15.5.0 + * + * \param chan channel to act upon + * \param msg ast_msg_data structure + * + * \details + * Write text to a display on a channel. If the channel driver doesn't support the + * send_text_data callback. then the original send_text callback will be used if + * available. + * + * \note The channel does not need to be locked before calling this function. + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_sendtext_data(struct ast_channel *chan, struct ast_msg_data *msg); + /*! * \brief Receives a text character from a channel * \param chan channel to act upon diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index c3c0f88176cbafd31fa9a5ae54d0d4cb72bb3b55..542407ecc93dbe5fa50281d0be5db1c30f0464bc 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -48,6 +48,7 @@ extern "C" { * \arg \b DTMF: A DTMF digit, subclass is the digit * \arg \b IMAGE: Image transport, mostly used in IAX * \arg \b TEXT: Text messages and character by character (real time text) + * \arg \b TEXT_DATA: Text messages in an ast_msg_data structure * \arg \b HTML: URL's and web pages * \arg \b MODEM: Modulated data encodings, such as T.38 and V.150 * \arg \b IAX: Private frame type for the IAX protocol @@ -129,6 +130,8 @@ enum ast_frame_type { AST_FRAME_BRIDGE_ACTION_SYNC, /*! RTCP feedback (the subclass will contain the payload type) */ AST_FRAME_RTCP, + /*! Text message in an ast_msg_data structure */ + AST_FRAME_TEXT_DATA, }; #define AST_FRAME_DTMF AST_FRAME_DTMF_END diff --git a/include/asterisk/message.h b/include/asterisk/message.h index ae6533c46f65cc48e5f996aff4f3e979f3d12acc..826fa0ac3aeffc6bd32cca44ef0c91020be94ff7 100644 --- a/include/asterisk/message.h +++ b/include/asterisk/message.h @@ -407,6 +407,129 @@ void ast_msg_var_iterator_destroy(struct ast_msg_var_iterator *iter); */ void ast_msg_var_unref_current(struct ast_msg_var_iterator *iter); + +/*! \defgroup ast_msg_data Enhanced Messaging + * @{ + * \page Messaging Enhanced Messaging + * + * The basic messaging framework has a basic drawback... It can only pass + * a text string through the core. This causes several issues: + * \li Only a content type of text/plain can be passed. + * \li If a softmix bridge is used, the original sender identity is lost. + * + * The Enhanced Messaging framework allows attributes, such as "From", "To" + * and "Content-Type" to be attached to the message by the incoming channel + * tech which can then be used by the outgoing channel tech to construct + * the appropriate technology-specific outgoing message. + */ + +/*! + * \brief Structure used to transport an enhanced message through the frame core + * \since 13.22.0 + * \since 15.5.0 + */ +struct ast_msg_data; + +enum ast_msg_data_source_type { + AST_MSG_DATA_SOURCE_TYPE_UNKNOWN = 0, + AST_MSG_DATA_SOURCE_TYPE_T140, + AST_MSG_DATA_SOURCE_TYPE_IN_DIALOG, + AST_MSG_DATA_SOURCE_TYPE_OUT_OF_DIALOG, + __AST_MSG_DATA_SOURCE_TYPE_LAST, +}; + +enum ast_msg_data_attribute_type { + AST_MSG_DATA_ATTR_TO = 0, + AST_MSG_DATA_ATTR_FROM, + AST_MSG_DATA_ATTR_CONTENT_TYPE, + AST_MSG_DATA_ATTR_BODY, + __AST_MSG_DATA_ATTR_LAST, +}; + +struct ast_msg_data_attribute { + enum ast_msg_data_attribute_type type; + char *value; +}; + +/*! + * \brief Allocates an ast_msg_data structure. + * \since 13.22.0 + * \since 15.5.0 + * + * \param source The source type of the message + * \param attributes A pointer to an array of ast_msg_data_attribute structures + * \param count The number of elements in the array + * + * \return Pointer to msg structure or NULL on allocation failure. + * Caller must call ast_free when done. + */ +struct ast_msg_data *ast_msg_data_alloc(enum ast_msg_data_source_type source, + struct ast_msg_data_attribute attributes[], size_t count); + +/*! + * \brief Clone an ast_msg_data structure + * \since 13.22.0 + * \since 15.5.0 + * + * \param msg The message to clone + * + * \return New message structure or NULL if there was an allocation failure. + * Caller must call ast_free when done. + */ +struct ast_msg_data *ast_msg_data_dup(struct ast_msg_data *msg); + +/*! + * \brief Get length of the structure + * \since 13.22.0 + * \since 15.5.0 + * + * \param msg Pointer to ast_msg_data structure + * + * \return The length of the structure itself plus the dynamically allocated attribute buffer. + */ +size_t ast_msg_data_get_length(struct ast_msg_data *msg); + +/*! + * \brief Get "source type" from ast_msg_data + * \since 13.22.0 + * \since 15.5.0 + * + * \param msg Pointer to ast_msg_data structure + * + * \return The source type field. + */ +enum ast_msg_data_source_type ast_msg_data_get_source_type(struct ast_msg_data *msg); + +/*! + * \brief Get attribute from ast_msg_data + * \since 13.22.0 + * \since 15.5.0 + * + * \param msg Pointer to ast_msg_data structure + * \param attribute_type One of ast_msg_data_attribute_type + * + * \return The attribute or an empty string ("") if the attribute wasn't set. + */ +const char *ast_msg_data_get_attribute(struct ast_msg_data *msg, + enum ast_msg_data_attribute_type attribute_type); + +/*! + * \brief Queue an AST_FRAME_TEXT_DATA frame containing an ast_msg_data structure + * \since 13.22.0 + * \since 15.5.0 + * + * \param channel The channel on which to queue the frame + * \param msg Pointer to ast_msg_data structure + * + * \retval -1 Error + * \retval 0 Success + */ +int ast_msg_data_queue_frame(struct ast_channel *channel, struct ast_msg_data *msg); + +/*! + * @} + */ + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/main/bridge_channel.c b/main/bridge_channel.c index 3aac5eb254dfe0b9d88be1b2ea580ded2e22368a..eb4b9ad0e8f76ccc4eab3d817c990bb23638b9e1 100644 --- a/main/bridge_channel.c +++ b/main/bridge_channel.c @@ -56,6 +56,7 @@ #include "asterisk/test.h" #include "asterisk/sem.h" #include "asterisk/stream.h" +#include "asterisk/message.h" /*! * \brief Used to queue an action frame onto a bridge channel and write an action frame into a bridge. @@ -1055,6 +1056,20 @@ int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, st return 0; } + if (DEBUG_ATLEAST(1)) { + if (fr->frametype == AST_FRAME_TEXT) { + ast_log(LOG_DEBUG, "Queuing TEXT frame to '%s': %*.s\n", ast_channel_name(bridge_channel->chan), + fr->datalen, (char *)fr->data.ptr); + } else if (fr->frametype == AST_FRAME_TEXT_DATA) { + struct ast_msg_data *msg = fr->data.ptr; + ast_log(LOG_DEBUG, "Queueing TEXT_DATA frame from '%s' to '%s:%s': %s\n", + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO), + ast_channel_name(bridge_channel->chan), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY)); + } + } + AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list); if (ast_alertpipe_write(bridge_channel->alert_pipe)) { ast_log(LOG_ERROR, "We couldn't write alert pipe for %p(%s)... something is VERY wrong\n", @@ -2349,6 +2364,7 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe struct ast_frame *fr; struct sync_payload *sync_payload; int num; + struct ast_msg_data *msg; ast_bridge_channel_lock(bridge_channel); @@ -2381,6 +2397,7 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe AST_LIST_TRAVERSE_SAFE_END; ast_bridge_channel_unlock(bridge_channel); + if (!fr) { /* * Wait some to reduce CPU usage from a tight loop @@ -2404,6 +2421,20 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe break; case AST_FRAME_NULL: break; + case AST_FRAME_TEXT: + ast_debug(1, "Sending TEXT frame to '%s': %*.s\n", + ast_channel_name(bridge_channel->chan), fr->datalen, (char *)fr->data.ptr); + ast_sendtext(bridge_channel->chan, fr->data.ptr); + break; + case AST_FRAME_TEXT_DATA: + msg = (struct ast_msg_data *)fr->data.ptr; + ast_debug(1, "Sending TEXT_DATA frame from '%s' to '%s:%s': %s\n", + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO), + ast_channel_name(bridge_channel->chan), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY)); + ast_sendtext_data(bridge_channel->chan, msg); + break; default: /* Assume that there is no mapped stream for this */ num = -1; diff --git a/main/channel.c b/main/channel.c index 815d5dbfe5b8316daadb93c2f1ef7e3f9aabe5e1..a23dfa10c7af2feb333cd6ba5abccb1a4fe40f10 100644 --- a/main/channel.c +++ b/main/channel.c @@ -73,6 +73,7 @@ #include "asterisk/stasis_channels.h" #include "asterisk/max_forwards.h" #include "asterisk/stream.h" +#include "asterisk/message.h" /*** DOCUMENTATION ***/ @@ -1492,6 +1493,7 @@ int ast_is_deferrable_frame(const struct ast_frame *frame) case AST_FRAME_BRIDGE_ACTION_SYNC: case AST_FRAME_CONTROL: case AST_FRAME_TEXT: + case AST_FRAME_TEXT_DATA: case AST_FRAME_IMAGE: case AST_FRAME_HTML: return 1; @@ -2767,6 +2769,7 @@ int __ast_answer(struct ast_channel *chan, unsigned int delay) case AST_FRAME_VOICE: case AST_FRAME_VIDEO: case AST_FRAME_TEXT: + case AST_FRAME_TEXT_DATA: case AST_FRAME_DTMF_BEGIN: case AST_FRAME_DTMF_END: case AST_FRAME_IMAGE: @@ -4652,9 +4655,11 @@ char *ast_recvtext(struct ast_channel *chan, int timeout) return buf; } -int ast_sendtext(struct ast_channel *chan, const char *text) +int ast_sendtext_data(struct ast_channel *chan, struct ast_msg_data *msg) { int res = 0; + const char *body = ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY); + const char *content_type = ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_CONTENT_TYPE); ast_channel_lock(chan); /* Stop if we're a zombie or need a soft hangup */ @@ -4663,35 +4668,76 @@ int ast_sendtext(struct ast_channel *chan, const char *text) return -1; } - if (ast_strlen_zero(text)) { - ast_channel_unlock(chan); - return 0; - } - CHECK_BLOCKING(chan); - if (ast_channel_tech(chan)->write_text && (ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_TEXT))) { + if (ast_channel_tech(chan)->write_text + && (ast_strlen_zero(content_type) || strcasecmp(content_type, "text/plain") == 0) + && (ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_TEXT))) { struct ast_frame f; + size_t body_len = strlen(body) + 1; + /* Process as T.140 text (moved here from ast_sendtext() */ memset(&f, 0, sizeof(f)); - f.frametype = AST_FRAME_TEXT; f.src = "DIALPLAN"; - f.mallocd = AST_MALLOCD_DATA; - f.datalen = strlen(text); - f.data.ptr = ast_strdup(text); f.subclass.format = ast_format_t140; - + f.frametype = AST_FRAME_TEXT; + f.datalen = body_len; + f.mallocd = AST_MALLOCD_DATA; + f.data.ptr = ast_strdup(body); if (f.data.ptr) { res = ast_channel_tech(chan)->write_text(chan, &f); - ast_frfree(&f); + } else { + res = -1; } - } else if (ast_channel_tech(chan)->send_text) { - res = ast_channel_tech(chan)->send_text(chan, text); + ast_frfree(&f); + } else if ((ast_channel_tech(chan)->properties & AST_CHAN_TP_SEND_TEXT_DATA) + && ast_channel_tech(chan)->send_text_data) { + /* Send enhanced message to a channel driver that supports it */ + ast_debug(1, "Sending TEXT_DATA from '%s' to %s:%s %s\n", + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO), + ast_channel_name(chan), body); + res = ast_channel_tech(chan)->send_text_data(chan, msg); + } else if (ast_channel_tech(chan)->send_text + && (ast_strlen_zero(content_type) || strcasecmp(content_type, "text/plain") == 0)) { + /* Send the body of an enhanced message to a channel driver that supports only a char str */ + ast_debug(1, "Sending TEXT to %s: %s\n", ast_channel_name(chan), body); + res = ast_channel_tech(chan)->send_text(chan, body); + } else { + ast_debug(1, "Channel technology does not support sending text on channel '%s'\n", + ast_channel_name(chan)); + res = -1; } ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BLOCKING); ast_channel_unlock(chan); return res; } +int ast_sendtext(struct ast_channel *chan, const char *text) +{ + struct ast_msg_data *msg; + int rc; + struct ast_msg_data_attribute attrs[] = + { + { + .type = AST_MSG_DATA_ATTR_BODY, + .value = (char *)text, + } + }; + + if (ast_strlen_zero(text)) { + return 0; + } + + msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_UNKNOWN, attrs, ARRAY_LEN(attrs)); + if (!msg) { + return -1; + } + rc = ast_sendtext_data(chan, msg); + ast_free(msg); + + return rc; +} + int ast_senddigit_begin(struct ast_channel *chan, char digit) { /* Device does not support DTMF tones, lets fake diff --git a/main/frame.c b/main/frame.c index dd47f42d0ad102e83e33f72800e4edd44eb11f67..383571f65dd349edc8241c65c6c2bd2f0270b340 100644 --- a/main/frame.c +++ b/main/frame.c @@ -593,6 +593,9 @@ void ast_frame_type2str(enum ast_frame_type frame_type, char *ftype, size_t len) case AST_FRAME_TEXT: ast_copy_string(ftype, "Text", len); break; + case AST_FRAME_TEXT_DATA: + ast_copy_string(ftype, "Text Data", len); + break; case AST_FRAME_IMAGE: ast_copy_string(ftype, "Image", len); break; diff --git a/main/message.c b/main/message.c index ac7965ea73013c56b715aacd15a9822b2bef5eb9..b7d14f1e8fc405bbdaa0ed4f892a1682817a3460 100644 --- a/main/message.c +++ b/main/message.c @@ -1348,6 +1348,148 @@ int ast_msg_send(struct ast_msg *msg, const char *to, const char *from) return res; } +/*! + * \brief Structure used to transport a message through the frame core + * \since 13.22.0 + * \since 15.5.0 + */ +struct ast_msg_data { + /*! The length of this structure plus the actual length of the allocated buffer */ + size_t length; + enum ast_msg_data_source_type source; + /*! These are indices into the buffer where teh attribute starts */ + int attribute_value_offsets[__AST_MSG_DATA_ATTR_LAST]; + /*! The buffer containing the NULL separated attributes */ + char buf[0]; +}; + +#define ATTRIBUTE_UNSET -1 + +struct ast_msg_data *ast_msg_data_alloc(enum ast_msg_data_source_type source, + struct ast_msg_data_attribute attributes[], size_t count) +{ + struct ast_msg_data *msg; + size_t len = sizeof(*msg); + size_t i; + size_t current_offset = 0; + enum ast_msg_data_attribute_type attr_type; + + if (!attributes) { + ast_assert(attributes != NULL); + return NULL; + } + + if (!count) { + ast_assert(count > 0); + return NULL; + } + + /* Calculate the length required for the buffer */ + for (i=0; i < count; i++) { + if (!attributes[i].value) { + ast_assert(attributes[i].value != NULL); + return NULL; + } + len += (strlen(attributes[i].value) + 1); + } + + msg = ast_calloc(1, len); + if (!msg) { + return NULL; + } + msg->source = source; + msg->length = len; + + /* Mark all of the attributes as unset */ + for (attr_type = 0; attr_type < __AST_MSG_DATA_ATTR_LAST; attr_type++) { + msg->attribute_value_offsets[attr_type] = ATTRIBUTE_UNSET; + } + + /* Set the ones we have and increment the offset */ + for (i=0; i < count; i++) { + len = (strlen(attributes[i].value) + 1); + strcpy(msg->buf + current_offset, attributes[i].value); /* Safe */ + msg->attribute_value_offsets[attributes[i].type] = current_offset; + current_offset += len; + } + + return msg; +} + +struct ast_msg_data *ast_msg_data_dup(struct ast_msg_data *msg) +{ + struct ast_msg_data *dest; + + if (!msg) { + ast_assert(msg != NULL); + return NULL; + } + + dest = ast_malloc(msg->length); + if (!dest) { + return NULL; + } + memcpy(dest, msg, msg->length); + + return dest; +} + +size_t ast_msg_data_get_length(struct ast_msg_data *msg) +{ + if (!msg) { + ast_assert(msg != NULL); + return 0; + } + + return msg->length; +} + +enum ast_msg_data_source_type ast_msg_data_get_source_type(struct ast_msg_data *msg) +{ + if (!msg) { + ast_assert(msg != NULL); + return AST_MSG_DATA_SOURCE_TYPE_UNKNOWN; + } + + return msg->source; +} + +const char *ast_msg_data_get_attribute(struct ast_msg_data *msg, + enum ast_msg_data_attribute_type attribute_type) +{ + if (!msg) { + ast_assert(msg != NULL); + return ""; + } + + if (msg->attribute_value_offsets[attribute_type] > ATTRIBUTE_UNSET) { + return msg->buf + msg->attribute_value_offsets[attribute_type]; + } + + return ""; +} + +int ast_msg_data_queue_frame(struct ast_channel *channel, struct ast_msg_data *msg) +{ + struct ast_frame f; + + if (!channel) { + ast_assert(channel != NULL); + return -1; + } + + if (!msg) { + ast_assert(msg != NULL); + return -1; + } + + memset(&f, 0, sizeof(f)); + f.frametype = AST_FRAME_TEXT_DATA; + f.data.ptr = msg; + f.datalen = msg->length; + return ast_queue_frame(channel, &f); +} + int ast_msg_tech_register(const struct ast_msg_tech *tech) { const struct ast_msg_tech *match; diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c index cbaed83fb3aa6572a38f5945806ae126f4dd762f..d8628f5bd842ff4c32c1c3e807b633a708bf441f 100644 --- a/res/res_pjsip_messaging.c +++ b/res/res_pjsip_messaging.c @@ -75,6 +75,32 @@ static enum pjsip_status_code check_content_type(const pjsip_rx_data *rdata) return res ? PJSIP_SC_OK : PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; } +/*! + * \internal + * \brief Checks to make sure the request has the correct content type. + * + * \details This module supports the following media types: "text/\*". + * Return unsupported otherwise. + * + * \param rdata The SIP request + */ +static enum pjsip_status_code check_content_type_any_text(const pjsip_rx_data *rdata) +{ + int res = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; + pj_str_t text = { "text", 4}; + + /* We'll accept any text/ content type */ + if (rdata->msg_info.msg->body && rdata->msg_info.msg->body->len + && pj_stricmp(&rdata->msg_info.msg->body->content_type.type, &text) == 0) { + res = PJSIP_SC_OK; + } else if (rdata->msg_info.ctype + && pj_stricmp(&rdata->msg_info.ctype->media.type, &text) == 0) { + res = PJSIP_SC_OK; + } + + return res; +} + /*! * \internal * \brief Puts pointer past 'sip[s]:' string that should be at the @@ -755,39 +781,98 @@ static pj_bool_t module_on_rx_request(pjsip_rx_data *rdata) static int incoming_in_dialog_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata) { - char buf[MAX_BODY_SIZE]; enum pjsip_status_code code; - struct ast_frame f; + int rc; pjsip_dialog *dlg = session->inv_session->dlg; pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); + struct ast_msg_data *msg; + struct ast_party_caller *caller; + pjsip_name_addr *name_addr; + size_t from_len; + size_t to_len; + struct ast_msg_data_attribute attrs[4]; + int pos = 0; + int body_pos; if (!session->channel) { send_response(rdata, PJSIP_SC_NOT_FOUND, dlg, tsx); return 0; } - if ((code = check_content_type(rdata)) != PJSIP_SC_OK) { + code = check_content_type_any_text(rdata); + if (code != PJSIP_SC_OK) { send_response(rdata, code, dlg, tsx); return 0; } - if (print_body(rdata, buf, sizeof(buf)-1) < 1) { - /* invalid body size */ - send_response(rdata, PJSIP_SC_REQUEST_ENTITY_TOO_LARGE, dlg, tsx); + caller = ast_channel_caller(session->channel); + + name_addr = (pjsip_name_addr *) rdata->msg_info.from->uri; + from_len = pj_strlen(&name_addr->display); + if (from_len) { + attrs[pos].type = AST_MSG_DATA_ATTR_FROM; + from_len++; + attrs[pos].value = ast_alloca(from_len); + ast_copy_pj_str(attrs[pos].value, &name_addr->display, from_len); + pos++; + } else if (caller->id.name.valid && !ast_strlen_zero(caller->id.name.str)) { + attrs[pos].type = AST_MSG_DATA_ATTR_FROM; + attrs[pos].value = caller->id.name.str; + pos++; + } + + name_addr = (pjsip_name_addr *) rdata->msg_info.to->uri; + to_len = pj_strlen(&name_addr->display); + if (to_len) { + attrs[pos].type = AST_MSG_DATA_ATTR_TO; + to_len++; + attrs[pos].value = ast_alloca(to_len); + ast_copy_pj_str(attrs[pos].value, &name_addr->display, to_len); + pos++; + } + + attrs[pos].type = AST_MSG_DATA_ATTR_CONTENT_TYPE; + attrs[pos].value = ast_alloca(rdata->msg_info.msg->body->content_type.type.slen + + rdata->msg_info.msg->body->content_type.subtype.slen + 2); + sprintf(attrs[pos].value, "%.*s/%.*s", + (int)rdata->msg_info.msg->body->content_type.type.slen, + rdata->msg_info.msg->body->content_type.type.ptr, + (int)rdata->msg_info.msg->body->content_type.subtype.slen, + rdata->msg_info.msg->body->content_type.subtype.ptr); + pos++; + + body_pos = pos; + attrs[pos].type = AST_MSG_DATA_ATTR_BODY; + attrs[pos].value = ast_malloc(rdata->msg_info.msg->body->len + 1); + if (!attrs[pos].value) { + send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx); return 0; } + ast_copy_string(attrs[pos].value, rdata->msg_info.msg->body->data, rdata->msg_info.msg->body->len); + pos++; - ast_debug(3, "Received in dialog SIP message\n"); + msg = ast_msg_data_alloc(AST_MSG_DATA_SOURCE_TYPE_IN_DIALOG, attrs, pos); + if (!msg) { + ast_free(attrs[body_pos].value); + send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx); + return 0; + } - memset(&f, 0, sizeof(f)); - f.frametype = AST_FRAME_TEXT; - f.subclass.integer = 0; - f.offset = 0; - f.data.ptr = buf; - f.datalen = strlen(buf) + 1; - ast_queue_frame(session->channel, &f); + ast_debug(1, "Received in-dialog MESSAGE from '%s:%s': %s %s\n", + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_FROM), + ast_channel_name(session->channel), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_TO), + ast_msg_data_get_attribute(msg, AST_MSG_DATA_ATTR_BODY)); + + rc = ast_msg_data_queue_frame(session->channel, msg); + ast_free(attrs[body_pos].value); + ast_free(msg); + if (rc != 0) { + send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, dlg, tsx); + } else { + send_response(rdata, PJSIP_SC_ACCEPTED, dlg, tsx); + } - send_response(rdata, PJSIP_SC_ACCEPTED, dlg, tsx); return 0; }