Ring Daemon
Loading...
Searching...
No Matches
call.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "call.h"
19#include "account.h"
20#include "jamidht/jamiaccount.h"
21#include "manager.h"
22#ifdef ENABLE_PLUGIN
24#include "plugin/streamdata.h"
25#endif
26#include "jami/call_const.h"
27#include "client/jami_signal.h"
28#include "call_factory.h"
29#include "string_utils.h"
30
31#include "json_utils.h"
32
33#include <dhtnet/ip_utils.h>
34#include <opendht/thread_pool.h>
35
36#include <system_error>
37#include <functional>
38#include <utility>
39
40namespace jami {
41
48template<typename T>
49static inline void
51{
52 for (auto& call : calls) {
53 if (not pred(call.get()))
54 continue;
55 dht::ThreadPool::io().run([call = std::move(call), errcode] { call->hangup(errcode); });
56 }
57}
58
62static inline void
64{
65 hangupCallsIf(std::move(callptr_list), errcode, [](Call*) { return true; });
66}
67
68//==============================================================================
69
70Call::Call(const std::shared_ptr<Account>& account, const std::string& id, Call::CallType type)
71 : id_(id)
72 , type_(type)
73 , account_(account)
74 , timeoutTimer_(*Manager::instance().ioContext())
75{
77 checkPendingIM();
79 if (auto call = callWkPtr.lock())
80 call->checkAudio();
81 });
82
83 // if call just started ringing, schedule call timeout
86 timeoutTimer_.async_wait([callWkPtr = weak_from_this()](const std::error_code& ec) {
87 if (ec == asio::error::operation_aborted)
88 return;
89 if (auto callShPtr = callWkPtr.lock()) {
90 if (callShPtr->getConnectionState() == Call::ConnectionState::RINGING) {
91 JAMI_LOG("Call {} is still ringing after timeout, setting state to BUSY",
92 callShPtr->getCallId());
95 }
96 }
97 });
98 }
99
100 if (!isSubcall()) {
102 reason_ = "no_device";
103 }
104 if (cnx_state == ConnectionState::CONNECTED && duration_start_ == time_point::min())
105 duration_start_ = clock::now();
107 if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
108 if (toUsername().find('/') == std::string::npos && getCallType() == CallType::OUTGOING) {
109 if (auto* cm = jamiAccount->convModule(true))
110 cm->addCallHistoryMessage(getPeerNumber(), getCallDuration().count(), reason_);
111 }
112 monitor();
113 }
114 }
115 }
116
117 // kill pending subcalls at disconnect
119 hangupCalls(safePopSubcalls(), 0);
120
121 return true;
122 });
123
125}
126
128
129void
131{
132 auto this_ = shared_from_this();
137 if (auto account = account_.lock())
138 account->detach(this_);
139 parent_.reset();
140 subcalls_.clear();
141}
142
143std::string
145{
146 if (auto shared = account_.lock())
147 return shared->getAccountID();
148 JAMI_ERR("No account detected");
149 return {};
150}
151
154{
155 std::lock_guard lock(callMutex_);
156 return connectionState_;
157}
158
161{
162 std::lock_guard lock(callMutex_);
163 return callState_;
164}
165
166bool
167Call::validStateTransition(CallState newState)
168{
169 // Notice to developper:
170 // - list only permitted transition (return true)
171 // - let non permitted ones as default case (return false)
172
173 // always permited
175 return true;
176
177 switch (callState_) {
179 switch (newState) {
181 case CallState::BUSY:
184 return true;
185 default: // INACTIVE, HOLD
186 return false;
187 }
188
190 switch (newState) {
191 case CallState::BUSY:
193 case CallState::HOLD:
195 return true;
196 default: // INACTIVE, ACTIVE
197 return false;
198 }
199
200 case CallState::HOLD:
201 switch (newState) {
204 return true;
205 default: // INACTIVE, HOLD, BUSY, PEER_BUSY, MERROR
206 return false;
207 }
208
209 case CallState::BUSY:
210 switch (newState) {
212 return true;
213 default: // INACTIVE, ACTIVE, HOLD, BUSY, PEER_BUSY
214 return false;
215 }
216
217 default: // MERROR
218 return false;
219 }
220}
221
222bool
224{
225 std::unique_lock<std::recursive_mutex> lock(callMutex_);
226 JAMI_DBG("[call:%s] state change %u/%u, cnx %u/%u, code %d",
227 id_.c_str(),
228 (unsigned) callState_,
229 (unsigned) call_state,
230 (unsigned) connectionState_,
231 (unsigned) cnx_state,
232 code);
233
234 if (callState_ != call_state) {
235 if (not validStateTransition(call_state)) {
236 JAMI_ERR("[call:%s] invalid call state transition from %u to %u",
237 id_.c_str(),
238 (unsigned) callState_,
239 (unsigned) call_state);
240 return false;
241 }
242 } else if (connectionState_ == cnx_state)
243 return true; // no changes as no-op
244
245 // Emit client state only if changed
250
251 for (auto it = stateChangedListeners_.begin(); it != stateChangedListeners_.end();) {
252 if ((*it)(callState_, connectionState_, code))
253 ++it;
254 else
255 it = stateChangedListeners_.erase(it);
256 }
257
259 if (not parent_) {
260 JAMI_DBG("[call:%s] emit client call state change %s, code %d", id_.c_str(), new_client_state.c_str(), code);
261 lock.unlock();
263 }
264 }
265
266 return true;
267}
268
269bool
271{
272 std::lock_guard lock(callMutex_);
273 return setState(call_state, connectionState_, code);
274}
275
276bool
278{
279 std::lock_guard lock(callMutex_);
280 return setState(callState_, cnx_state, code);
281}
282
283std::string
285{
286 using namespace libjami::Call;
287
288 switch (getState()) {
290 switch (getConnectionState()) {
293
296
298 return StateEvent::HUNGUP;
299
301 default:
302 return StateEvent::CURRENT;
303 }
304
305 case CallState::HOLD:
307 return StateEvent::HUNGUP;
308 return StateEvent::HOLD;
309
310 case CallState::BUSY:
311 return StateEvent::BUSY;
312
315
317 switch (getConnectionState()) {
320
323
325 return StateEvent::CURRENT;
326
327 default:
329 }
330
331 case CallState::OVER:
332 return StateEvent::OVER;
333
335 default:
336 return StateEvent::FAILURE;
337 }
338}
339
340bool
346
347std::map<std::string, std::string>
365
366void
367Call::onTextMessage(std::map<std::string, std::string>&& messages)
368{
369 auto it = messages.find("application/confInfo+json");
370 if (it != messages.end()) {
371 setConferenceInfo(it->second);
372 return;
373 }
374
375 it = messages.find("application/confOrder+json");
376 if (it != messages.end()) {
377 if (auto conf = conf_.lock())
378 conf->onConfOrder(getCallId(), it->second);
379 return;
380 }
381
382 {
383 std::lock_guard lk {callMutex_};
384 if (parent_) {
385 pendingInMessages_.emplace_back(std::move(messages), "");
386 return;
387 }
388 }
389#ifdef ENABLE_PLUGIN
390 auto& pluginChatManager = Manager::instance().getJamiPluginManager().getChatServicesManager();
391 if (pluginChatManager.hasHandlers()) {
392 pluginChatManager.publishMessage(
393 std::make_shared<JamiMessage>(getAccountId(), getPeerNumber(), true, messages, false));
394 }
395#endif
397}
398
399void
406
407void
409{
410 std::lock_guard lk {callMutex_};
411
412 // Add subCall only if call is not connected or terminated
413 // Because we only want to addSubCall if the peer didn't answer
414 // So till it's <= RINGING
417 subcall.removeCall();
418 return;
419 }
420
421 if (not subcalls_.emplace(getPtr(subcall)).second) {
422 JAMI_ERR("[call:%s] add twice subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
423 return;
424 }
425
426 JAMI_DBG("[call:%s] add subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
427 subcall.parent_ = getPtr(*this);
428
429 for (const auto& msg : pendingOutMessages_)
430 subcall.sendTextMessage(msg.first, msg.second);
431
432 subcall.addStateListener(
433 [sub = subcall.weak_from_this(),
436 if (auto p = parent.lock()) {
437 if (auto s = sub.lock()) {
438 p->subcallStateChanged(*s, new_state, new_cstate, code);
439 }
440 }
441 });
442 return true;
443 });
444}
445
451void
452Call::subcallStateChanged(Call& subcall, Call::CallState new_state, Call::ConnectionState new_cstate, int code)
453{
454 {
455 // This condition happens when a subcall hangups/fails after removed from parent's list.
456 // This is normal to keep parent_ != nullptr on the subcall, as it's the way to flag it
457 // as an subcall and not a master call.
458 // XXX: having a way to unsubscribe the state listener could be better than such test
459 std::lock_guard lk {callMutex_};
460 auto sit = subcalls_.find(getPtr(subcall));
461 if (sit == subcalls_.end())
462 return;
463 }
464
465 // We found a responding device: hangup all other subcalls and merge
466 if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
467 JAMI_DBG("[call:%s] subcall %s answered by peer", getCallId().c_str(), subcall.getCallId().c_str());
468
469 hangupCallsIf(safePopSubcalls(), 0, [&](const Call* call) { return call != &subcall; });
470 merge(subcall);
471 Manager::instance().peerAnsweredCall(*this);
472 return;
473 }
474
475 // Hangup the call if any device hangup or send busy
476 if ((new_state == CallState::ACTIVE or new_state == CallState::PEER_BUSY)
477 and new_cstate == ConnectionState::DISCONNECTED) {
478 JAMI_WARN("[call:%s] subcall %s hangup by peer", getCallId().c_str(), subcall.getCallId().c_str());
479 reason_ = new_state == CallState::ACTIVE ? "declined" : "busy";
480 hangupCalls(safePopSubcalls(), 0);
481 Manager::instance().peerHungupCall(*this);
482 removeCall();
483 return;
484 }
485
486 // Subcall is busy or failed
487 if (new_state >= CallState::BUSY) {
488 if (new_state == CallState::BUSY || new_state == CallState::PEER_BUSY)
489 JAMI_WARN("[call:%s] subcall %s busy", getCallId().c_str(), subcall.getCallId().c_str());
490 else
491 JAMI_WARN("[call:%s] subcall %s failed", getCallId().c_str(), subcall.getCallId().c_str());
492 std::lock_guard lk {callMutex_};
493 subcalls_.erase(getPtr(subcall));
494
495 // Parent call fails if last subcall is busy or failed
496 if (subcalls_.empty()) {
497 if (new_state == CallState::BUSY) {
498 setState(CallState::BUSY, ConnectionState::DISCONNECTED, PJSIP_SC_BUSY_HERE);
499 } else if (new_state == CallState::PEER_BUSY) {
500 setState(CallState::PEER_BUSY, ConnectionState::DISCONNECTED, PJSIP_SC_BUSY_EVERYWHERE);
501 } else {
502 // Subcall failed with a non-busy state so we send a generic server error
503 setState(CallState::MERROR, ConnectionState::DISCONNECTED, PJSIP_SC_INTERNAL_SERVER_ERROR);
504 }
505 Manager::instance().callFailure(*this);
506 removeCall(code);
507 } else {
508 JAMI_DEBUG("[call:{}] Subcalls remaining : {}", getCallId(), subcalls_.size());
509 }
510
511 return;
512 }
513
514 // Copy call/cnx states (forward only)
515 if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
516 setState(new_state);
517 }
518 if (static_cast<unsigned>(connectionState_) < static_cast<unsigned>(new_cstate)
519 and static_cast<unsigned>(new_cstate) <= static_cast<unsigned>(ConnectionState::RINGING)) {
520 setState(new_cstate);
521 }
522}
523
526void
527Call::merge(Call& subcall)
528{
529 JAMI_DBG("[call:%s] merge subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
530
531 // Merge data
532 pendingInMessages_ = std::move(subcall.pendingInMessages_);
533 if (peerNumber_.empty())
534 peerNumber_ = std::move(subcall.peerNumber_);
535 peerDisplayName_ = std::move(subcall.peerDisplayName_);
536 setState(subcall.getState(), subcall.getConnectionState());
537
538 std::weak_ptr<Call> subCallWeak = subcall.shared_from_this();
539 runOnMainThread([subCallWeak] {
540 if (auto subcall = subCallWeak.lock())
541 subcall->removeCall();
542 });
543}
544
548void
549Call::checkPendingIM()
550{
551 std::lock_guard lk {callMutex_};
552
553 auto state = getStateStr();
554 // Let parent call handles IM after the merge
555 if (not parent_) {
557 for (const auto& msg : pendingInMessages_)
558 Manager::instance().incomingMessage(getAccountId(), getCallId(), getPeerNumber(), msg.first);
559 pendingInMessages_.clear();
560
561 std::weak_ptr<Call> callWkPtr = shared_from_this();
562 runOnMainThread([callWkPtr, pending = std::move(pendingOutMessages_)] {
563 if (auto call = callWkPtr.lock())
564 for (const auto& msg : pending)
565 call->sendTextMessage(msg.first, msg.second);
566 });
567 }
568 }
569}
570
573void
574Call::checkAudio()
575{
576 using namespace libjami::Call;
577
578 auto state = getStateStr();
579 if (state == StateEvent::RINGING) {
580 Manager::instance().peerRingingCall(*this);
581 } else if (state == StateEvent::BUSY) {
582 Manager::instance().callBusy(*this);
583 }
584}
585
586// Helper to safely pop subcalls list
587Call::SubcallSet
588Call::safePopSubcalls()
589{
590 std::lock_guard lk {callMutex_};
591 // std::exchange is C++14
592 auto old_value = std::move(subcalls_);
593 subcalls_.clear();
594 return old_value;
595}
596
597void
598Call::setConferenceInfo(const std::string& msg)
599{
600 ConfInfo newInfo;
601 Json::Value json;
602 if (json::parse(msg, json)) {
603 if (json.isObject()) {
604 // new confInfo
605 if (json.isMember("p")) {
606 for (const auto& participantInfo : json["p"]) {
607 ParticipantInfo pInfo;
608 if (!participantInfo.isMember("uri"))
609 continue;
610 pInfo.fromJson(participantInfo);
611 newInfo.emplace_back(pInfo);
612 }
613 }
614 if (json.isMember("v")) {
615 newInfo.v = json["v"].asInt();
616 peerConfProtocol_ = newInfo.v;
617 }
618 if (json.isMember("w"))
619 newInfo.w = json["w"].asInt();
620 if (json.isMember("h"))
621 newInfo.h = json["h"].asInt();
622 } else {
623 // old confInfo
624 for (const auto& participantInfo : json) {
625 ParticipantInfo pInfo;
626 if (!participantInfo.isMember("uri"))
627 continue;
628 pInfo.fromJson(participantInfo);
629 newInfo.emplace_back(pInfo);
630 }
631 }
632 }
633
634 {
635 std::lock_guard lk(confInfoMutex_);
636 if (not isConferenceParticipant()) {
637 // confID_ empty -> participant set confInfo with the received one
638 confInfo_ = std::move(newInfo);
639
640 // Create sink for each participant
641#ifdef ENABLE_VIDEO
642 createSinks(confInfo_);
643#endif
644 // Inform client that layout has changed
645 jami::emitSignal<libjami::CallSignal::OnConferenceInfosUpdated>(id_, confInfo_.toVectorMapStringString());
646 } else if (auto conf = conf_.lock()) {
647 conf->mergeConfInfo(newInfo, getPeerNumber());
648 }
649 }
650}
651
652void
653Call::sendConfOrder(const Json::Value& root)
654{
655 std::map<std::string, std::string> messages;
656 messages["application/confOrder+json"] = json::toString(root);
657
658 auto w = getAccount();
659 auto account = w.lock();
660 if (account)
661 sendTextMessage(messages, account->getFromUri());
662}
663
664void
665Call::sendConfInfo(const std::string& json)
666{
667 std::map<std::string, std::string> messages;
668 messages["application/confInfo+json"] = json;
669
670 auto w = getAccount();
671 auto account = w.lock();
672 if (account)
673 sendTextMessage(messages, account->getFromUri());
674}
675
676void
677Call::resetConfInfo()
678{
679 sendConfInfo("{}");
680}
681
682} // namespace jami
Interface to protocol account (ex: SIPAccount) It can be enable on loading or activate after.
void removeCall(Call &call)
Remove given call instance from call list.
std::string reason_
Definition call.h:502
asio::steady_timer timeoutTimer_
Definition call.h:522
void onTextMessage(std::map< std::string, std::string > &&messages)
Definition call.cpp:367
MsgList pendingInMessages_
Definition call.h:516
const std::string & toUsername() const
Get "To" from the invite.
Definition call.h:150
std::string peerNumber_
Number of the peer.
Definition call.h:508
const std::string & getCallId() const
Return a reference on the call id.
Definition call.h:111
virtual bool isCaptureDeviceMuted(const MediaType &mediaType) const =0
std::set< std::shared_ptr< Call >, std::owner_less< std::shared_ptr< Call > > > SubcallSet
Definition call.h:93
CallState getState() const
Get the call state of the call (protected by mutex)
Definition call.cpp:160
void addSubCall(Call &call)
Attach subcall to this instance.
Definition call.cpp:408
std::weak_ptr< Account > account_
Associate account ID.
Definition call.h:494
std::string getStateStr() const
Definition call.cpp:284
virtual void monitor() const =0
ConnectionState
Tell where we're at with the call.
Definition call.h:82
std::chrono::milliseconds getCallDuration() const
Definition call.h:343
ConnectionState getConnectionState() const
Get the connection state of the call (protected by mutex)
Definition call.cpp:153
CallType type_
Type of the call.
Definition call.h:491
std::shared_ptr< Call > parent_
MultiDevice: list of attached subcall.
Definition call.h:456
bool isSubcall() const
Return true if this call instance is a subcall (internal call for multi-device handling)
Definition call.h:334
virtual std::map< std::string, std::string > getDetails() const
Definition call.cpp:348
CallState
The Call State.
Definition call.h:89
bool isIncoming() const
Tell if the call is incoming.
Definition call.h:167
Call(const std::shared_ptr< Account > &account, const std::string &id, Call::CallType type)
Constructor of a call.
Definition call.cpp:70
virtual void peerHungup()
Peer has hung up a call.
Definition call.cpp:400
virtual bool hasVideo() const =0
virtual void removeCall(int code=0)
Definition call.cpp:130
std::string peerDisplayName_
Peer Display Name.
Definition call.h:511
CallType getCallType() const
Definition call.h:123
std::weak_ptr< Conference > conf_
Unique conference ID, used exclusively in case of a conference.
Definition call.h:488
const std::string & getPeerNumber() const
Get the peer number (destination on outgoing) not protected by mutex (when created)
Definition call.h:137
SubcallSet subcalls_
Definition call.h:459
std::string getAccountId() const
Definition call.cpp:144
CallType
This determines if the call originated from the local user (OUTGOING) or from some remote peer (INCOM...
Definition call.h:101
virtual bool toggleRecording()
This method must be implemented for this interface as calls and conferences have different behavior.
Definition call.cpp:341
MsgList pendingOutMessages_
Definition call.h:464
void setConferenceInfo(const std::string &msg)
A Call can be in a conference.
Definition call.cpp:598
bool setState(CallState call_state, signed code=0)
Set the state of the call (protected by mutex)
Definition call.cpp:270
const std::string id_
MultiDevice: parent call, nullptr otherwise. Access protected by callMutex_.
Definition call.h:453
CallState callState_
Inactive/Active/Hold/Busy/Error.
Definition call.h:500
ConnectionState connectionState_
Disconnected/Progressing/Trying/Ringing/Connected.
Definition call.h:497
time_t timestamp_start_
MultiDevice: message received by subcall to merged yet.
Definition call.h:513
std::weak_ptr< Account > getAccount() const
Definition call.h:120
virtual ~Call()
Definition call.cpp:127
time_point duration_start_
Definition call.h:471
void addStateListener(StateListenerCb &&listener)
Definition call.h:319
std::recursive_mutex callMutex_
Protect every attribute that can be changed by two threads.
Definition call.h:467
Manager (controller) of daemon.
Definition manager.h:66
void callFailure(Call &call)
Handle played sound when a failure occurs.
Definition manager.cpp:2041
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
CallFactory callFactory
Definition manager.h:826
void incomingMessage(const std::string &accountId, const std::string &callId, const std::string &from, const std::map< std::string, std::string > &messages)
Notify the client with an incoming message.
Definition manager.cpp:1898
std::chrono::seconds getRingingTimeout() const
Get ringing timeout (number of seconds after which a call will enter BUSY state if not answered).
Definition manager.cpp:2391
virtual bool startRecording(const std::string &path)
Start recording.
virtual void stopRecording()
Stop recording.
virtual bool toggleRecording()
This method must be implemented for this interface as calls and conferences have different behavior.
bool isRecording() const
Return recording state (true/false)
Definition recordable.h:36
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:238
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_LOG(formatstr,...)
Definition logger.h:237
void setState(const std::string &accountID, const State migrationState)
static void hangupCallsIf(Call::SubcallSet &&calls, int errcode, T pred)
Hangup many calls with same error code, filtered by a predicate.
Definition call.cpp:50
void emitSignal(Args... args)
Definition jami_signal.h:64
static constexpr const char * bool_to_str(bool b) noexcept
std::shared_ptr< Call > getPtr(Call &call)
Obtain a shared smart pointer of instance.
Definition call.h:531
@ MEDIA_AUDIO
Definition media_codec.h:46
@ MEDIA_VIDEO
Definition media_codec.h:47
static void hangupCalls(Call::SubcallSet &&callptr_list, int errcode)
Hangup many calls with same error code.
Definition call.cpp:63
static void runOnMainThread(Callback &&cb)
Definition manager.h:930
static constexpr char CONF_ID[]
Definition call_const.h:48
static constexpr char CALL_TYPE[]
Definition call_const.h:43
static constexpr char TO_USERNAME[]
Definition call_const.h:50
static constexpr char TIMESTAMP_START[]
Definition call_const.h:49
static constexpr char VIDEO_MUTED[]
Definition call_const.h:54
static constexpr char AUDIO_ONLY[]
Definition call_const.h:56
static constexpr char CALL_STATE[]
Definition call_const.h:47
static constexpr char DISPLAY_NAME[]
Definition call_const.h:46
static constexpr char PEER_NUMBER[]
Definition call_const.h:44
static constexpr char ACCOUNTID[]
Definition call_const.h:51
static constexpr char AUDIO_MUTED[]
Definition call_const.h:53
static constexpr char BUSY[]
Definition call_const.h:32
static constexpr char OVER[]
Definition call_const.h:37
static constexpr char RINGING[]
Definition call_const.h:29
static constexpr char HOLD[]
Definition call_const.h:35
static constexpr char INACTIVE[]
Definition call_const.h:36
static constexpr char INCOMING[]
Definition call_const.h:27
static constexpr char CURRENT[]
Definition call_const.h:30
static constexpr char FAILURE[]
Definition call_const.h:34
static constexpr char CONNECTING[]
Definition call_const.h:28
static constexpr char HUNGUP[]
Definition call_const.h:31
static constexpr char PEER_BUSY[]
Definition call_const.h:33
void fromJson(const Json::Value &v)
Definition conference.h:79