Commit 9a12c78a authored by Hugo Lefeuvre's avatar Hugo Lefeuvre

call management: add ringing timeout

Currently there is no timeout for received calls. If a peer is
connected but doesn't answer a call, then this call will stay in
RINGING state 'forever' until the calling user manually aborts
it or peer becomes unreachable.

This patch implements a timeout system: When a call starts ringing
a job is scheduled in a fixed amount of time (corresponding to a new
user preference, by default 30s) to check whether the call is still
ringing or not. If the call is still ringing after timeout, hangup()
is called with 486 (BUSY) state and ringing stops.

Change-Id: I49218f5d4bdef8e4a8cee35fa6dde80f572b46c6
Gitlab: #42
parent 4a4c8fdd
......@@ -881,6 +881,16 @@
</arg>
</method>
<method name="getRingingTimeout" tp:name-for-bindings="getRingingTimeout">
<arg type="i" name="timeout" direction="out">
</arg>
</method>
<method name="setRingingTimeout" tp:name-for-bindings="setRingingTimeout">
<arg type="i" name="timeout" direction="in">
</arg>
</method>
<!-- Hook configuration -->
<method name="getHookSettings" tp:name-for-bindings="getHookSettings">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/>
......
......@@ -425,6 +425,18 @@ DBusConfigurationManager::getHistoryLimit() -> decltype(DRing::getHistoryLimit()
return DRing::getHistoryLimit();
}
void
DBusConfigurationManager::setRingingTimeout(const int32_t& timeout)
{
DRing::setRingingTimeout(timeout);
}
auto
DBusConfigurationManager::getRingingTimeout() -> decltype(DRing::getRingingTimeout())
{
return DRing::getRingingTimeout();
}
void
DBusConfigurationManager::setAccountsOrder(const std::string& order)
{
......
......@@ -124,6 +124,8 @@ class DBusConfigurationManager :
void setIsAlwaysRecording(const bool& rec);
void setHistoryLimit(const int32_t& days);
int32_t getHistoryLimit();
void setRingingTimeout(const int32_t& timeout);
int32_t getRingingTimeout();
void setAccountsOrder(const std::string& order);
std::map<std::string, std::string> getHookSettings();
void setHookSettings(const std::map<std::string, std::string>& settings);
......
......@@ -143,6 +143,9 @@ void setIsAlwaysRecording(bool rec);
void setHistoryLimit(int32_t days);
int32_t getHistoryLimit();
void setRingingTimeout(int32_t timeout);
int32_t getRingingTimeout();
void setAccountsOrder(const std::string& order);
std::map<std::string, std::string> getHookSettings();
......
......@@ -139,6 +139,9 @@ void setIsAlwaysRecording(bool rec);
void setHistoryLimit(int32_t days);
int32_t getHistoryLimit();
void setRingingTimeout(int32_t timeout);
int32_t getRingingTimeout();
void setAccountsOrder(const std::string& order);
std::map<std::string, std::string> getHookSettings();
......
......@@ -85,12 +85,29 @@ Call::Call(Account& account, const std::string& id, Call::CallType type,
{
updateDetails(details);
addStateListener([this](UNUSED Call::CallState call_state,
UNUSED Call::ConnectionState cnx_state,
addStateListener([this](Call::CallState call_state,
Call::ConnectionState cnx_state,
UNUSED int code) {
checkPendingIM();
checkAudio();
// if call just started ringing, schedule call timeout
if (type_ == CallType::INCOMING and cnx_state == ConnectionState::RINGING) {
auto timeout = Manager::instance().getRingingTimeout();
RING_DBG("Scheduling call timeout in %d seconds", timeout);
std::weak_ptr<Call> callWkPtr = shared_from_this();
Manager::instance().scheduler().scheduleIn([callWkPtr]{
if (auto callShPtr = callWkPtr.lock()) {
if (callShPtr->getConnectionState() == Call::ConnectionState::RINGING) {
RING_DBG("Call %s is still ringing after timeout, setting state to BUSY",
callShPtr->getCallId().c_str());
callShPtr->hangup(PJSIP_SC_BUSY_HERE);
}
}
}, std::chrono::seconds(timeout));
}
// kill pending subcalls at disconnect
if (call_state == CallState::OVER)
hangupCalls(safePopSubcalls(), 0);
......@@ -159,10 +176,11 @@ Call::validStateTransition(CallState newState)
case CallState::ACTIVE:
switch (newState) {
case CallState::BUSY:
case CallState::HOLD:
case CallState::MERROR:
return true;
default: // INACTIVE, ACTIVE, BUSY
default: // INACTIVE, ACTIVE
return false;
}
......@@ -441,17 +459,27 @@ Call::subcallStateChanged(Call& subcall,
// Subcall is busy or failed
if (new_state >= CallState::BUSY) {
RING_WARN("[call:%s] subcall %s failed", getCallId().c_str(), subcall.getCallId().c_str());
if (new_state == CallState::BUSY)
RING_WARN("[call:%s] subcall %s busy", getCallId().c_str(), subcall.getCallId().c_str());
else
RING_WARN("[call:%s] subcall %s failed", getCallId().c_str(), subcall.getCallId().c_str());
std::lock_guard<std::recursive_mutex> lk {callMutex_};
subcalls_.erase(getPtr(subcall));
// Parent call fails if all subcalls have failed
if (subcalls_.empty())
// XXX: first idea was to use std::errc::host_unreachable, but it's not available on some platforms
// like mingw.
setState(CallState::MERROR, ConnectionState::DISCONNECTED, static_cast<int>(std::errc::io_error));
else
// Parent call fails if last subcall is busy or failed
if (subcalls_.empty()) {
if (new_state == CallState::BUSY) {
setState(CallState::BUSY, ConnectionState::DISCONNECTED, static_cast<int>(std::errc::device_or_resource_busy));
} else {
// XXX: first idea was to use std::errc::host_unreachable, but it's not available on some platforms
// like mingw.
setState(CallState::MERROR, ConnectionState::DISCONNECTED, static_cast<int>(std::errc::io_error));
}
removeCall();
} else {
RING_DBG("[call:%s] remains %zu subcall(s)", getCallId().c_str(), subcalls_.size());
}
return;
}
......
......@@ -682,6 +682,18 @@ setHistoryLimit(int32_t days)
ring::Manager::instance().setHistoryLimit(days);
}
int32_t
getRingingTimeout()
{
return ring::Manager::instance().getRingingTimeout();
}
void
setRingingTimeout(int32_t timeout)
{
ring::Manager::instance().setRingingTimeout(timeout);
}
bool
setAudioManager(const std::string& api)
{
......
......@@ -126,6 +126,9 @@ void setIsAlwaysRecording(bool rec);
void setHistoryLimit(int32_t days);
int32_t getHistoryLimit();
void setRingingTimeout(int32_t timeout);
int32_t getRingingTimeout();
void setAccountsOrder(const std::string& order);
std::map<std::string, std::string> getHookSettings();
......
......@@ -2387,6 +2387,20 @@ Manager::getHistoryLimit() const
return preferences.getHistoryLimit();
}
void
Manager::setRingingTimeout(int timeout)
{
RING_DBG("Set ringing timeout");
preferences.setRingingTimeout(timeout);
saveConfig();
}
int
Manager::getRingingTimeout() const
{
return preferences.getRingingTimeout();
}
bool
Manager::setAudioManager(const std::string &api)
{
......
......@@ -147,7 +147,6 @@ class Manager {
*/
bool hangupCall(const std::string& id);
/**
* Functions which occur with a user's action
* Hangup the conference (hangup every participants)
......@@ -620,6 +619,20 @@ class Manager {
*/
int getHistoryLimit() const;
/**
* Set ringing timeout (number of seconds after which a call will
* enter BUSY state if not answered).
* @param timeout in seconds
*/
void setRingingTimeout(int timeout);
/**
* Get ringing timeout (number of seconds after which a call will
* enter BUSY state if not answered).
* @return timeout in seconds
*/
int getRingingTimeout() const;
/**
* Get the audio manager
* @return int The audio manager
......
......@@ -82,6 +82,7 @@ const char * const Preferences::REGISTRATION_EXPIRE_KEY = "registrationexpire";
static const char * const ORDER_KEY = "order";
static const char * const AUDIO_API_KEY = "audioApi";
static const char * const HISTORY_LIMIT_KEY = "historyLimit";
static const char * const RINGING_TIMEOUT = "ringingTimeout";
static const char * const HISTORY_MAX_CALLS_KEY = "historyMaxCalls";
static const char * const ZONE_TONE_CHOICE_KEY = "zoneToneChoice";
static const char * const PORT_NUM_KEY = "portNum";
......@@ -145,6 +146,7 @@ static const char * const ALSA_DFT_CARD = "0"; /** Default sound car
Preferences::Preferences() :
accountOrder_("")
, historyLimit_(0)
, ringingTimeout_(30)
, historyMaxCalls_(20)
, zoneToneChoice_(DFT_ZONE) // DFT_ZONE
, registrationExpire_(180)
......@@ -202,6 +204,7 @@ void Preferences::serialize(YAML::Emitter &out)
out << YAML::Key << CONFIG_LABEL << YAML::Value << YAML::BeginMap;
out << YAML::Key << HISTORY_LIMIT_KEY << YAML::Value << historyLimit_;
out << YAML::Key << RINGING_TIMEOUT << YAML::Value << ringingTimeout_;
out << YAML::Key << HISTORY_MAX_CALLS_KEY << YAML::Value << historyMaxCalls_;
out << YAML::Key << MD5_HASH_KEY << YAML::Value << md5Hash_;
out << YAML::Key << ORDER_KEY << YAML::Value << accountOrder_;
......@@ -218,6 +221,7 @@ void Preferences::unserialize(const YAML::Node &in)
parseValue(node, ORDER_KEY, accountOrder_);
parseValue(node, HISTORY_LIMIT_KEY, historyLimit_);
parseValue(node, RINGING_TIMEOUT, ringingTimeout_);
parseValue(node, HISTORY_MAX_CALLS_KEY, historyMaxCalls_);
parseValue(node, ZONE_TONE_CHOICE_KEY, zoneToneChoice_);
parseValue(node, REGISTRATION_EXPIRE_KEY, registrationExpire_);
......
......@@ -69,6 +69,14 @@ class Preferences : public Serializable {
historyLimit_ = lim;
}
int getRingingTimeout() const {
return ringingTimeout_;
}
void setRingingTimeout(int timeout) {
ringingTimeout_ = timeout;
}
int getHistoryMaxCalls() const {
return historyMaxCalls_;
}
......@@ -120,6 +128,7 @@ class Preferences : public Serializable {
std::string accountOrder_;
int historyLimit_;
int historyMaxCalls_;
int ringingTimeout_;
std::string zoneToneChoice_;
int registrationExpire_;
int portNum_;
......
......@@ -713,6 +713,14 @@ SIPCall::onFailure(signed cause)
removeCall();
}
void
SIPCall::onBusyHere()
{
setState(CallState::BUSY, ConnectionState::DISCONNECTED);
Manager::instance().callBusy(*this);
removeCall();
}
void
SIPCall::onClosed()
{
......
......@@ -133,6 +133,12 @@ public: // SIP related
*/
void onFailure(signed cause=0);
/**
* Peer answered busy
* @param
*/
void onBusyHere();
/**
* Peer close the connection
* @param
......
......@@ -836,10 +836,13 @@ invite_session_state_changed_cb(pjsip_inv_session *inv, pjsip_event *ev)
case PJSIP_INV_STATE_DISCONNECTED:
switch (inv->cause) {
// When a peer's device replies busy
case PJSIP_SC_BUSY_HERE:
call->onBusyHere();
break;
// When the peer manually refuse the call
case PJSIP_SC_DECLINE:
case PJSIP_SC_BUSY_EVERYWHERE:
case PJSIP_SC_BUSY_HERE:
if (inv->role != PJSIP_ROLE_UAC)
break;
// close call
......
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