From b7ee00ee777d0a6b1862e1d6a648b68ea6707cab Mon Sep 17 00:00:00 2001
From: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
Date: Fri, 11 Sep 2015 15:35:46 -0400
Subject: [PATCH] im: use pjsip api to create and parse messages

This supports both simple and multipart content types.

*WARNING* the daemon API has not changed; however the previous
implementation was not RFC compliant and thus messages sent from or
received by a previous version of the daemon will not be interpreted
correctly. Additionally the behaviour of the API has slightly changed.
It will no longer split multiple message pairs received in the
map<string, string> into multiple, separate SIP messages if they do not
all fit into one. If there is only one pair in the map, then a message
with a single content-type will be created and sent. If multiple pairs
exist, then a multipart/mixed type message will be created. If the
created message is too large to be sent, then no parts will be sent.

Support for storing the URI of the original sender is dropped for now as it
was not being used by the clients and the implementation was buggy.

The APIs for creating XML recipient lists as defined by RFC 5365 remains
but is no longer used for now.

Issue: #79657
Change-Id: I2b00cbd797fbb423ee0a7eb24748d2362e9f9ff8
Signed-off-by: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
---
 src/iax/iaxcall.cpp          |   8 +-
 src/im/instant_messaging.cpp | 448 ++++++++++++++++++-----------------
 src/im/instant_messaging.h   |  87 +++----
 src/manager.cpp              | 113 ++++-----
 src/manager.h                |  12 +-
 src/sip/sip_utils.cpp        |  11 +
 src/sip/sip_utils.h          |  15 ++
 src/sip/sipcall.cpp          |  14 +-
 src/sip/sipcall.h            |   2 +-
 src/sip/sipvoiplink.cpp      |  43 +---
 10 files changed, 371 insertions(+), 382 deletions(-)

diff --git a/src/iax/iaxcall.cpp b/src/iax/iaxcall.cpp
index 5397a7fa59..5cd366f869 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 9656b91e87..f5d3829547 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 43b62d7598..407e24d63b 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 7f75ae71d2..f1b92c3b42 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 355aaca4a4..1cf2257c35 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 c37674050f..0710501f4b 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 7f2aa28838..1364f76ba1 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 b6ee564469..83c4021f00 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 fa805a2af4..8265619632 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 81579ef9af..c19dc2695b 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
 }
 
-- 
GitLab