Commit 27565853 authored by Guillaume Roguez's avatar Guillaume Roguez

im: normalize call/account instant-messaging API's

WARNING: API changes

This patch makes code common for IM send/receive message API's
between call and account classes.

Multi-part messages is supported for :
- SIPAccount
- SIPCall (any SIP like account)

RingAccount supports only Single-part messages yet.

Change-Id: Ic65425cd95f02f89f03dd6ea2a9c4a10bb233859
Tuleap: #157
parent 87f3f337
......@@ -446,7 +446,7 @@
</tp:docstring>
<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="a{ss}" name="payloads" direction="in"/>
<arg type="b" name="isMixed" direction="in"/>
</method>
......
......@@ -254,12 +254,10 @@
</method>
<method name="sendTextMessage" tp:name-for-bindings="sendTextMessage">
<arg type="s" name="accountID" direction="in">
</arg>
<arg type="s" name="to" direction="in">
</arg>
<arg type="s" name="message" direction="in">
</arg>
<arg type="s" name="accountID" direction="in"/>
<arg type="s" name="to" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="MapStringString"/>
<arg type="a{ss}" name="payloads" direction="in"/>
</method>
<signal name="incomingAccountMessage" tp:name-for-bindings="incomingAccountMessage">
......@@ -267,12 +265,10 @@
<tp:docstring>
Notify clients that a new text message has been received at the account level.
</tp:docstring>
<arg type="s" name="accountID">
</arg>
<arg type="s" name="from">
</arg>
<arg type="s" name="message">
</arg>
<arg type="s" name="accountID"/>
<arg type="s" name="from"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="MapStringString"/>
<arg type="a{ss}" name="payloads"/>
</signal>
<method name="setVolume" tp:name-for-bindings="setVolume">
......
......@@ -92,9 +92,9 @@ DBusConfigurationManager::registerAllAccounts(void)
}
void
DBusConfigurationManager::sendTextMessage(const std::string& accountID, const std::string& to, const std::string& message)
DBusConfigurationManager::sendTextMessage(const std::string& accountID, const std::string& to, const std::map<std::string, std::string>& payloads)
{
DRing::sendAccountTextMessage(accountID, to, message);
DRing::sendAccountTextMessage(accountID, to, payloads);
}
auto
......
......@@ -66,7 +66,7 @@ class DBusConfigurationManager :
std::vector<std::string> getAccountList();
void sendRegister(const std::string& accoundID, const bool& enable);
void registerAllAccounts(void);
void sendTextMessage(const std::string& accoundID, const std::string& to, const std::string& message);
void sendTextMessage(const std::string& accoundID, const std::string& to, const std::map<std::string, std::string>& payloads);
std::map<std::string, std::string> getTlsDefaultSettings();
std::vector<std::string> getSupportedCiphers(const std::string& accountID);
std::vector<unsigned> getCodecList();
......
......@@ -33,6 +33,7 @@
#include "ip_utils.h"
#include "media_codec.h"
#include "logger.h"
#include "intrin.h" // UNUSED
#include <functional>
#include <string>
......@@ -147,7 +148,8 @@ class Account : public Serializable, public std::enable_shared_from_this<Account
/**
* If supported, send a text message from this account.
*/
virtual void sendTextMessage(const std::string& /* to */, const std::string& /* message */) {};
virtual void sendTextMessage(const std::string& to UNUSED,
const std::map<std::string, std::string>& payloads UNUSED) {};
std::vector<std::shared_ptr<Call>> getCalls();
......
......@@ -287,9 +287,9 @@ registerAllAccounts()
}
void
sendAccountTextMessage(const std::string& accountID, const std::string& to, const std::string& message)
sendAccountTextMessage(const std::string& accountID, const std::string& to, const std::map<std::string, std::string>& payloads)
{
ring::Manager::instance().sendTextMessage(accountID, to, message);
ring::Manager::instance().sendTextMessage(accountID, to, payloads);
}
/* contact requests */
......
......@@ -49,7 +49,7 @@ void setAccountEnabled(const std::string& accountID, bool enable);
std::vector<std::string> getAccountList();
void sendRegister(const std::string& accountID, bool enable);
void registerAllAccounts(void);
void sendAccountTextMessage(const std::string& accountID, const std::string& to, const std::string& message);
void sendAccountTextMessage(const std::string& accountID, const std::string& to, const std::map<std::string, std::string>& payloads);
std::map<std::string, std::string> getTlsDefaultSettings();
......@@ -179,7 +179,7 @@ struct ConfigurationSignal {
};
struct IncomingAccountMessage {
constexpr static const char* name = "IncomingAccountMessage";
using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, const std::string& /*message*/);
using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, const std::map<std::string, std::string>& /*payloads*/);
};
struct IncomingTrustRequest {
constexpr static const char* name = "IncomingTrustRequest";
......
......@@ -30,6 +30,8 @@
namespace ring {
using sip_utils::CONST_PJ_STR;
/**
* 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"
......@@ -115,6 +117,42 @@ createMessageBody(pj_pool_t* pool, const std::pair<std::string, std::string>& pa
} while (std::string::npos != sep);
}
void
InstantMessaging::fillPJSIPMessageBody(pjsip_tx_data& tdata,
const std::map<std::string, std::string>& payloads)
{
// multi-part body?
if (payloads.size() == 1) {
createMessageBody(tdata.pool, *payloads.begin(), &tdata.msg->body);
return;
}
/* 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: not enough memory");
throw InstantMessageException("Internal SIP error");
}
createMessageBody(tdata.pool, pair, &part->body);
auto 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");
}
}
}
void
InstantMessaging::sendSipMessage(pjsip_inv_session* session,
const std::map<std::string, std::string>& payloads)
......@@ -124,7 +162,7 @@ InstantMessaging::sendSipMessage(pjsip_inv_session* session,
return;
}
const pjsip_method msg_method = {PJSIP_OTHER_METHOD, {const_cast<char*>("MESSAGE"), 7}};
const pjsip_method msg_method = {PJSIP_OTHER_METHOD, CONST_PJ_STR("MESSAGE")};
{
auto dialog = session->dlg;
......@@ -138,36 +176,7 @@ InstantMessaging::sendSipMessage(pjsip_inv_session* session,
throw InstantMessageException("Internal SIP error");
}
// 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);
}
fillPJSIPMessageBody(*tdata, payloads);
status = pjsip_dlg_send_request(dialog, tdata, -1, nullptr);
if (status != PJ_SUCCESS) {
......
......@@ -37,6 +37,7 @@
struct pjsip_inv_session;
struct pjsip_rx_data;
struct pjsip_msg;
struct pjsip_tx_data;
namespace ring { namespace InstantMessaging {
......@@ -81,4 +82,6 @@ void sendIaxMessage(iax_session* session, const std::string& id,
const std::vector<std::string>& chunks);
#endif
void fillPJSIPMessageBody(pjsip_tx_data& tdata, const std::map<std::string, std::string>& payloads);
}} // namespace ring::InstantMessaging
......@@ -2701,12 +2701,13 @@ Manager::sendRegister(const std::string& accountID, bool enable)
}
void
Manager::sendTextMessage(const std::string& accountID, const std::string& to, const std::string& message)
Manager::sendTextMessage(const std::string& accountID, const std::string& to,
const std::map<std::string, std::string>& payloads)
{
const auto acc = getAccount(accountID);
if (!acc)
return;
acc->sendTextMessage(to, message);
acc->sendTextMessage(to, payloads);
}
void
......
......@@ -407,12 +407,13 @@ class Manager {
* ConfigurationManager - Send registration request
* @param accountId The account to register/unregister
* @param enable The flag for the type of registration
* false for unregistration request
* true for registration request
* false for unregistration request
* true for registration request
*/
void sendRegister(const std::string& accountId, bool enable);
void sendTextMessage(const std::string& accountID, const std::string& to, const std::string& message);
void sendTextMessage(const std::string& accountID, const std::string& to,
const std::map<std::string, std::string>& payloads);
/**
* Get account list
......
......@@ -894,8 +894,9 @@ RingAccount::doRegister_()
return true;
auto from = v.from.toString();
auto msg = v.msg;
RING_DBG("Text message received from DHT ! %s -> %s", from.c_str(), msg.c_str());
std::map<std::string, std::string> msg = {{"text/plain", v.msg}};
RING_DBG("Text message received from DHT from %s, %zu part(s)",
from.c_str(), msg.size());
emitSignal<DRing::ConfigurationSignal::IncomingAccountMessage>(this_.getAccountID(), from, msg);
return true;
}
......@@ -1317,13 +1318,26 @@ RingAccount::connectivityChanged()
}
void
RingAccount::sendTextMessage(const std::string& to, const std::string& message)
RingAccount::sendTextMessage(const std::string& to,
const std::map<std::string, std::string>& payloads)
{
const std::string& toUri = parseRingUri(to);
if (to.empty() or payloads.empty())
return;
const auto& toUri = parseRingUri(to);
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
dht_.putEncrypted(dht::InfoHash::get("inbox:"+toUri),
dht::InfoHash(toUri),
dht::ImMessage(udist(rand_), std::string(message), now));
// Single-part message
if (payloads.size() == 1) {
dht_.putEncrypted(dht::InfoHash::get("inbox:"+toUri),
dht::InfoHash(toUri),
dht::ImMessage(udist(rand_), std::string(payloads.begin()->second), now));
return;
}
// Multi-part message
// TODO: not supported yet
RING_ERR("Multi-part im is not supported yet by RingAccount");
}
} // namespace ring
......@@ -257,7 +257,7 @@ class RingAccount : public SIPAccountBase {
bool discardTrustRequest(const std::string& from);
void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
virtual void sendTextMessage(const std::string& /* to */, const std::string& /* message */) override;
virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads) override;
void connectivityChanged();
......
......@@ -60,6 +60,8 @@
#include "ip_utils.h"
#include "string_utils.h"
#include "im/instant_messaging.h"
#include <unistd.h>
#include <algorithm>
......@@ -2073,6 +2075,7 @@ void SIPAccount::updateDialogViaSentBy(pjsip_dialog *dlg)
pjsip_dlg_set_via_sent_by(dlg, &via_addr_, via_tp_);
}
#if 0
/**
* Create Accept header for MESSAGE.
*/
......@@ -2088,11 +2091,13 @@ static pjsip_accept_hdr* im_create_accept(pj_pool_t *pool)
return accept;
}
#endif
void
SIPAccount::sendTextMessage(const std::string& to, const std::string& msg)
SIPAccount::sendTextMessage(const std::string& to,
const std::map<std::string, std::string>& payloads)
{
if (to.empty() or msg.empty())
if (to.empty() or payloads.empty())
return;
std::string toUri;
......@@ -2102,7 +2107,7 @@ SIPAccount::sendTextMessage(const std::string& to, const std::string& msg)
else
toUri = getToUri(to);
const pjsip_method msg_method = { PJSIP_OTHER_METHOD, CONST_PJ_STR("MESSAGE") };
const pjsip_method msg_method = {PJSIP_OTHER_METHOD, CONST_PJ_STR("MESSAGE")};
std::string from(getFromUri());
pj_str_t pjFrom = pj_str((char*) from.c_str());
pj_str_t pjTo = pj_str((char*) toUri.c_str());
......@@ -2110,7 +2115,8 @@ SIPAccount::sendTextMessage(const std::string& to, const std::string& msg)
/* Create request. */
pjsip_tx_data *tdata;
pj_status_t status = pjsip_endpt_create_request(link_->getEndpoint(), &msg_method,
&pjTo, &pjFrom, &pjTo, NULL, NULL, -1, NULL, &tdata);
&pjTo, &pjFrom, &pjTo, nullptr, nullptr, -1,
nullptr, &tdata);
if (status != PJ_SUCCESS) {
RING_ERR("Unable to create request: %s", sip_utils::sip_strerror(status).c_str());
return;
......@@ -2119,21 +2125,7 @@ SIPAccount::sendTextMessage(const std::string& to, const std::string& msg)
const pjsip_tpselector tp_sel = getTransportSelector();
pjsip_tx_data_set_transport(tdata, &tp_sel);
/* Add accept header. */
pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)im_create_accept(tdata->pool));
/* Set default media type if none is specified */
static const constexpr pj_str_t type = CONST_PJ_STR("text");
static const constexpr pj_str_t subtype = CONST_PJ_STR("plain");
pj_str_t message = pj_str((char*) msg.c_str());
tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &message);
if (tdata->msg->body == NULL) {
RING_ERR("Unable to create msg body");
pjsip_tx_data_dec_ref(tdata);
return;
}
InstantMessaging::fillPJSIPMessageBody(*tdata, payloads);
status = pjsip_endpt_send_request(link_->getEndpoint(), tdata, -1, nullptr, nullptr);
if (status != PJ_SUCCESS) {
......
......@@ -497,7 +497,8 @@ class SIPAccount : public SIPAccountBase {
void onRegister(pjsip_regc_cbparam *param);
virtual void sendTextMessage(const std::string& /* to */, const std::string& /* message */) override;
virtual void sendTextMessage(const std::string& to,
const std::map<std::string, std::string>& payloads) override;
private:
void doRegister1_();
......
......@@ -336,10 +336,11 @@ SIPAccountBase::getIceOptions() const noexcept
}
void
SIPAccountBase::onTextMessage(const std::string& from, const std::string& msg)
SIPAccountBase::onTextMessage(const std::string& from,
const std::map<std::string, std::string>& payloads)
{
RING_WARN("Text message received ! %s -> %s", from.c_str(), msg.c_str());
emitSignal<DRing::ConfigurationSignal::IncomingAccountMessage>(accountID_, from, msg);
RING_DBG("Text message received from %s, %zu part(s)", from.c_str(), payloads.size());
emitSignal<DRing::ConfigurationSignal::IncomingAccountMessage>(accountID_, from, payloads);
}
void
......
......@@ -233,7 +233,7 @@ public:
const IceTransportOptions getIceOptions() const noexcept override;
void onTextMessage(const std::string& from, const std::string& msg);
void onTextMessage(const std::string& from, const std::map<std::string, std::string>& payloads);
protected:
virtual void serialize(YAML::Emitter &out) override;
......
......@@ -237,12 +237,13 @@ transaction_request_cb(pjsip_rx_data *rdata)
Manager::instance().startVoiceMessageNotification(account_id, voicemail);
}
} else if (request.find("MESSAGE") != std::string::npos) {
if (body) {
pjsip_endpt_respond( endpt_, NULL, rdata, PJSIP_SC_OK, NULL, NULL, NULL, NULL);
std::string text {(char*)body->data, (char*)body->data+body->len};
account->onTextMessage(peerNumber, text);
return PJ_FALSE;
}
// Reply 200 immediatly (RFC 3428, ch. 7)
try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, nullptr, nullptr, nullptr);
// Process message content in case of multi-part body
auto payloads = InstantMessaging::parseSipMessage(rdata->msg_info.msg);
if (payloads.size() > 0)
account->onTextMessage(peerNumber, payloads);
return PJ_FALSE;
}
try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, NULL, NULL, NULL);
......
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