Commit b7ee00ee authored by Stepan Salenikovich's avatar Stepan Salenikovich Committed by Guillaume Roguez

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's avatarGuillaume Roguez <guillaume.roguez@savoirfairelinux.com>
parent 6f868d66
......@@ -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
......
This diff is collapsed.
......@@ -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
......@@ -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
......
......@@ -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
......
......@@ -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
......@@ -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_
......@@ -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
......
......@@ -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;
......
......@@ -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
}
......
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