Commit b45c6d9a authored by Emmanuel Lepage Vallee's avatar Emmanuel Lepage Vallee Committed by Guillaume Roguez

im: Improve message splitting

Handle more corner cases to more messages can be obtained and
parsed by Ring. This add a "isMixed" agument to the API
to tell if the message parts are related (true by default)
or not.
Do also some code cleanup on affected code.

WARNING: Break API

Refs #77651

Change-Id: I8f5d88f87b2bd5c66963047c7ced29d69498b668
Signed-off-by: Guillaume Roguez's avatarGuillaume Roguez <guillaume.roguez@savoirfairelinux.com>
parent 135a5a82
......@@ -447,6 +447,7 @@
<arg type="s" name="callID" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="MapStringString"/>
<arg type="a{ss}" name="message" direction="in"/>
<arg type="b" name="isMixed" direction="in"/>
</method>
<signal name="newCallCreated" tp:name-for-bindings="newCallCreated">
......
......@@ -289,7 +289,7 @@ DBusCallManager::acceptEnrollment(const std::string& callID, const bool& accepte
}
void
DBusCallManager::sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages)
DBusCallManager::sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const bool& isMixed)
{
DRing::sendTextMessage(callID, messages, "Me");
DRing::sendTextMessage(callID, messages, "Me", isMixed);
}
......@@ -106,7 +106,7 @@ class DBusCallManager :
void setConfirmGoClear(const std::string& callID);
void requestGoClear(const std::string& callID);
void acceptEnrollment(const std::string& callID, const bool& accepted);
void sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages);
void sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const bool& isMixed);
};
#endif // __RING_CALLMANAGER_H__
......@@ -332,10 +332,10 @@ acceptEnrollment(const std::string& /*callID*/, bool /*accepted*/)
}
void
sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from)
sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from, bool isMixed)
{
#if HAVE_INSTANT_MESSAGING
ring::Manager::instance().sendCallTextMessage(callID, messages, from);
ring::Manager::instance().sendCallTextMessage(callID, messages, from, isMixed);
#endif
}
......
......@@ -102,7 +102,7 @@ void requestGoClear(const std::string& callID);
void acceptEnrollment(const std::string& callID, bool accepted);
/* Instant messaging */
void sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from);
void sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from, bool isMixed);
// Call signal type definitions
struct CallSignal {
......
......@@ -240,13 +240,13 @@ IAXCall::carryingDTMFdigits(char code)
}
#if HAVE_INSTANT_MESSAGING
void IAXCall::sendTextMessage(const std::map<std::string, std::string>& messages,
const std::string &from)
void
IAXCall::sendTextMessage(const std::map<std::string, std::string>& messages,
const std::string& /*from*/)
{
std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX);
for (const auto &message : messages)
InstantMessaging::send_iax_message(session, getCallId(), message.second.c_str());
const auto& msgs = InstantMessaging::appendMimePayloads(messages);
InstantMessaging::sendIaxMessage(session, getCallId(), msgs);
}
#endif
......
......@@ -32,12 +32,14 @@
#include "instant_messaging.h"
#include "logger.h"
#include "sip/sip_utils.h"
#include <expat.h>
namespace ring {
static void XMLCALL
startElementCallback(void *userData, const char *name, const char **atts)
startElementCallback(void* userData, const char* name, const char** atts)
{
if (strcmp(name, "entry"))
return;
......@@ -51,10 +53,12 @@ startElementCallback(void *userData, const char *name, const char **atts)
}
static void XMLCALL
endElementCallback(void * /*userData*/, const char * /*name*/)
endElementCallback(void * /*userData*/, const char* /*name*/)
{}
bool InstantMessaging::saveMessage(const std::string &message, const std::string &author, const std::string &id, int mode)
bool
InstantMessaging::saveMessage(const std::string& message, const std::string& author,
const std::string& id, int mode)
{
std::ofstream File;
std::string filename = "im_" + id;
......@@ -69,82 +73,69 @@ bool InstantMessaging::saveMessage(const std::string &message, const std::string
return true;
}
void InstantMessaging::sip_send(pjsip_inv_session *session, const std::string& id, const std::string& text)
void
InstantMessaging::sendSipMessage(pjsip_inv_session* session, const std::string& id,
const std::vector<std::string>& chunks)
{
pjsip_tx_data *tdata;
pjsip_dialog* dialog = session->dlg;
for (const auto& text: chunks) {
const pjsip_method msg_method = { PJSIP_OTHER_METHOD, pj_str((char*)"MESSAGE") };
pjsip_tx_data* tdata;
pjsip_dlg_inc_lock(dialog);
auto dialog = session->dlg;
pjsip_dlg_inc_lock(dialog);
pjsip_method msg_method = { PJSIP_OTHER_METHOD, pj_str((char*)"MESSAGE") };
if (pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata) != PJ_SUCCESS) {
pjsip_dlg_dec_lock(dialog);
return;
}
if (pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata) != PJ_SUCCESS) {
//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);
return;
}
const pj_str_t type = pj_str((char*) "text");
const pj_str_t subtype = pj_str((char*) "plain");
pj_str_t message = pj_str((char*) text.c_str());
tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &message);
pjsip_dlg_send_request(dialog, tdata, -1, NULL);
pjsip_dlg_dec_lock(dialog);
saveMessage(text, "Me", id);
}
void InstantMessaging::send_sip_message(pjsip_inv_session *session, const std::string &id, const std::string &message)
{
std::vector<std::string> msgs(split_message(message));
for (const auto &item : msgs)
sip_send(session, id, item);
saveMessage(text, "Me", id);
}
}
#if HAVE_IAX
void InstantMessaging::send_iax_message(iax_session *session, const std::string &/* id */, const std::string &message)
void
InstantMessaging::sendIaxMessage(iax_session* session, const std::string& /* id */,
const std::vector<std::string>& chunks)
{
std::vector<std::string> msgs(split_message(message));
for (const auto &item : msgs)
iax_send_text(session, item.c_str());
for (const auto& msg: chunks)
iax_send_text(session, msg.c_str());
}
#endif
std::vector<std::string> InstantMessaging::split_message(std::string text)
{
std::vector<std::string> messages;
size_t len = MAXIMUM_MESSAGE_LENGTH;
while (text.length() > len - 2) {
messages.push_back(text.substr(0, len - 2) + "\n\0");
text = text.substr(len - 2);
}
messages.push_back(text);
return messages;
}
std::string InstantMessaging::generateXmlUriList(UriList &list)
std::string
InstantMessaging::generateXmlUriList(const UriList& list)
{
std::string xmlbuffer = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:cp=\"urn:ietf:params:xml:ns:copycontrol\">"
"<list>";
for (auto &item : list)
xmlbuffer += "<entry uri=" + item[IM_XML_URI] + " cp:copyControl=\"to\" />";
"<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:cp=\"urn:ietf:params:xml:ns:copycontrol\">"
"<list>";
for (const auto& item: list) {
const auto it = item.find(IM_XML_URI);
if (it == item.cend())
continue;
xmlbuffer += "<entry uri=" + it->second + " cp:copyControl=\"to\" />";
}
return xmlbuffer + "</list></resource-lists>";
}
InstantMessaging::UriList
InstantMessaging::parseXmlUriList(const std::string &urilist)
InstantMessaging::parseXmlUriList(const std::string& urilist)
{
InstantMessaging::UriList list;
......@@ -162,7 +153,9 @@ InstantMessaging::parseXmlUriList(const std::string &urilist)
}
///See rfc2046#section-5.1.4
static std::string buildMimeMultipartPart(const std::string &type, const std::string &dispo, const std::string &content)
static std::string
buildMimeMultipartPart(const std::string& type, const std::string& dispo,
const std::string& content)
{
return
"--boundary\n"
......@@ -172,22 +165,46 @@ static std::string buildMimeMultipartPart(const std::string &type, const std::st
content + "\n";
}
std::string InstantMessaging::appendMimePayloads(const std::map<std::string,std::string> payloads, UriList& list)
std::vector<std::string>
InstantMessaging::appendMimePayloads(const std::map<std::string, std::string>& payloads,
const UriList& list)
{
std::string ret;
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) {
ret += buildMimeMultipartPart(pair.first, {}, pair.second);
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 %d",
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 %d, the payload would be %d %d %d",
max_message_size, m.size() + chunk.size(), m.size() , chunk.size()
);
chunk += urilist + footer;
ret.push_back(chunk);
chunk = "";
}
chunk += m;
}
if (!list.empty())
ret += buildMimeMultipartPart("application/resource-lists+xml", "recipient-list", generateXmlUriList(list));
if (chunk.size())
ret.push_back(chunk);
ret += "--boundary--";
return ret;
}
std::string InstantMessaging::findTextUriList(const std::string &text)
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");
......@@ -244,7 +261,8 @@ std::string InstantMessaging::findTextUriList(const std::string &text)
* --boundary42--
*/
std::string InstantMessaging::findMimePayload(const std::string &encodedPayloads, const std::string &mime)
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);
......@@ -261,7 +279,8 @@ std::string InstantMessaging::findMimePayload(const std::string &encodedPayloads
return encodedPayloads.substr(begin, end - begin);
}
std::map< std::string, std::string > InstantMessaging::parsePayloads(const std::string &encodedPayloads)
std::map<std::string, std::string>
InstantMessaging::parsePayloads(const std::string& encodedPayloads)
{
//Constants
static const std::string boud = "--boundary" ;
......
......@@ -51,25 +51,33 @@
#include <iax/iax-client.h>
#endif
#define EMPTY_MESSAGE pj_str((char*)"")
#define MAXIMUM_MESSAGE_LENGTH 1560 /* PJSIP's sip message limit */
#define MODE_APPEND std::ios::out || std::ios::app
#define MODE_TEST std::ios::out
#define MODE_APPEND std::ios::out || std::ios::app
#define MODE_TEST std::ios::out
namespace ring { namespace InstantMessaging {
const std::string IM_XML_URI("uri");
const std::string BOUNDARY("--boundary");
/* 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;
class InstantMessageException : public std::runtime_error {
public:
InstantMessageException(const std::string& str="") :
std::runtime_error("InstantMessageException occured: " + str) {}
constexpr static const char* IM_XML_URI = "uri";
constexpr static const char* BOUNDARY = "--boundary";
struct InstantMessageException : std::runtime_error
{
InstantMessageException(const std::string& str="") :
std::runtime_error("InstantMessageException occured: " + str) {}
};
typedef std::map<std::string, std::string> UriEntry;
typedef std::list<UriEntry> UriList;
using UriEntry = std::map<std::string, std::string>;
using UriList = std::list<UriEntry>;
/*
* Write the text message to the right file
......@@ -79,23 +87,16 @@ typedef std::list<UriEntry> UriList;
* @param id The current call
* @return True if the message could have been successfully saved, False otherwise
*/
bool saveMessage(const std::string& message, const std::string& author, const std::string& id, int mode = MODE_APPEND);
bool saveMessage(const std::string& message, const std::string& author, const std::string& id,
int mode = MODE_APPEND);
/*
* Send a SIP string message inside a call
*
* @param id The call ID we will retrieve the invite session from
* @param message The string message, as sent by the client
*/
void sip_send(pjsip_inv_session*, const std::string& id, const std::string&);
void send_sip_message(pjsip_inv_session*, const std::string& id, const std::string&);
void sendSipMessage(pjsip_inv_session* session, const std::string& id,
const std::vector<std::string>& chunks);
#if HAVE_IAX
void send_iax_message(iax_session *session, const std::string& id, const std::string&);
void sendIaxMessage(iax_session* session, const std::string& id,
const std::vector<std::string>& chunks);
#endif
std::vector<std::string> split_message(std::string);
/**
* Generate Xml participant list for multi recipient based on RFC Draft 5365
*
......@@ -104,7 +105,7 @@ std::vector<std::string> split_message(std::string);
* @return A string containing the full XML formated information to be included in the
* sip instant message.
*/
std::string generateXmlUriList(UriList &list);
std::string generateXmlUriList(const UriList& list);
/**
* Parse the Urilist from a SIP Instant Message provided by a UriList service.
......@@ -118,12 +119,13 @@ UriList parseXmlUriList(const std::string &urilist);
/**
* Format text message according to RFC 5365, append recipient-list to the message
*
* @param text to be displayed
* @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::string appendMimePayloads(const std::map<std::string,std::string> payloads, UriList& list);
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
......@@ -132,25 +134,26 @@ std::string appendMimePayloads(const std::map<std::string,std::string> payloads,
*
* @return A string containing the XML content
*/
std::string findTextUriList(const std::string &text);
std::string findTextUriList(const std::string& text);
/**
* Retrieve a MIME payload from the SIP container RFC5365
*
* @param mime the mime type
* @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");
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);
std::map< std::string, std::string > parsePayloads(const std::string& encodedPayloads);
}} // namespace ring::InstantMessaging
......
......@@ -1650,8 +1650,9 @@ Manager::incomingMessage(const std::string& callID,
//THREAD=VoIP
bool
Manager::sendCallTextMessage(const std::string& callID,
const std::map<std::string, std::string>& messages,
const std::string& from)
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");
......
......@@ -401,7 +401,7 @@ class Manager {
* @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)
*/
bool sendCallTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from);
bool sendCallTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from, bool isMixed);
#endif // HAVE_INSTANT_MESSAGING
/**
......
......@@ -686,9 +686,9 @@ SIPCall::carryingDTMFdigits(char code)
}
#if HAVE_INSTANT_MESSAGING
void SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages,
const std::string &from)
void
SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages,
const std::string& from)
{
if (not inv)
throw VoipLinkException("No invite session for this call");
......@@ -698,8 +698,8 @@ void SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages
InstantMessaging::UriEntry entry;
entry[InstantMessaging::IM_XML_URI] = std::string("\"" + from + "\""); // add double quotes for xml formating
list.push_front(entry);
auto msg = InstantMessaging::appendMimePayloads(messages, list);
InstantMessaging::send_sip_message(inv.get(), getCallId(), msg);
const auto& msgs = InstantMessaging::appendMimePayloads(messages, list);
InstantMessaging::sendSipMessage(inv.get(), getCallId(), msgs);
}
#endif // HAVE_INSTANT_MESSAGING
......
......@@ -1115,25 +1115,30 @@ onRequestMessage(pjsip_inv_session* inv, pjsip_rx_data* rdata, pjsip_msg* msg, S
const std::string formattedMessage {formattedMsgPtr};
// retreive the recipient-list of this message
InstantMessaging::UriList list;
try {
// retreive the recipient-list of this message
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()) {
// 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();
} 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);
// strip < and > characters in case of an IP address
if (from[0] == '<' and from[from.size() - 1] == '>')
from = from.substr(1, from.size() - 2);
try {
const auto& messages = InstantMessaging::parsePayloads(formattedMessage);
Manager::instance().incomingMessage(call.getCallId(), from, messages);
replyToRequest(inv, rdata, PJSIP_SC_OK);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment