diff --git a/src/iax/iaxcall.cpp b/src/iax/iaxcall.cpp index 5397a7fa59eb1e6b4a1d9dc293b632906d57f6d2..5cd366f869e42b91585946114439b1e4603a1c2f 100644 --- a/src/iax/iaxcall.cpp +++ b/src/iax/iaxcall.cpp @@ -241,12 +241,12 @@ IAXCall::carryingDTMFdigits(char code) #if HAVE_INSTANT_MESSAGING void -IAXCall::sendTextMessage(const std::map<std::string, std::string>& messages, +IAXCall::sendTextMessage(const std::map<std::string, std::string>& /*messages */, const std::string& /*from*/) { - std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); - const auto& msgs = InstantMessaging::appendMimePayloads(messages); - InstantMessaging::sendIaxMessage(session, getCallId(), msgs); + // std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + //TODO: implement multipart messages for IAX + // InstantMessaging::sendIaxMessage(session, getCallId(), msgs); } #endif diff --git a/src/im/instant_messaging.cpp b/src/im/instant_messaging.cpp index 9656b91e87fa9dde69f87489bf1d1bcc02d26d91..f5d3829547f3b1a93fc133d6de5c820e59969be6 100644 --- a/src/im/instant_messaging.cpp +++ b/src/im/instant_messaging.cpp @@ -3,6 +3,7 @@ * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> * Author: Emmanuel Lepage <elv1313@gmail.com> * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,69 +37,269 @@ #include <expat.h> #include <pjsip_ua.h> +#include <pjsip.h> namespace ring { -static void XMLCALL -startElementCallback(void* userData, const char* name, const char** atts) +/** + * the pair<string, string> we receive is expected to be in the format <mime type, payload> + * the mime type is in the format "type/subtype" + * in the header it will be presented as "Content-Type: type/subtype" + * following the RFC spec, this header line can also contain other parameters in the format: + * Content-Type: type/subtype; arg=value; arg=value; ... + * thus we also accept the key of the map to be in such a format: + * type/subtype; arg=value; arg=value; ... + */ +static void +createMessageBody(pj_pool_t* pool, const std::pair<std::string, std::string>& payload, + pjsip_msg_body** body_p) { - if (strcmp(name, "entry")) + /* parse the key: + * 1. split by ';' + * 2. parse the first result by spliting by '/' into a type and subtype + * 3. parse any following strings into arg=value by splitting by '=' + */ + + // NOTE: we duplicate all the c_str when creating pj_str_t strings because we're potentially + // working with local vars which might be destroyed before the message is sent and thus the + // the mem they pointed to could be something else at the time the message is actually sent + + std::string mimeType, parameters; + auto sep = payload.first.find(';'); + if (std::string::npos == sep) { + mimeType = payload.first; + } else { + mimeType = payload.first.substr(0, sep); + parameters = payload.first.substr(sep + 1); + } + + // split mime type to type and subtype + sep = mimeType.find('/'); + if (std::string::npos == sep) { + RING_DBG("bad mime type: '%.30s'", mimeType.c_str()); + throw InstantMessaging::InstantMessageException("invalid mime type"); + } + + const auto& type = mimeType.substr(0, sep); + const auto& subtype = mimeType.substr(sep + 1); + + // create part + auto type_pj = pj_strdup3(pool, type.c_str()); + auto subtype_pj = pj_strdup3(pool, subtype.c_str()); + auto message_pj = pj_strdup3(pool, payload.second.c_str()); + *body_p = pjsip_msg_body_create(pool, &type_pj, &subtype_pj, &message_pj); + + if (not parameters.size()) return; - InstantMessaging::UriEntry entry = InstantMessaging::UriEntry(); + // now try to add parameters one by one + do { + sep = parameters.find(';'); + const auto& paramPair = parameters.substr(0, sep); + if (not paramPair.size()) + break; - for (const char **att = atts; *att; att += 2) - entry.insert(std::pair<std::string, std::string> (*att, *(att+1))); + // split paramPair into arg and value by '=' + auto paramSplit = paramPair.find('='); + if (std::string::npos == paramSplit) { + RING_DBG("bad parameter: '%.30s'", paramPair.c_str()); + throw InstantMessaging::InstantMessageException("invalid parameter"); + } - static_cast<InstantMessaging::UriList *>(userData)->push_back(entry); -} + const auto& arg = paramPair.substr(0, paramSplit); + const auto& value = paramPair.substr(paramSplit + 1); -static void XMLCALL -endElementCallback(void * /*userData*/, const char* /*name*/) -{} + // add to the body content type + auto arg_pj = pj_strdup3(pool, arg.c_str()); + pj_strtrim(&arg_pj); + auto value_pj = pj_strdup3(pool, value.c_str()); + pj_strtrim(&value_pj); + + pjsip_param* param = PJ_POOL_ALLOC_T(pool, pjsip_param); + param->name = arg_pj; + param->value = value_pj; + pj_list_push_back(&(*body_p)->content_type.param, param); + + // next parameter? + if (std::string::npos != sep) + parameters = parameters.substr(sep + 1); + } while (std::string::npos != sep); +} void -InstantMessaging::sendSipMessage(pjsip_inv_session* session, const std::string& id, - const std::vector<std::string>& chunks) +InstantMessaging::sendSipMessage(pjsip_inv_session* session, + const std::map<std::string, std::string>& payloads) { - for (const auto& text: chunks) { - const pjsip_method msg_method = { PJSIP_OTHER_METHOD, pj_str((char*)"MESSAGE") }; - pjsip_tx_data* tdata; + if (payloads.empty()) { + RING_WARN("the payloads argument is empty; ignoring message"); + return; + } + + const pjsip_method msg_method = {PJSIP_OTHER_METHOD, {const_cast<char*>("MESSAGE"), 7}}; + { auto dialog = session->dlg; - pjsip_dlg_inc_lock(dialog); + sip_utils::PJDialogLock dialog_lock {dialog}; + + pjsip_tx_data* tdata = nullptr; + auto status = pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata); + if (status != PJ_SUCCESS) { + RING_ERR("pjsip_dlg_create_request failed: %s", + sip_utils::sip_strerror(status).c_str()); + throw InstantMessageException("Internal SIP error"); + } - if (pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata) != PJ_SUCCESS) { - pjsip_dlg_dec_lock(dialog); - return; + // multi-part body? + if (payloads.size() > 1) { + /* if ctype is not specified "multipart/mixed" will be used + * if the boundary is not specified, a random one will be generateAudioPort + * FIXME: generate boundary and check that none of the message parts contain it before + * calling this function; however the probability of this happenings if quite low as + * the randomly generated string is fairly long + */ + tdata->msg->body = pjsip_multipart_create(tdata->pool, nullptr, nullptr); + + for (const auto& pair: payloads) { + auto part = pjsip_multipart_create_part(tdata->pool); + if (not part) { + RING_ERR("pjsip_multipart_create_part failed: %s", + sip_utils::sip_strerror(status).c_str()); + throw InstantMessageException("Internal SIP error"); + } + + createMessageBody(tdata->pool, pair, &part->body); + + status = pjsip_multipart_add_part(tdata->pool, tdata->msg->body, part); + if (status != PJ_SUCCESS) { + RING_ERR("pjsip_multipart_add_part failed: %s", + sip_utils::sip_strerror(status).c_str()); + throw InstantMessageException("Internal SIP error"); + } + } + } else { + createMessageBody(tdata->pool, *payloads.begin(), &tdata->msg->body); } - //TODO multipart/mixed and multipart/related need to be handled separately - //Use the "is_mixed" sendMessage() API -// const auto type = pj_str((char*) "multipart"); -// const auto subtype = pj_str((char*) "related"); - const auto type = pj_str((char*) "text"); - const auto subtype = pj_str((char*) "plain"); - const auto message = pj_str((char*) text.c_str()); - - tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &message); - auto ret = pjsip_dlg_send_request(dialog, tdata, -1, nullptr); - if (ret != PJ_SUCCESS) - RING_WARN("SIP send message failed: %s", sip_utils::sip_strerror(ret).c_str()); - pjsip_dlg_dec_lock(dialog); + status = pjsip_dlg_send_request(dialog, tdata, -1, nullptr); + if (status != PJ_SUCCESS) { + RING_ERR("pjsip_dlg_send_request failed: %s", + sip_utils::sip_strerror(status).c_str()); + throw InstantMessageException("Internal SIP error"); + } } } +/** + * Creates std::pair with the Content-Type header contents as the first value and the message + * payload as the second value. + * + * The format of the first value will be: + * type/subtype[; *[; arg=value]] + * eg: "text/plain;id=1234;part=2;of=1001" + */ +static std::pair<std::string, std::string> +parseMessageBody(const pjsip_msg_body* body) +{ + const std::string type {body->content_type.type.ptr, (size_t)body->content_type.type.slen}; + const std::string subtype {body->content_type.subtype.ptr, (size_t)body->content_type.subtype.slen}; + std::string header = type + "/" + subtype; + + // iterate over parameters + auto param = body->content_type.param.next; + while (param != &body->content_type.param) { + const std::string arg {param->name.ptr, (size_t)param->name.slen}; + const std::string value {param->value.ptr, (size_t)param->value.slen}; + + header += ";" + arg + "=" + value; + + param = param->next; + } + + // get the payload, assume we can interpret it as chars + const std::string payload {static_cast<char*>(body->data), body->len}; + + return std::make_pair(header, payload); +} + +/** + * Parses given SIP message into a map where the key is the contents of the Content-Type header + * (along with any parameters) and the value is the message payload. + * + * @param msg received SIP message + * + * @return map of content types and message payloads + */ +std::map<std::string, std::string> +InstantMessaging::parseSipMessage(const pjsip_msg* msg) +{ + std::map<std::string, std::string> ret; + + if (!msg->body) { + RING_WARN("message body is empty"); + return ret; + } + + // check if its a multipart message + pj_str_t typeMultipart {const_cast<char*>("multipart"), 9}; + + if (pj_strcmp(&typeMultipart, &msg->body->content_type.type) != 0) { + // treat as single content type message + ret.insert(parseMessageBody(msg->body)); + } else { + /* multipart type message, we will treat it as multipart/mixed even if the subtype is + * something else, eg: related + */ + auto part = pjsip_multipart_get_first_part(msg->body); + while (part != nullptr) { + ret.insert(parseMessageBody(part->body)); + part = pjsip_multipart_get_next_part(msg->body, part); + } + } + + return ret; +} + #if HAVE_IAX void InstantMessaging::sendIaxMessage(iax_session* session, const std::string& /* id */, const std::vector<std::string>& chunks) { + //TODO: implement multipart message creation for IAX via the pjsip api and then convert + // into string for sending for (const auto& msg: chunks) iax_send_text(session, msg.c_str()); } #endif + +/* + * The following functions are for creating and parsing XML URI lists which are appended to + * instant messages in order to be able to send a message to multiple recipients as defined in + * RFC 5365 + * + * These functions are not currently used, but are left for now, along with the Expat library + * dependance, in case this functionality is implemented later. Note that it may be possible to + * replace the Expat XML parser library by using the pjsip xml functions. + */ + +static void XMLCALL +startElementCallback(void* userData, const char* name, const char** atts) +{ + if (strcmp(name, "entry")) + return; + + InstantMessaging::UriEntry entry = InstantMessaging::UriEntry(); + + for (const char **att = atts; *att; att += 2) + entry.insert(std::pair<std::string, std::string> (*att, *(att+1))); + + static_cast<InstantMessaging::UriList *>(userData)->push_back(entry); +} + +static void XMLCALL +endElementCallback(void * /*userData*/, const char* /*name*/) +{} + std::string InstantMessaging::generateXmlUriList(const UriList& list) { @@ -115,7 +316,6 @@ InstantMessaging::generateXmlUriList(const UriList& list) return xmlbuffer + "</list></resource-lists>"; } - InstantMessaging::UriList InstantMessaging::parseXmlUriList(const std::string& urilist) { @@ -134,184 +334,4 @@ InstantMessaging::parseXmlUriList(const std::string& urilist) return list; } -///See rfc2046#section-5.1.4 -static std::string -buildMimeMultipartPart(const std::string& type, const std::string& dispo, - const std::string& content) -{ - return - "--boundary\n" - "Content-Type: " + type + "\n" + - (!dispo.empty() ? "Content-Disposition: "+ dispo + "\n" : "") + - "\n" + - content + "\n"; -} - -std::vector<std::string> -InstantMessaging::appendMimePayloads(const std::map<std::string, std::string>& payloads, - const UriList& list) -{ - static const std::string footer = "--boundary--"; - std::vector<std::string> ret; - std::string chunk; - - const auto& urilist = not list.empty() ? buildMimeMultipartPart("application/resource-lists+xml", - "recipient-list", - generateXmlUriList(list)) : ""; - - const size_t max_message_size = MAXIMUM_MESSAGE_LENGTH - urilist.size() - footer.size(); - - for (const auto& pair : payloads) { - const auto& m = buildMimeMultipartPart(pair.first, {}, pair.second); - if (m.size() > max_message_size) { - RING_DBG("An %s payload is too large to be sent, the maximum lenght is %zu", - m.c_str(), max_message_size); - continue; - } - if (m.size() + chunk.size() > max_message_size) { - RING_DBG("Some MIME payloads don't fit into the packet, splitting, max size is %zu, the payload would be %zu %zu %zu", - max_message_size, m.size() + chunk.size(), m.size() , chunk.size() - ); - chunk += urilist + footer; - ret.push_back(chunk); - chunk = ""; - } - chunk += m; - } - - if (chunk.size()) - ret.push_back(chunk); - - return ret; -} - -std::string -InstantMessaging::findTextUriList(const std::string& text) -{ - static const std::string ctype("Content-Type: application/resource-lists+xml"); - static const std::string cdispo("Content-Disposition: recipient-list"); - static const std::string boundary("--boundary--"); - - // init position pointer - size_t pos = 0; - - // find the content type - if ((pos = text.find(ctype)) == std::string::npos) - throw InstantMessageException("Could not find Content-Type tag while parsing sip message for recipient-list"); - - // find the content disposition - if ((pos = text.find(cdispo, pos)) == std::string::npos) - throw InstantMessageException("Could not find Content-Disposition tag while parsing sip message for recipient-list"); - - // xml content start after content disposition tag - size_t begin = pos + cdispo.size(); - - //Remove arbitrary number of empty lines, otherwise XML_Parse will return XML_STATUS_ERROR - while (text[begin] == '\n' || text[begin] == '\r') - begin++; - - // find final boundary - size_t end; - if ((end = text.find(boundary, begin)) == std::string::npos) - throw InstantMessageException("Could not find final \"boundary\" while parsing sip message for recipient-list"); - - return text.substr(begin, end - begin); -} - -/* - * From RFC2046: - * - * MIME-Version: 1.0 - * Content-Type: multipart/alternative; boundary=boundary42 - * - * --boundary42 - * Content-Type: text/plain; charset=us-ascii - * - * ... plain text version of message goes here ... - * - * --boundary42 - * Content-Type: text/html - * - * ... RFC 1896 text/enriched version of same message - * goes here ... - * - * --boundary42 - * Content-Type: application/x-whatever - * - * ... fanciest version of same message goes here ... - * - * --boundary42-- - */ - -std::string -InstantMessaging::findMimePayload(const std::string& encodedPayloads, const std::string& mime) -{ - const std::string ctype = "Content-Type: " + mime; - const size_t pos = encodedPayloads.find(ctype); - if (pos == std::string::npos) - return {}; - const size_t begin = pos + ctype.size(); - - const size_t end = encodedPayloads.find("--boundary", begin); - if (end == std::string::npos) { - RING_DBG("Could not find end of text \"boundary\" while parsing sip message for text"); - return {}; - } - - return encodedPayloads.substr(begin, end - begin); -} - -std::map<std::string, std::string> -InstantMessaging::parsePayloads(const std::string& encodedPayloads) -{ - //Constants - static const std::string boud = "--boundary" ; - static const std::string type = "Content-Type: " ; - static const std::string dispo = "Content-Disposition: "; - const size_t end = encodedPayloads.find("--boundary--"); - - size_t next_start = encodedPayloads.find(boud); - - std::map< std::string, std::string > ret; - - do { - size_t currentStart = next_start; - - next_start = encodedPayloads.find(boud, currentStart+1); - - //Get the mime type - size_t context_pos = encodedPayloads.find(type, currentStart+1); - if (context_pos == std::string::npos) - break; - else if (context_pos >= next_start) - continue; - - context_pos += type.size(); - - size_t mimeTypeEnd = encodedPayloads.find('\n', context_pos+1); - if (encodedPayloads[context_pos-1] == '\r') - mimeTypeEnd--; - - std::string mimeType = encodedPayloads.substr(context_pos, mimeTypeEnd - context_pos); - currentStart = mimeTypeEnd+1; - - //Remove the disposition - const size_t dispoPos = encodedPayloads.find(dispo, currentStart); - if (dispoPos != std::string::npos && dispoPos < next_start) { - currentStart = encodedPayloads.find('\n', dispoPos); - while (encodedPayloads[currentStart] == '\n' || encodedPayloads[currentStart] == '\r') - currentStart++; - } - - //Get the payload - std::string payload = encodedPayloads.substr(currentStart, next_start - currentStart); - - //WARNING assume only one message per payload exist - ret[mimeType] = payload; - - } while(next_start < end); - - return ret; -} - } // namespace ring diff --git a/src/im/instant_messaging.h b/src/im/instant_messaging.h index 43b62d7598b0c6b648cc416e52db2d5f7a4380fb..407e24d63b3ff5487b6943c97da9eb14a033688f 100644 --- a/src/im/instant_messaging.h +++ b/src/im/instant_messaging.h @@ -2,6 +2,7 @@ * Copyright (C) 2004-2015 Savoir-faire Linux Inc. * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,20 +45,11 @@ #endif struct pjsip_inv_session; +struct pjsip_rx_data; +struct pjsip_msg; namespace ring { namespace InstantMessaging { -/* PJSIP's sip message limit, PJSIP_MAX_PKT_LEN is the total, but most - is used for the SIP header - - Ring currently split the messages into smaller ones and send them. Obviously - it is invalid as some messages wont have the MIME boundary section. - - The number set here is arbitrary, the theoretical limit is around 3000, - but some messages may fail. -*/ -constexpr static unsigned MAXIMUM_MESSAGE_LENGTH = 1800; - constexpr static const char* IM_XML_URI = "uri"; struct InstantMessageException : std::runtime_error @@ -69,8 +61,36 @@ struct InstantMessageException : std::runtime_error using UriEntry = std::map<std::string, std::string>; using UriList = std::list<UriEntry>; -void sendSipMessage(pjsip_inv_session* session, const std::string& id, - const std::vector<std::string>& chunks); +/** + * Constructs and sends a SIP message. + * + * The expected format of the map key is: + * type/subtype[; *[; arg=value]] + * eg: "text/plain; id=1234;part=2;of=1001" + * note: all whitespace is optional + * + * If the map contains more than one pair, then a multipart/mixed message type will be created + * containing multiple message parts. Note that all of the message parts must be able to fit into + * one message... they will not be split into multiple messages. + * + * @param session SIP session + * @param payloads a map where the mime type and optional parameters are the key + * and the message payload is the value + * + * Exception: throw InstantMessageException if no message sent + */ +void sendSipMessage(pjsip_inv_session* session, const std::map<std::string, std::string>& payloads); + +/** + * Parses given SIP message into a map where the key is the contents of the Content-Type header + * (along with any parameters) and the value is the message payload. + * + * @param msg received SIP message + * + * @return map of content types and message payloads + */ +std::map<std::string, std::string> parseSipMessage(const pjsip_msg* msg); + #if HAVE_IAX void sendIaxMessage(iax_session* session, const std::string& id, const std::vector<std::string>& chunks); @@ -93,45 +113,6 @@ std::string generateXmlUriList(const UriList& list); * * @return An UriList of UriEntry containing parsed XML information as a map. */ -UriList parseXmlUriList(const std::string &urilist); - -/** - * Format text message according to RFC 5365, append recipient-list to the message - * - * @param Key/Value MIME pairs to be sent. If a payload doesn't fit, the message will be split - * @param list containing the recipients - * - * @return formated text stored into a string to be included in sip MESSAGE - */ -std::vector<std::string> appendMimePayloads(const std::map<std::string, std::string>& payloads, - const UriList& list = {}); - -/** - * Retreive the xml formated uri list in formated text data according to RFC 5365 - * - * @param text The formated text message as retreived in the SIP message - * - * @return A string containing the XML content - */ -std::string findTextUriList(const std::string& text); - -/** - * Retrieve a MIME payload from the SIP container RFC5365 - * - * @param mime the mime type - * - * @param encodedPayloads a MIME encoded set of payloads - * - * @return A string containing the actual message - */ -std::string findMimePayload(const std::string& encodedPayloads, - const std::string& mime = "text/plain"); - -/** - * Retrieve all MIME payloads from encodedPayloads - * - * @param encodedPayloads a MIME encoded set of payloads - */ -std::map< std::string, std::string > parsePayloads(const std::string& encodedPayloads); +UriList parseXmlUriList(const std::string& urilist); }} // namespace ring::InstantMessaging diff --git a/src/manager.cpp b/src/manager.cpp index 7f75ae71d2aacf6b0783168a5a09252abdf885fc..f1b92c3b42c3ca6cb8f6eea6af303f5a671ae13e 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1607,101 +1607,86 @@ Manager::incomingCall(Call &call, const std::string& accountId) //THREAD=VoIP #if HAVE_INSTANT_MESSAGING +void +Manager::sendTextMessageToConference(const Conference& conf, + const std::map<std::string, std::string>& messages, + const std::string& from) const noexcept +{ + ParticipantSet participants(conf.getParticipantList()); + for (const auto& call_id: participants) { + try { + auto call = getCallFromCallID(call_id); + if (not call) + throw std::runtime_error("no associated call"); + call->sendTextMessage(messages, from); + } catch (const std::exception& e) { + RING_ERR("Failed to send message to conference participant %s: %s", + call_id.c_str(), e.what()); + } + } +} void Manager::incomingMessage(const std::string& callID, - const std::string& from, - const std::map<std::string, std::string>& messages) + const std::string& from, + const std::map<std::string, std::string>& messages) { if (isConferenceParticipant(callID)) { auto conf = getConferenceFromCallID(callID); - - ParticipantSet participants(conf->getParticipantList()); - - for (const auto &item_p : participants) { - - if (item_p == callID) - continue; - - RING_DBG("Send message to %s", item_p.c_str()); - - if (auto call = getCallFromCallID(item_p)) { - call->sendTextMessage(messages, from); - } else { - RING_ERR("Failed to get call while sending instant message"); - return; - } + if (not conf) { + RING_ERR("no conference associated to ID %s", callID.c_str()); + return; } + RING_DBG("Is a conference, send incoming message to everyone"); + sendTextMessageToConference(*conf, messages, from); + // in case of a conference we must notify client using conference id emitSignal<DRing::CallSignal::IncomingMessage>(conf->getConfID(), from, messages); } else emitSignal<DRing::CallSignal::IncomingMessage>(callID, from, messages); } -//THREAD=VoIP -bool +void Manager::sendCallTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from, bool /*isMixed TODO: use it */) { if (isConference(callID)) { - RING_DBG("Is a conference, send instant message to everyone"); - ConferenceMap::iterator it = conferenceMap_.find(callID); - - if (it == conferenceMap_.end()) - return false; - - auto conf = it->second; - - if (!conf) - return false; - - ParticipantSet participants(conf->getParticipantList()); - - for (const auto &participant_id : participants) { - - if (auto call = getCallFromCallID(participant_id)) { - call->sendTextMessage(messages, from); - } else { - RING_ERR("Failed to get call while sending instant message"); - return false; - } + const auto& it = conferenceMap_.find(callID); + if (it == conferenceMap_.cend() or not it->second) { + RING_ERR("no conference associated to ID %s", callID.c_str()); + return; } - return true; - } + RING_DBG("Is a conference, send instant message to everyone"); + sendTextMessageToConference(*it->second, messages, from); - if (isConferenceParticipant(callID)) { - RING_DBG("Call is participant in a conference, send instant message to everyone"); + } else if (isConferenceParticipant(callID)) { auto conf = getConferenceFromCallID(callID); + if (not conf) { + RING_ERR("no conference associated to call ID %s", callID.c_str()); + return; + } - if (!conf) - return false; - - ParticipantSet participants(conf->getParticipantList()); - - for (const auto &participant_id : participants) { + RING_DBG("Call is participant in a conference, send instant message to everyone"); + sendTextMessageToConference(*conf, messages, from); - if (auto call = getCallFromCallID(participant_id)) { - call->sendTextMessage(messages, from); - } else { - RING_ERR("Failed to get call while sending instant message"); - return false; - } - } } else { - if (auto call = getCallFromCallID(callID)) { + auto call = getCallFromCallID(callID); + if (not call) { + RING_ERR("Failed to send message to %s: inexistant call ID", call->getCallId().c_str()); + return; + } + + try { call->sendTextMessage(messages, from); - } else { - RING_ERR("Failed to get call while sending instant message"); - return false; + } catch (const InstantMessaging::InstantMessageException& e) { + RING_ERR("Failed to send message to call %s: %s", call->getCallId().c_str(), e.what()); } } - return true; } - #endif // HAVE_INSTANT_MESSAGING //THREAD=VoIP CALL=Outgoing diff --git a/src/manager.h b/src/manager.h index 355aaca4a4805bf6f14a9b26b9b72e02b4317b3c..1cf2257c35a5f8407f3fe99d5b2245f07b19c1f3 100644 --- a/src/manager.h +++ b/src/manager.h @@ -399,9 +399,11 @@ class Manager { * Send a new text message to the call, if participate to a conference, send to all participant. * @param callID The call to send the message * @param message A list of pair of mime types and payloads - * @param from The sender of this message (could be another participant of a conference) + * @param from The sender of this message (could be another participant of a conference) */ - bool sendCallTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from, bool isMixed); + void sendCallTextMessage(const std::string& callID, + const std::map<std::string, std::string>& messages, + const std::string& from, bool isMixed); #endif // HAVE_INSTANT_MESSAGING /** @@ -1008,6 +1010,12 @@ class Manager { /* Sink ID mapping */ std::map<std::string, std::weak_ptr<video::SinkClient>> sinkMap_; + +#if HAVE_INSTANT_MESSAGING + void sendTextMessageToConference(const Conference& conf, + const std::map<std::string, std::string>& messages, + const std::string& from) const noexcept; +#endif }; // Helper to install a callback to be called once by the main event loop diff --git a/src/sip/sip_utils.cpp b/src/sip/sip_utils.cpp index c37674050f050665be547dc5b77fe799b8e8a2b3..0710501f4baa5399a4bcf7ba4add7d386e87157f 100644 --- a/src/sip/sip_utils.cpp +++ b/src/sip/sip_utils.cpp @@ -190,4 +190,15 @@ sip_strerror(pj_status_t code) return std::string{err_msg}; } +PJDialogLock::PJDialogLock(pjsip_dialog* dialog) + : dialog_(dialog) +{ + pjsip_dlg_inc_lock(dialog_); +} + +PJDialogLock::~PJDialogLock() +{ + pjsip_dlg_dec_lock(dialog_); +} + }} // namespace ring::sip_utils diff --git a/src/sip/sip_utils.h b/src/sip/sip_utils.h index 7f2aa28838c9dabdd64551d7b931b4aca4bbe8f5..1364f76ba1f11d7eaf15dc857bd5bbca94504f4f 100644 --- a/src/sip/sip_utils.h +++ b/src/sip/sip_utils.h @@ -45,6 +45,7 @@ #include <cstring> // strcmp struct pjsip_msg; +struct pjsip_dialog; namespace ring { namespace sip_utils { @@ -91,6 +92,20 @@ constexpr const pj_str_t CONST_PJ_STR(T (&a)[N]) noexcept { return {const_cast<char*>(a), N-1}; } +// PJSIP dialog locking in RAII way +// Usage: declare local variable like this: sip_utils::PJDialogLock lock {dialog}; +// The lock is kept until the local variable is deleted +class PJDialogLock { +public: + explicit PJDialogLock(pjsip_dialog* dialog); + ~PJDialogLock(); + PJDialogLock() = delete; + PJDialogLock(const PJDialogLock&) = delete; // enough to disable all cp/mv stuff + +private: + pjsip_dialog* dialog_; +}; + }} // namespace ring::sip_utils #endif // SIP_UTILS_H_ diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index b6ee5644690b97324b159d38132daa0d67ae622d..83c4021f00db113e560880507fd1507f43c0edd0 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -682,18 +682,16 @@ SIPCall::carryingDTMFdigits(char code) #if HAVE_INSTANT_MESSAGING void SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages, - const std::string& from) + const std::string& /* from */) { if (not inv) throw VoipLinkException("No invite session for this call"); - /* Send IM message */ - InstantMessaging::UriList list; - InstantMessaging::UriEntry entry; - entry[InstantMessaging::IM_XML_URI] = std::string("\"" + from + "\""); // add double quotes for xml formating - list.push_front(entry); - const auto& msgs = InstantMessaging::appendMimePayloads(messages, list); - InstantMessaging::sendSipMessage(inv.get(), getCallId(), msgs); + //TODO: for now we ignore the "from" (the previous implementation for sending this info was + // buggy and verbose), another way to send the original message sender will be implemented + // in the future + + InstantMessaging::sendSipMessage(inv.get(), messages); } #endif // HAVE_INSTANT_MESSAGING diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index fa805a2af4a9be788f72c0e3c496026675979dca..82656196322e27945a17b7a1181deb1d0dbb5fd5 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -168,7 +168,7 @@ class SIPCall : public Call #if HAVE_INSTANT_MESSAGING void sendTextMessage(const std::map<std::string, std::string>& messages, - const std::string &from) override; + const std::string& from) override; #endif SIPAccountBase& getSIPAccount() const; diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp index 81579ef9afcf8072c98f2e24498bb2de0be83f55..c19dc2695b2a8e6c250bc9e40550134e84002e7d 100644 --- a/src/sip/sipvoiplink.cpp +++ b/src/sip/sipvoiplink.cpp @@ -1117,48 +1117,19 @@ onRequestNotify(pjsip_inv_session* /*inv*/, pjsip_rx_data* /*rdata*/, pjsip_msg* } static void -onRequestMessage(pjsip_inv_session* inv, pjsip_rx_data* rdata, pjsip_msg* msg, SIPCall& call) +onRequestMessage(pjsip_inv_session* /*inv*/, pjsip_rx_data* /*rdata*/, pjsip_msg* msg, + SIPCall& call) { #if HAVE_INSTANT_MESSAGING if (!msg->body) return; - const auto formattedMsgPtr = static_cast<const char*>(msg->body->data); - if (!formattedMsgPtr) - return; - - const std::string formattedMessage {formattedMsgPtr}; - - // retreive the recipient-list of this message - InstantMessaging::UriList list; - try { - const auto& urilist = InstantMessaging::findTextUriList(formattedMessage); - auto list = InstantMessaging::parseXmlUriList(urilist); - } catch (const InstantMessaging::InstantMessageException& e) { - RING_DBG("[call:%s] Empty urilist", call.getCallId().c_str()); - } - - // If no item present in the list, peer is considered as the sender - std::string from; - if (list.empty()) { - from = call.getPeerNumber(); - } else { - from = list.front()[InstantMessaging::IM_XML_URI]; - if (from == "Me") - from = call.getPeerNumber(); - } - - // strip < and > characters in case of an IP address - if (from[0] == '<' and from[from.size() - 1] == '>') - from = from.substr(1, from.size() - 2); + //TODO: for now we assume that the "from" is the message sender, this may not be true in the + // case of conferences; a content type containing this info will be added to the messages + // in the future + Manager::instance().incomingMessage(call.getCallId(), call.getPeerNumber(), + InstantMessaging::parseSipMessage(msg)); - try { - const auto& messages = InstantMessaging::parsePayloads(formattedMessage); - Manager::instance().incomingMessage(call.getCallId(), from, messages); - replyToRequest(inv, rdata, PJSIP_SC_OK); - } catch (const InstantMessaging::InstantMessageException& except) { - RING_ERR("Exception during SIP message parsing: %s", except.what()); - } #endif // HAVE_INSTANT_MESSAGING }